mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
feat(MA): add campaign name mappings configuration and validation (#40182)
Co-authored-by: Javier Bahamondes <javierbahamondes@Javiers-MacBook-Pro.local>
This commit is contained in:
committed by
GitHub
parent
0602183cfa
commit
515d0d25ea
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 191 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 187 KiB |
@@ -326,6 +326,7 @@ export const FEATURE_FLAGS = {
|
||||
REPLAY_CLIENT_SIDE_DECOMPRESSION: 'replay-client-side-decompression', // owner: @pauldambra #team-replay
|
||||
COHORT_CALCULATION_CHUNKED: 'cohort-calculation-chunked', // owner: @gustavo #team-feature-flags
|
||||
EXPERIMENTS_USE_NEW_QUERY_BUILDER: 'experiments-use-new-query-builder', // owner: @andehen #team-experiments
|
||||
ADVANCE_MARKETING_ANALYTICS_SETTINGS: 'advance-marketing-analytics-settings', // owner: @jabahamondes #team-web-analytics
|
||||
} as const
|
||||
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
|
||||
|
||||
|
||||
@@ -16663,6 +16663,18 @@
|
||||
"attribution_window_days": {
|
||||
"type": "number"
|
||||
},
|
||||
"campaign_name_mappings": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"conversion_goals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConversionGoalFilter"
|
||||
|
||||
@@ -4023,6 +4023,7 @@ export interface MarketingAnalyticsConfig {
|
||||
conversion_goals?: ConversionGoalFilter[]
|
||||
attribution_window_days?: number
|
||||
attribution_mode?: AttributionMode
|
||||
campaign_name_mappings?: Record<string, Record<string, string[]>>
|
||||
}
|
||||
|
||||
export enum MarketingAnalyticsBaseColumns {
|
||||
|
||||
@@ -81,6 +81,9 @@ SettingsEnvironmentRevenueAnalytics.args = { sectionId: 'environment-revenue-ana
|
||||
|
||||
export const SettingsEnvironmentMarketingAnalytics: Story = Template.bind({})
|
||||
SettingsEnvironmentMarketingAnalytics.args = { sectionId: 'environment-marketing-analytics' }
|
||||
SettingsEnvironmentMarketingAnalytics.parameters = {
|
||||
featureFlags: [...Object.values(FEATURE_FLAGS), 'advance-marketing-analytics-settings'],
|
||||
}
|
||||
|
||||
export const SettingsEnvironmentWebAnalytics: Story = Template.bind({})
|
||||
SettingsEnvironmentWebAnalytics.args = { sectionId: 'environment-web-analytics' }
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { useActions, useValues } from 'kea'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { IconPlusSmall, IconTrash } from '@posthog/icons'
|
||||
import { LemonButton, LemonInput, LemonSelect, LemonTag } from '@posthog/lemon-ui'
|
||||
|
||||
import { externalDataSources } from '~/queries/schema/schema-general'
|
||||
|
||||
import { marketingAnalyticsSettingsLogic } from '../../logic/marketingAnalyticsSettingsLogic'
|
||||
import { VALID_NATIVE_MARKETING_SOURCES } from '../../logic/utils'
|
||||
|
||||
const SEPARATOR = ','
|
||||
|
||||
export function CampaignNameMappingsConfiguration(): JSX.Element {
|
||||
const { marketingAnalyticsConfig } = useValues(marketingAnalyticsSettingsLogic)
|
||||
const { updateCampaignNameMappings } = useActions(marketingAnalyticsSettingsLogic)
|
||||
|
||||
const campaignMappings = marketingAnalyticsConfig?.campaign_name_mappings || {}
|
||||
const [selectedSource, setSelectedSource] = useState<string>('')
|
||||
const [newCleanName, setNewCleanName] = useState('')
|
||||
const [newRawValues, setNewRawValues] = useState('')
|
||||
|
||||
const availableSources = externalDataSources.filter((source) =>
|
||||
VALID_NATIVE_MARKETING_SOURCES.includes(source as any)
|
||||
)
|
||||
|
||||
const updateMappings = (newMappings: Record<string, Record<string, string[]>>): void => {
|
||||
updateCampaignNameMappings(newMappings)
|
||||
}
|
||||
|
||||
const addMapping = (): void => {
|
||||
if (!selectedSource || !newCleanName.trim() || !newRawValues.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
const rawValuesArray = newRawValues
|
||||
.split(SEPARATOR)
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => v.length > 0)
|
||||
|
||||
const sourceMappings = campaignMappings[selectedSource] || {}
|
||||
|
||||
updateMappings({
|
||||
...campaignMappings,
|
||||
[selectedSource]: {
|
||||
...sourceMappings,
|
||||
[newCleanName.trim()]: rawValuesArray,
|
||||
},
|
||||
})
|
||||
|
||||
setNewCleanName('')
|
||||
setNewRawValues('')
|
||||
}
|
||||
|
||||
const removeMapping = (source: string, cleanName: string): void => {
|
||||
const sourceMappings = { ...campaignMappings[source] }
|
||||
delete sourceMappings[cleanName]
|
||||
|
||||
if (Object.keys(sourceMappings).length === 0) {
|
||||
const newMappings = { ...campaignMappings }
|
||||
delete newMappings[source]
|
||||
updateMappings(newMappings)
|
||||
} else {
|
||||
updateMappings({
|
||||
...campaignMappings,
|
||||
[source]: sourceMappings,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalMappings = Object.values(campaignMappings).reduce(
|
||||
(sum, sourceMappings) => sum + Object.keys(sourceMappings).length,
|
||||
0
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-1">Campaign name mappings</h3>
|
||||
<p className="text-muted mb-4">
|
||||
Map UTM campaign values to your ad platform campaign names for proper conversion attribution. Ad
|
||||
platforms (LinkedIn, Google, TikTok, etc.) don't store UTM parameters—they only have campaign names.
|
||||
PostHog joins conversions to paid campaigns by matching{' '}
|
||||
<code className="text-xs">utm_campaign</code> values with campaign names from your ad integrations.
|
||||
If your <code className="text-xs">utm_campaign</code> doesn't exactly match your ad platform
|
||||
campaign name (e.g., "2025q3_paid_social_linkedin" vs "TOFU Video Views | LinkedIn | Global"), your
|
||||
conversions won't attribute to the paid campaign. Use this to map multiple UTM variations to the
|
||||
correct campaign name.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{totalMappings > 0 && (
|
||||
<div className="border rounded p-4 space-y-4">
|
||||
<h4 className="font-semibold">Current mappings ({totalMappings})</h4>
|
||||
{Object.entries(campaignMappings).map(([source, sourceMappings]) => (
|
||||
<div key={source} className="space-y-2">
|
||||
<div className="font-medium text-sm text-muted">{source}</div>
|
||||
{Object.entries(sourceMappings).map(([cleanName, rawValues]) => (
|
||||
<div
|
||||
key={cleanName}
|
||||
className="flex items-start justify-between bg-bg-light rounded p-3"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium mb-2">{cleanName}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{(rawValues as string[]).map((rawValue) => (
|
||||
<LemonTag key={rawValue}>{rawValue}</LemonTag>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<LemonButton
|
||||
type="secondary"
|
||||
size="small"
|
||||
icon={<IconTrash />}
|
||||
onClick={() => removeMapping(source, cleanName)}
|
||||
tooltip="Remove mapping"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border rounded p-4 space-y-3">
|
||||
<h4 className="font-semibold">Add new mapping</h4>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Data source</label>
|
||||
<LemonSelect
|
||||
value={selectedSource}
|
||||
onChange={setSelectedSource}
|
||||
options={[
|
||||
{ label: 'Select a source...', value: '' },
|
||||
...availableSources.map((source) => ({ label: source, value: source })),
|
||||
]}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Ad platform campaign name</label>
|
||||
<LemonInput
|
||||
value={newCleanName}
|
||||
onChange={setNewCleanName}
|
||||
placeholder="e.g., campaign name from the Data Warehouse table (e.g., TOFU Video Views | LinkedIn | Global)"
|
||||
fullWidth
|
||||
/>
|
||||
<div className="text-xs text-muted mt-1">
|
||||
The exact campaign name from your ad platform (LinkedIn, Google, etc.)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">UTM campaign values to map</label>
|
||||
<LemonInput
|
||||
value={newRawValues}
|
||||
onChange={setNewRawValues}
|
||||
placeholder="e.g., utm campaign from the url (e.g., 2025q3_paid_social_linkedin)"
|
||||
fullWidth
|
||||
/>
|
||||
<div className="text-xs text-muted mt-1">
|
||||
Comma-separated list of utm_campaign values that should map to this campaign
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LemonButton
|
||||
type="primary"
|
||||
icon={<IconPlusSmall />}
|
||||
onClick={addMapping}
|
||||
disabled={!selectedSource || !newCleanName.trim() || !newRawValues.trim()}
|
||||
fullWidth
|
||||
>
|
||||
Add mapping
|
||||
</LemonButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { LemonCollapse } from '@posthog/lemon-ui'
|
||||
|
||||
import { BaseCurrency } from 'lib/components/BaseCurrency/BaseCurrency'
|
||||
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
|
||||
import { Scene } from 'scenes/sceneTypes'
|
||||
import { sceneConfigurations } from 'scenes/scenes'
|
||||
|
||||
@@ -7,6 +10,7 @@ import { SceneDivider } from '~/layout/scenes/components/SceneDivider'
|
||||
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
|
||||
|
||||
import { AttributionSettings } from './AttributionSettings'
|
||||
import { CampaignNameMappingsConfiguration } from './CampaignNameMappingsConfiguration'
|
||||
import { ConversionGoalsConfiguration } from './ConversionGoalsConfiguration'
|
||||
import { NativeExternalDataSourceConfiguration } from './NativeExternalDataSourceConfiguration'
|
||||
import { NonNativeExternalDataSourceConfiguration } from './NonNativeExternalDataSourceConfiguration'
|
||||
@@ -46,6 +50,18 @@ export function MarketingAnalyticsSettings({
|
||||
<NonNativeExternalDataSourceConfiguration />
|
||||
<SceneDivider />
|
||||
<SelfManagedExternalDataSourceConfiguration />
|
||||
<SceneDivider />
|
||||
<FlaggedFeature flag="advance-marketing-analytics-settings">
|
||||
<LemonCollapse
|
||||
panels={[
|
||||
{
|
||||
key: 'advanced-marketing-settings',
|
||||
header: 'Advanced marketing settings',
|
||||
content: <CampaignNameMappingsConfiguration />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</FlaggedFeature>
|
||||
</SceneContent>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const createEmptyConfig = (): MarketingAnalyticsConfig => ({
|
||||
conversion_goals: [],
|
||||
attribution_window_days: DEFAULT_ATTRIBUTION_WINDOW_DAYS,
|
||||
attribution_mode: AttributionMode.LastTouch,
|
||||
campaign_name_mappings: {},
|
||||
})
|
||||
|
||||
export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLogicType>([
|
||||
@@ -47,6 +48,9 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
|
||||
updateAttributionMode: (mode: AttributionMode) => ({
|
||||
mode,
|
||||
}),
|
||||
updateCampaignNameMappings: (campaignNameMappings: Record<string, Record<string, string[]>>) => ({
|
||||
campaignNameMappings,
|
||||
}),
|
||||
}),
|
||||
reducers(({ values }) => ({
|
||||
marketingAnalyticsConfig: [
|
||||
@@ -138,6 +142,12 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
|
||||
}
|
||||
return { ...state, attribution_mode: mode }
|
||||
},
|
||||
updateCampaignNameMappings: (state: MarketingAnalyticsConfig | null, { campaignNameMappings }) => {
|
||||
if (!state) {
|
||||
return { ...createEmptyConfig(), campaign_name_mappings: campaignNameMappings }
|
||||
}
|
||||
return { ...state, campaign_name_mappings: campaignNameMappings }
|
||||
},
|
||||
},
|
||||
],
|
||||
savedMarketingAnalyticsConfig: [
|
||||
@@ -188,6 +198,7 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
|
||||
removeConversionGoal: updateCurrentTeam,
|
||||
updateAttributionWindowWeeks: updateCurrentTeam,
|
||||
updateAttributionMode: updateCurrentTeam,
|
||||
updateCampaignNameMappings: updateCurrentTeam,
|
||||
}
|
||||
}),
|
||||
loaders(({ values }) => ({
|
||||
|
||||
@@ -211,10 +211,17 @@ class TeamMarketingAnalyticsConfigSerializer(serializers.ModelSerializer):
|
||||
attribution_mode = serializers.ChoiceField(
|
||||
choices=[(mode.value, mode.value.replace("_", " ").title()) for mode in AttributionMode], required=False
|
||||
)
|
||||
campaign_name_mappings = serializers.JSONField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = TeamMarketingAnalyticsConfig
|
||||
fields = ["sources_map", "conversion_goals", "attribution_window_days", "attribution_mode"]
|
||||
fields = [
|
||||
"sources_map",
|
||||
"conversion_goals",
|
||||
"attribution_window_days",
|
||||
"attribution_mode",
|
||||
"campaign_name_mappings",
|
||||
]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
# Handle sources_map with partial updates
|
||||
@@ -240,6 +247,9 @@ class TeamMarketingAnalyticsConfigSerializer(serializers.ModelSerializer):
|
||||
if "attribution_mode" in validated_data:
|
||||
instance.attribution_mode = validated_data["attribution_mode"]
|
||||
|
||||
if "campaign_name_mappings" in validated_data:
|
||||
instance.campaign_name_mappings = validated_data["campaign_name_mappings"]
|
||||
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
@@ -626,7 +626,8 @@
|
||||
"posthog_teammarketinganalyticsconfig"."attribution_window_days",
|
||||
"posthog_teammarketinganalyticsconfig"."attribution_mode",
|
||||
"posthog_teammarketinganalyticsconfig"."sources_map",
|
||||
"posthog_teammarketinganalyticsconfig"."conversion_goals"
|
||||
"posthog_teammarketinganalyticsconfig"."conversion_goals",
|
||||
"posthog_teammarketinganalyticsconfig"."campaign_name_mappings"
|
||||
FROM "posthog_teammarketinganalyticsconfig"
|
||||
WHERE "posthog_teammarketinganalyticsconfig"."team_id" = 99999
|
||||
LIMIT 21
|
||||
@@ -3258,7 +3259,8 @@
|
||||
"posthog_teammarketinganalyticsconfig"."attribution_window_days",
|
||||
"posthog_teammarketinganalyticsconfig"."attribution_mode",
|
||||
"posthog_teammarketinganalyticsconfig"."sources_map",
|
||||
"posthog_teammarketinganalyticsconfig"."conversion_goals"
|
||||
"posthog_teammarketinganalyticsconfig"."conversion_goals",
|
||||
"posthog_teammarketinganalyticsconfig"."campaign_name_mappings"
|
||||
FROM "posthog_teammarketinganalyticsconfig"
|
||||
WHERE "posthog_teammarketinganalyticsconfig"."team_id" = 99999
|
||||
LIMIT 21
|
||||
|
||||
@@ -138,6 +138,7 @@ class TestQueryRunner(BaseTest):
|
||||
"attribution_mode": "last_touch",
|
||||
"attribution_window_days": 90,
|
||||
"base_currency": "USD",
|
||||
"campaign_name_mappings": {},
|
||||
"sources_map": {
|
||||
"01977f7b-7f29-0000-a028-7275d1a767a4": {
|
||||
"cost": "cost",
|
||||
@@ -200,7 +201,7 @@ class TestQueryRunner(BaseTest):
|
||||
runner = TestQueryRunner(query={"some_attr": "bla"}, team=team)
|
||||
|
||||
cache_key = runner.get_cache_key()
|
||||
assert cache_key == "cache_335c3a8a6a92c0551e6b428e5bef5aee"
|
||||
assert cache_key == "cache_db05113c9937a0e206afcc48c43e0a87"
|
||||
|
||||
def test_cache_key_runner_subclass(self):
|
||||
TestQueryRunner = self.setup_test_query_runner_class()
|
||||
@@ -214,7 +215,7 @@ class TestQueryRunner(BaseTest):
|
||||
runner = TestSubclassQueryRunner(query={"some_attr": "bla"}, team=team)
|
||||
|
||||
cache_key = runner.get_cache_key()
|
||||
assert cache_key == "cache_a1ce3db3977f9e2c80431c2ce9679d26"
|
||||
assert cache_key == "cache_6369d6d904d6ce9dea56120059bdceba"
|
||||
|
||||
def test_cache_key_different_timezone(self):
|
||||
TestQueryRunner = self.setup_test_query_runner_class()
|
||||
@@ -225,7 +226,7 @@ class TestQueryRunner(BaseTest):
|
||||
runner = TestQueryRunner(query={"some_attr": "bla"}, team=team)
|
||||
|
||||
cache_key = runner.get_cache_key()
|
||||
assert cache_key == "cache_236279f500076c3545cc9cadf63f2838"
|
||||
assert cache_key == "cache_ee3b91f6f3800eba3e0420c65bbe082c"
|
||||
|
||||
@mock.patch("django.db.transaction.on_commit")
|
||||
def test_cache_response(self, mock_on_commit):
|
||||
|
||||
23
posthog/migrations/0890_add_campaign_name_mappings.py
Normal file
23
posthog/migrations/0890_add_campaign_name_mappings.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated migration
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("posthog", "0889_add_schema_models"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="teammarketinganalyticsconfig",
|
||||
name="_campaign_name_mappings",
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
db_column="campaign_name_mappings",
|
||||
default=dict,
|
||||
help_text="Maps campaign names to lists of raw UTM values per data source",
|
||||
null=False,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
0889_add_schema_models
|
||||
0890_add_campaign_name_mappings
|
||||
|
||||
@@ -56,6 +56,42 @@ def validate_attribution_mode(mode: str) -> None:
|
||||
raise ValidationError(f"attribution_mode must be one of {valid_modes}")
|
||||
|
||||
|
||||
def validate_campaign_name_mappings(mappings: dict) -> None:
|
||||
"""
|
||||
Validate campaign_name_mappings structure: dict of source_id -> dict of clean_name -> list of raw utm values.
|
||||
|
||||
Structure: {
|
||||
"GoogleAds": {
|
||||
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024"],
|
||||
"Black Friday": ["bf_2024", "blackfriday"]
|
||||
},
|
||||
"MetaAds": {...}
|
||||
}
|
||||
"""
|
||||
if not isinstance(mappings, dict):
|
||||
raise ValidationError("campaign_name_mappings must be a dictionary")
|
||||
|
||||
for source_id, campaign_mappings in mappings.items():
|
||||
if not isinstance(source_id, str):
|
||||
raise ValidationError(f"Source ID '{source_id}' must be a string")
|
||||
|
||||
if not isinstance(campaign_mappings, dict):
|
||||
raise ValidationError(f"Campaign mappings for source '{source_id}' must be a dictionary")
|
||||
|
||||
for clean_name, raw_values in campaign_mappings.items():
|
||||
if not isinstance(clean_name, str):
|
||||
raise ValidationError(f"Clean campaign name '{clean_name}' in source '{source_id}' must be a string")
|
||||
|
||||
if not isinstance(raw_values, list):
|
||||
raise ValidationError(f"Raw values for campaign '{clean_name}' in source '{source_id}' must be a list")
|
||||
|
||||
for raw_value in raw_values:
|
||||
if not isinstance(raw_value, str):
|
||||
raise ValidationError(
|
||||
f"Raw value '{raw_value}' for campaign '{clean_name}' in source '{source_id}' must be a string"
|
||||
)
|
||||
|
||||
|
||||
def validate_conversion_goals(conversion_goals: list) -> None:
|
||||
"""Validate conversion goals structure: list of dicts with name, event, and properties."""
|
||||
if not isinstance(conversion_goals, list):
|
||||
@@ -143,6 +179,13 @@ class TeamMarketingAnalyticsConfig(models.Model):
|
||||
# that are then wrapped by schema-validation getters/setters
|
||||
_sources_map = models.JSONField(default=dict, db_column="sources_map", null=False, blank=True)
|
||||
_conversion_goals = models.JSONField(default=list, db_column="conversion_goals", null=True, blank=True)
|
||||
_campaign_name_mappings = models.JSONField(
|
||||
default=dict,
|
||||
db_column="campaign_name_mappings",
|
||||
null=False,
|
||||
blank=True,
|
||||
help_text="Maps campaign names to lists of raw UTM values per data source",
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
"""Validate model fields"""
|
||||
@@ -192,6 +235,19 @@ class TeamMarketingAnalyticsConfig(models.Model):
|
||||
except ValidationError as e:
|
||||
raise ValidationError(f"Invalid conversion goals: {str(e)}")
|
||||
|
||||
@property
|
||||
def campaign_name_mappings(self) -> dict[str, dict[str, list[str]]]:
|
||||
return self._campaign_name_mappings or {}
|
||||
|
||||
@campaign_name_mappings.setter
|
||||
def campaign_name_mappings(self, value: dict) -> None:
|
||||
value = value or {}
|
||||
try:
|
||||
validate_campaign_name_mappings(value)
|
||||
self._campaign_name_mappings = value
|
||||
except ValidationError as e:
|
||||
raise ValidationError(f"Invalid campaign name mappings: {str(e)}")
|
||||
|
||||
def update_source_mapping(self, source_id: str, field_mapping: dict) -> None:
|
||||
"""Update or add a single source mapping while preserving existing sources."""
|
||||
|
||||
@@ -234,6 +290,7 @@ class TeamMarketingAnalyticsConfig(models.Model):
|
||||
"sources_map": self.sources_map,
|
||||
"attribution_window_days": self.attribution_window_days,
|
||||
"attribution_mode": self.attribution_mode,
|
||||
"campaign_name_mappings": self.campaign_name_mappings,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9471,6 +9471,7 @@ class MarketingAnalyticsConfig(BaseModel):
|
||||
)
|
||||
attribution_mode: Optional[AttributionMode] = None
|
||||
attribution_window_days: Optional[float] = None
|
||||
campaign_name_mappings: Optional[dict[str, dict[str, list[str]]]] = None
|
||||
conversion_goals: Optional[list[Union[ConversionGoalFilter1, ConversionGoalFilter2, ConversionGoalFilter3]]] = None
|
||||
sources_map: Optional[dict[str, SourceMap]] = None
|
||||
|
||||
|
||||
@@ -1,189 +1,510 @@
|
||||
# serializer version: 1
|
||||
# name: TestConversionGoalsAggregator.test_multiple_goals_sql_snapshot
|
||||
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_integration_with_events
|
||||
'''
|
||||
|
||||
SELECT
|
||||
campaign,
|
||||
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['spring_sale_2024', 'spring-sale-2024'])), 'Spring Sale 2024', and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['holiday_campaign', 'holiday_promo'])), 'Holiday Promo', campaign) AS campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0,
|
||||
sum(conversion_1) AS conversion_1,
|
||||
sum(conversion_2) AS conversion_2
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0,
|
||||
0 AS conversion_1,
|
||||
0 AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
count() AS conversion_1,
|
||||
0 AS conversion_2
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
0 AS conversion_1,
|
||||
count() AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'login'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_no_mappings
|
||||
'''
|
||||
|
||||
SELECT
|
||||
campaign AS campaign,
|
||||
source,
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_single_source_sql
|
||||
'''
|
||||
|
||||
SELECT
|
||||
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['messy_campaign_1', 'messy_campaign_2', 'messy_campaign_3'])), 'Clean Campaign Name', campaign) AS campaign,
|
||||
source,
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'conversion'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'conversion'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'conversion'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'conversion'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'conversion'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_sql_generation
|
||||
'''
|
||||
|
||||
SELECT
|
||||
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['spring_sale_2024', 'spring-sale-2024', 'spring_sale'])), 'Spring Sale 2024', and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['bf_2024', 'blackfriday', 'bf-promo'])), 'Black Friday', and(in(lower(source), ['meta', 'facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), in(lower(campaign), ['spring_sale_fb', 'springsalefb'])), 'Spring Sale 2024', and(in(lower(source), ['meta', 'facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), in(lower(campaign), ['summer_2024', 'summer_promo'])), 'Summer Campaign', campaign) AS campaign,
|
||||
source,
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestConversionGoalsAggregator.test_multiple_goals_sql_snapshot
|
||||
'''
|
||||
|
||||
SELECT
|
||||
campaign AS campaign,
|
||||
source,
|
||||
conversion_0,
|
||||
conversion_1,
|
||||
conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0,
|
||||
sum(conversion_1) AS conversion_1,
|
||||
sum(conversion_2) AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0,
|
||||
0 AS conversion_1,
|
||||
0 AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
count() AS conversion_1,
|
||||
0 AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
0 AS conversion_1,
|
||||
count() AS conversion_2
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'login'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
@@ -192,128 +513,136 @@
|
||||
'''
|
||||
|
||||
SELECT
|
||||
campaign,
|
||||
campaign AS campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0,
|
||||
sum(conversion_1) AS conversion_1
|
||||
conversion_0,
|
||||
conversion_1
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0,
|
||||
0 AS conversion_1
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
count() AS conversion_1
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0,
|
||||
sum(conversion_1) AS conversion_1
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0,
|
||||
0 AS conversion_1
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
0 AS conversion_0,
|
||||
count() AS conversion_1
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source
|
||||
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
|
||||
@@ -92,69 +92,76 @@
|
||||
source) AS campaign_costs
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
campaign,
|
||||
campaign AS campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-01-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-01-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
|
||||
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-01-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-01-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))
|
||||
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))
|
||||
ORDER BY
|
||||
Cost DESC
|
||||
|
||||
|
||||
@@ -106,69 +106,76 @@
|
||||
source) AS campaign_costs
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
campaign,
|
||||
campaign AS campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-11-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-11-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
|
||||
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-11-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-11-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS current_period
|
||||
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS current_period
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
campaign_costs.campaign AS Campaign,
|
||||
@@ -213,69 +220,76 @@
|
||||
source) AS campaign_costs
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
campaign,
|
||||
campaign AS campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
campaign,
|
||||
source,
|
||||
sum(conversion_0) AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
|
||||
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
|
||||
count() AS conversion_0
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
|
||||
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
|
||||
1 AS conversion_value
|
||||
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
person_id,
|
||||
conversion_timestamps[i] AS conversion_time,
|
||||
conversion_math_values[i] AS conversion_math_value,
|
||||
conversion_campaigns[i] AS conversion_campaign,
|
||||
conversion_sources[i] AS conversion_source,
|
||||
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
|
||||
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
|
||||
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
(
|
||||
SELECT
|
||||
events.person_id,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
|
||||
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
|
||||
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
|
||||
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
|
||||
|
||||
FROM
|
||||
events
|
||||
|
||||
WHERE
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-09-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-09-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))))
|
||||
|
||||
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-09-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-09-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))))
|
||||
|
||||
GROUP BY
|
||||
events.person_id
|
||||
|
||||
events.person_id
|
||||
|
||||
HAVING
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
greater(length(conversion_timestamps), 0))
|
||||
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS conv
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source)
|
||||
|
||||
GROUP BY
|
||||
campaign,
|
||||
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS previous_period ON and(equals(current_period.Campaign, previous_period.Campaign), equals(current_period.Source, previous_period.Source))
|
||||
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS previous_period ON and(equals(current_period.Campaign, previous_period.Campaign), equals(current_period.Source, previous_period.Source))
|
||||
ORDER BY
|
||||
Cost DESC
|
||||
|
||||
|
||||
@@ -879,12 +879,6 @@ class ConversionGoalProcessor:
|
||||
group_by=[ast.Field(chain=[field]) for field in self.config.group_by_fields],
|
||||
)
|
||||
|
||||
def generate_cte_query_expr(self, additional_conditions: list[ast.Expr]) -> ast.Expr:
|
||||
"""Generate CTE query expression"""
|
||||
cte_name = self.get_cte_name()
|
||||
select_query = self.generate_cte_query(additional_conditions)
|
||||
return ast.Alias(alias=cte_name, expr=select_query)
|
||||
|
||||
def generate_join_clause(self, use_full_outer_join: bool = False) -> ast.JoinExpr:
|
||||
"""Generate JOIN clause for this conversion goal"""
|
||||
cte_name = self.get_cte_name()
|
||||
|
||||
@@ -4,6 +4,7 @@ from posthog.hogql_queries.utils.query_date_range import QueryDateRange
|
||||
|
||||
from products.marketing_analytics.backend.hogql_queries.constants import UNIFIED_CONVERSION_GOALS_CTE_ALIAS
|
||||
|
||||
from .adapters.factory import MarketingSourceFactory
|
||||
from .conversion_goal_processor import ConversionGoalProcessor
|
||||
from .marketing_analytics_config import MarketingAnalyticsConfig
|
||||
|
||||
@@ -88,7 +89,13 @@ class ConversionGoalsAggregator:
|
||||
union_query = ast.SelectSetQuery.create_from_queries(conversion_subqueries, "UNION ALL")
|
||||
|
||||
# Step 3: Create final aggregation query that sums all conversion goals by campaign/source
|
||||
final_select: list[ast.Expr] = [ast.Field(chain=[field]) for field in self.config.group_by_fields]
|
||||
# First, wrap the union in a subquery to materialize campaign/source fields
|
||||
subquery_alias = "conv"
|
||||
|
||||
final_select: list[ast.Expr] = [
|
||||
ast.Field(chain=[self.config.campaign_field]),
|
||||
ast.Field(chain=[self.config.source_field]),
|
||||
]
|
||||
|
||||
# Add each conversion goal as a summed column
|
||||
for processor in self.processors:
|
||||
@@ -104,11 +111,108 @@ class ConversionGoalsAggregator:
|
||||
|
||||
final_query = ast.SelectQuery(
|
||||
select=final_select,
|
||||
select_from=ast.JoinExpr(table=union_query),
|
||||
group_by=[ast.Field(chain=[field]) for field in self.config.group_by_fields],
|
||||
select_from=ast.JoinExpr(table=union_query, alias=subquery_alias),
|
||||
group_by=[
|
||||
ast.Field(chain=[self.config.campaign_field]),
|
||||
ast.Field(chain=[self.config.source_field]),
|
||||
],
|
||||
)
|
||||
|
||||
return ast.CTE(name=UNIFIED_CONVERSION_GOALS_CTE_ALIAS, expr=final_query, cte_type="subquery")
|
||||
# Now apply campaign name mappings by wrapping in another SELECT
|
||||
campaign_field_expr = ast.Field(chain=[self.config.campaign_field])
|
||||
source_field_expr = ast.Field(chain=[self.config.source_field])
|
||||
mapped_campaign_expr = self._apply_campaign_name_mappings(campaign_field_expr, source_field_expr)
|
||||
|
||||
outer_select: list[ast.Expr] = [
|
||||
ast.Alias(alias=self.config.campaign_field, expr=mapped_campaign_expr),
|
||||
ast.Field(chain=[self.config.source_field]),
|
||||
]
|
||||
|
||||
# Add conversion goal columns
|
||||
for processor in self.processors:
|
||||
outer_select.append(ast.Field(chain=[self.config.get_conversion_goal_column_name(processor.index)]))
|
||||
|
||||
wrapped_query = ast.SelectQuery(
|
||||
select=outer_select,
|
||||
select_from=ast.JoinExpr(table=final_query),
|
||||
)
|
||||
|
||||
return ast.CTE(name=UNIFIED_CONVERSION_GOALS_CTE_ALIAS, expr=wrapped_query, cte_type="subquery")
|
||||
|
||||
def _apply_campaign_name_mappings(self, campaign_expr: ast.Expr, source_expr: ast.Expr) -> ast.Expr:
|
||||
"""Apply campaign name mappings from team config"""
|
||||
# Get team from first processor (all processors have the same team)
|
||||
if not self.processors or not self.processors[0].team:
|
||||
return campaign_expr
|
||||
|
||||
team = self.processors[0].team
|
||||
|
||||
try:
|
||||
campaign_mappings = team.marketing_analytics_config.campaign_name_mappings
|
||||
except Exception:
|
||||
return campaign_expr
|
||||
|
||||
if not campaign_mappings:
|
||||
return campaign_expr
|
||||
|
||||
conditions_and_results: list[ast.Expr] = []
|
||||
lowercase_campaign = ast.Call(name="lower", args=[campaign_expr])
|
||||
lowercase_source = ast.Call(name="lower", args=[source_expr])
|
||||
|
||||
for external_source, source_mappings in campaign_mappings.items():
|
||||
if not source_mappings:
|
||||
continue
|
||||
|
||||
# Get utm_source values for this adapter
|
||||
adapter_class = MarketingSourceFactory._adapter_registry.get(external_source)
|
||||
if not adapter_class:
|
||||
continue
|
||||
|
||||
source_mapping = adapter_class.get_source_identifier_mapping()
|
||||
utm_sources = []
|
||||
for alternatives in source_mapping.values():
|
||||
utm_sources.extend(alternatives)
|
||||
|
||||
if not utm_sources:
|
||||
continue
|
||||
|
||||
# Build source condition once for this adapter
|
||||
source_condition = ast.Call(
|
||||
name="in",
|
||||
args=[
|
||||
lowercase_source,
|
||||
ast.Array(exprs=[ast.Constant(value=s.lower()) for s in utm_sources]),
|
||||
],
|
||||
)
|
||||
|
||||
# Add condition/result pairs for each campaign mapping
|
||||
for clean_name, raw_values in source_mappings.items():
|
||||
if not raw_values:
|
||||
continue
|
||||
|
||||
campaign_condition = ast.Call(
|
||||
name="in",
|
||||
args=[
|
||||
lowercase_campaign,
|
||||
ast.Array(exprs=[ast.Constant(value=val.lower()) for val in raw_values]),
|
||||
],
|
||||
)
|
||||
|
||||
# Combine source and campaign conditions
|
||||
combined_condition = ast.Call(name="and", args=[source_condition, campaign_condition])
|
||||
|
||||
conditions_and_results.append(combined_condition)
|
||||
conditions_and_results.append(ast.Constant(value=clean_name))
|
||||
|
||||
# If no mappings were added, return original campaign
|
||||
if not conditions_and_results:
|
||||
return campaign_expr
|
||||
|
||||
# Add default case (original campaign)
|
||||
conditions_and_results.append(campaign_expr)
|
||||
|
||||
# Build multiIf with all conditions
|
||||
return ast.Call(name="multiIf", args=conditions_and_results)
|
||||
|
||||
def get_conversion_goal_columns(self) -> dict[str, ast.Alias]:
|
||||
"""Get the column mappings for accessing conversion goals from the unified CTE"""
|
||||
|
||||
@@ -125,12 +125,10 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
final_query = cte.expr
|
||||
assert isinstance(final_query, ast.SelectQuery)
|
||||
assert len(final_query.select) == 3
|
||||
assert final_query.group_by is not None
|
||||
assert len(final_query.group_by) == 2
|
||||
|
||||
conversion_column = final_query.select[2]
|
||||
assert isinstance(conversion_column, ast.Alias)
|
||||
assert conversion_column.alias == self.config.get_conversion_goal_column_name(0)
|
||||
assert isinstance(conversion_column, ast.Field)
|
||||
assert conversion_column.chain == [self.config.get_conversion_goal_column_name(0)]
|
||||
|
||||
def test_unified_cte_multiple_processors(self):
|
||||
goal1 = self._create_test_conversion_goal("multi_goal1", "Goal 1")
|
||||
@@ -150,17 +148,13 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
final_query = cte.expr
|
||||
assert isinstance(final_query, ast.SelectQuery)
|
||||
assert len(final_query.select) == 5
|
||||
assert final_query.group_by is not None
|
||||
assert len(final_query.group_by) == 2
|
||||
|
||||
conversion_columns = final_query.select[2:]
|
||||
assert len(conversion_columns) == 3
|
||||
|
||||
for i, column in enumerate(conversion_columns):
|
||||
assert isinstance(column, ast.Alias)
|
||||
assert column.alias == self.config.get_conversion_goal_column_name(i)
|
||||
assert isinstance(column.expr, ast.Call)
|
||||
assert column.expr.name == "sum"
|
||||
assert isinstance(column, ast.Field)
|
||||
assert column.chain == [self.config.get_conversion_goal_column_name(i)]
|
||||
|
||||
@pytest.mark.usefixtures("unittest_snapshot")
|
||||
def test_unified_cte_sql_snapshot(self):
|
||||
@@ -193,10 +187,8 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
final_query = cte.expr
|
||||
assert isinstance(final_query, ast.SelectQuery)
|
||||
assert len(final_query.select) == 4
|
||||
assert final_query.group_by is not None
|
||||
assert len(final_query.group_by) == 2
|
||||
assert isinstance(final_query.select_from, ast.JoinExpr)
|
||||
assert isinstance(final_query.select_from.table, ast.SelectSetQuery)
|
||||
assert isinstance(final_query.select_from.table, ast.SelectQuery)
|
||||
|
||||
def test_conversion_goal_columns_single(self):
|
||||
goal = self._create_test_conversion_goal("columns_test", "Columns Test")
|
||||
@@ -373,8 +365,6 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
final_query = cte.expr
|
||||
assert isinstance(final_query, ast.SelectQuery)
|
||||
assert len(final_query.select) == 5
|
||||
assert final_query.group_by is not None
|
||||
assert len(final_query.group_by) == 2
|
||||
|
||||
conversion_columns = final_query.select[2:]
|
||||
expected_names = [
|
||||
@@ -382,7 +372,7 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
self.config.get_conversion_goal_column_name(1),
|
||||
self.config.get_conversion_goal_column_name(2),
|
||||
]
|
||||
actual_names = [col.alias for col in conversion_columns if isinstance(col, ast.Alias)]
|
||||
actual_names = [col.chain[0] for col in conversion_columns if isinstance(col, ast.Field)]
|
||||
assert actual_names == expected_names
|
||||
|
||||
def test_empty_goal_name(self):
|
||||
@@ -413,8 +403,8 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
final_query = cte.expr
|
||||
assert isinstance(final_query, ast.SelectQuery)
|
||||
conversion_column = final_query.select[2]
|
||||
assert isinstance(conversion_column, ast.Alias)
|
||||
assert conversion_column.alias == self.config.get_conversion_goal_column_name(999)
|
||||
assert isinstance(conversion_column, ast.Field)
|
||||
assert conversion_column.chain == [self.config.get_conversion_goal_column_name(999)]
|
||||
|
||||
def test_duplicate_goal_names(self):
|
||||
goal1 = self._create_test_conversion_goal("dup1", "Duplicate Goal")
|
||||
@@ -459,3 +449,105 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
|
||||
|
||||
assert isinstance(cte, ast.CTE)
|
||||
assert cte.name == "ucg"
|
||||
|
||||
@pytest.mark.usefixtures("unittest_snapshot")
|
||||
def test_campaign_name_mapping_sql_generation(self):
|
||||
"""Test that campaign name mappings generate correct SQL with nested CASE statements"""
|
||||
# Configure campaign name mappings for multiple sources
|
||||
self.team.marketing_analytics_config.campaign_name_mappings = {
|
||||
"GoogleAds": {
|
||||
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024", "SPRING_SALE"],
|
||||
"Black Friday": ["bf_2024", "blackfriday", "BF-PROMO"],
|
||||
},
|
||||
"MetaAds": {
|
||||
"Spring Sale 2024": ["spring_sale_fb", "SpringSaleFB"],
|
||||
"Summer Campaign": ["summer_2024", "SUMMER_PROMO"],
|
||||
},
|
||||
}
|
||||
self.team.marketing_analytics_config.save()
|
||||
|
||||
goal = self._create_test_conversion_goal("mapping_sql_test", "Mapping SQL Test", "purchase")
|
||||
processor = self._create_test_processor(goal, 0)
|
||||
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
|
||||
|
||||
additional_conditions_getter = self._create_mock_additional_conditions_getter()
|
||||
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
|
||||
|
||||
# Execute to get the SQL string
|
||||
response = execute_hogql_query(query=cte.expr, team=self.team)
|
||||
|
||||
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
|
||||
|
||||
@pytest.mark.usefixtures("unittest_snapshot")
|
||||
def test_campaign_name_mapping_single_source_sql(self):
|
||||
"""Test campaign name mapping for a single data source"""
|
||||
self.team.marketing_analytics_config.campaign_name_mappings = {
|
||||
"GoogleAds": {
|
||||
"Clean Campaign Name": ["messy_campaign_1", "messy_campaign_2", "MESSY_CAMPAIGN_3"],
|
||||
}
|
||||
}
|
||||
self.team.marketing_analytics_config.save()
|
||||
|
||||
goal = self._create_test_conversion_goal("single_source_mapping", "Single Source Mapping", "conversion")
|
||||
processor = self._create_test_processor(goal, 0)
|
||||
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
|
||||
|
||||
additional_conditions_getter = self._create_mock_additional_conditions_getter()
|
||||
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
|
||||
|
||||
response = execute_hogql_query(query=cte.expr, team=self.team)
|
||||
|
||||
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
|
||||
|
||||
@pytest.mark.usefixtures("unittest_snapshot")
|
||||
def test_campaign_name_mapping_no_mappings(self):
|
||||
"""Test that when no mappings are configured, the query uses original campaign names"""
|
||||
self.team.marketing_analytics_config.campaign_name_mappings = {}
|
||||
self.team.marketing_analytics_config.save()
|
||||
|
||||
goal = self._create_test_conversion_goal("no_mapping_test", "No Mapping Test", "purchase")
|
||||
processor = self._create_test_processor(goal, 0)
|
||||
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
|
||||
|
||||
additional_conditions_getter = self._create_mock_additional_conditions_getter()
|
||||
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
|
||||
|
||||
response = execute_hogql_query(query=cte.expr, team=self.team)
|
||||
|
||||
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
|
||||
|
||||
@pytest.mark.usefixtures("unittest_snapshot")
|
||||
def test_campaign_name_mapping_integration_with_events(self):
|
||||
"""Test campaign name mapping SQL generation with actual events to verify mapping works end-to-end"""
|
||||
# Configure campaign name mappings for GoogleAds
|
||||
self.team.marketing_analytics_config.campaign_name_mappings = {
|
||||
"GoogleAds": {
|
||||
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024"],
|
||||
"Holiday Promo": ["holiday_campaign", "holiday_promo"],
|
||||
}
|
||||
}
|
||||
self.team.marketing_analytics_config.save()
|
||||
|
||||
# Create conversion goal
|
||||
goal = self._create_test_conversion_goal("integration_test", "Integration Test", "purchase")
|
||||
processor = self._create_test_processor(goal, 0)
|
||||
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
|
||||
|
||||
additional_conditions_getter = self._create_mock_additional_conditions_getter()
|
||||
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
|
||||
|
||||
# Execute the query to get the SQL (but don't worry about results due to memory limits)
|
||||
response = execute_hogql_query(query=cte.expr, team=self.team)
|
||||
|
||||
# Verify the SQL contains campaign name mapping logic
|
||||
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
|
||||
|
||||
# Verify that the SQL contains the expected campaign name mapping logic
|
||||
sql_string = pretty_print_in_tests(response.hogql, self.team.pk)
|
||||
|
||||
# Check that the SQL contains multiIf with campaign name mapping
|
||||
assert "multiIf" in sql_string, "SQL should contain multiIf for campaign name mapping"
|
||||
assert "Spring Sale 2024" in sql_string, "SQL should contain mapped campaign name"
|
||||
assert "Holiday Promo" in sql_string, "SQL should contain mapped campaign name"
|
||||
assert "spring_sale_2024" in sql_string, "SQL should contain original campaign name in mapping"
|
||||
assert "holiday_campaign" in sql_string, "SQL should contain original campaign name in mapping"
|
||||
|
||||
Reference in New Issue
Block a user