Skip to content

Commit 85aaf16

Browse files
committed
merge
2 parents 3c879db + 8c18071 commit 85aaf16

File tree

10 files changed

+285
-14
lines changed

10 files changed

+285
-14
lines changed

apps/api/src/query/builders/pages.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
1818
orderBy: 'pageviews DESC',
1919
limit: 100,
2020
timeField: 'time',
21+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
2122
customizable: true,
2223
meta: {
2324
title: 'Top Pages',
@@ -67,6 +68,8 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
6768
},
6869

6970
entry_pages: {
71+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
72+
customizable: true,
7073
customSql: (
7174
websiteId: string,
7275
startDate: string,
@@ -121,6 +124,8 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
121124
},
122125

123126
exit_pages: {
127+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
128+
customizable: true,
124129
customSql: (
125130
websiteId: string,
126131
startDate: string,
@@ -199,6 +204,7 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
199204
orderBy: 'pageviews DESC',
200205
limit: 100,
201206
timeField: 'time',
207+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
202208
customizable: true,
203209
},
204210
};

apps/api/src/query/builders/traffic.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const TrafficBuilders: Record<string, SimpleQueryConfig> = {
6565
orderBy: 'pageviews DESC',
6666
limit: 100,
6767
timeField: 'time',
68+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
6869
customizable: true,
6970
plugins: { parseReferrers: true },
7071
},
@@ -119,6 +120,7 @@ export const TrafficBuilders: Record<string, SimpleQueryConfig> = {
119120
orderBy: 'pageviews DESC',
120121
limit: 100,
121122
timeField: 'time',
123+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
122124
customizable: true,
123125
},
124126

@@ -135,6 +137,7 @@ export const TrafficBuilders: Record<string, SimpleQueryConfig> = {
135137
orderBy: 'pageviews DESC',
136138
limit: 100,
137139
timeField: 'time',
140+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
138141
customizable: true,
139142
},
140143

@@ -188,6 +191,7 @@ export const TrafficBuilders: Record<string, SimpleQueryConfig> = {
188191
orderBy: 'pageviews DESC',
189192
limit: 100,
190193
timeField: 'time',
194+
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
191195
customizable: true,
192196
},
193197

apps/api/src/query/simple-builder.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,67 @@ export class SimpleQueryBuilder {
3333
const key = `f${index}`;
3434
const operator = FilterOperators[filter.op];
3535

36+
// Special handling for path filters - apply same normalization as used in queries
37+
if (filter.field === 'path') {
38+
const normalizedPathExpression = "CASE WHEN trimRight(path(path), '/') = '' THEN '/' ELSE trimRight(path(path), '/') END";
39+
40+
if (filter.op === 'like') {
41+
return {
42+
clause: `${normalizedPathExpression} ${operator} {${key}:String}`,
43+
params: { [key]: `%${filter.value}%` },
44+
};
45+
}
46+
47+
if (filter.op === 'in' || filter.op === 'notIn') {
48+
const values = Array.isArray(filter.value)
49+
? filter.value
50+
: [filter.value];
51+
return {
52+
clause: `${normalizedPathExpression} ${operator} {${key}:Array(String)}`,
53+
params: { [key]: values },
54+
};
55+
}
56+
57+
return {
58+
clause: `${normalizedPathExpression} ${operator} {${key}:String}`,
59+
params: { [key]: filter.value },
60+
};
61+
}
62+
63+
// Special handling for referrer filters - apply same normalization as used in queries
64+
if (filter.field === 'referrer') {
65+
const normalizedReferrerExpression =
66+
'CASE ' +
67+
"WHEN domain(referrer) LIKE '%.google.com%' OR domain(referrer) LIKE 'google.com%' THEN 'https://google.com' " +
68+
"WHEN domain(referrer) LIKE '%.facebook.com%' OR domain(referrer) LIKE 'facebook.com%' THEN 'https://facebook.com' " +
69+
"WHEN domain(referrer) LIKE '%.twitter.com%' OR domain(referrer) LIKE 'twitter.com%' OR domain(referrer) LIKE 't.co%' THEN 'https://twitter.com' " +
70+
"WHEN domain(referrer) LIKE '%.instagram.com%' OR domain(referrer) LIKE 'instagram.com%' OR domain(referrer) LIKE 'l.instagram.com%' THEN 'https://instagram.com' " +
71+
"ELSE concat('https://', domain(referrer)) " +
72+
'END';
73+
74+
if (filter.op === 'like') {
75+
return {
76+
clause: `${normalizedReferrerExpression} ${operator} {${key}:String}`,
77+
params: { [key]: `%${filter.value}%` },
78+
};
79+
}
80+
81+
if (filter.op === 'in' || filter.op === 'notIn') {
82+
const values = Array.isArray(filter.value)
83+
? filter.value
84+
: [filter.value];
85+
return {
86+
clause: `${normalizedReferrerExpression} ${operator} {${key}:Array(String)}`,
87+
params: { [key]: values },
88+
};
89+
}
90+
91+
return {
92+
clause: `${normalizedReferrerExpression} ${operator} {${key}:String}`,
93+
params: { [key]: filter.value },
94+
};
95+
}
96+
3697
if (filter.op === 'like') {
3798
return {
3899
clause: `${filter.field} ${operator} {${key}:String}`,

apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { MetricsChart } from '@/components/charts/metrics-chart';
3434
import { useBatchDynamicQuery } from '@/hooks/use-dynamic-query';
3535
import { useTableTabs } from '@/lib/table-tabs';
3636
import { getUserTimezone } from '@/lib/timezone';
37+
3738
import {
3839
metricVisibilityAtom,
3940
toggleMetricAtom,
@@ -143,6 +144,7 @@ export function WebsiteOverviewTab({
143144
isRefreshing,
144145
setIsRefreshing,
145146
filters,
147+
addFilter,
146148
}: FullTabProps) {
147149
const calculatePreviousPeriod = useCallback(
148150
(currentRange: typeof dateRange) => {
@@ -313,24 +315,43 @@ export function WebsiteOverviewTab({
313315
primaryField: 'name',
314316
primaryHeader: 'Source',
315317
customCell: referrerCustomCell,
318+
getFilter: (row: any) => {
319+
// For referrers, use the referrer field and get the actual referrer URL
320+
return {
321+
field: 'referrer',
322+
value: row.referrer
323+
};
324+
},
316325
},
317326
utm_sources: {
318327
data: analytics.utm_sources || [],
319328
label: 'UTM Sources',
320329
primaryField: 'name',
321330
primaryHeader: 'Source',
331+
getFilter: (row: any) => ({
332+
field: 'utm_source',
333+
value: row.name
334+
}),
322335
},
323336
utm_mediums: {
324337
data: analytics.utm_mediums || [],
325338
label: 'UTM Mediums',
326339
primaryField: 'name',
327340
primaryHeader: 'Medium',
341+
getFilter: (row: any) => ({
342+
field: 'utm_medium',
343+
value: row.name
344+
}),
328345
},
329346
utm_campaigns: {
330347
data: analytics.utm_campaigns || [],
331348
label: 'UTM Campaigns',
332349
primaryField: 'name',
333350
primaryHeader: 'Campaign',
351+
getFilter: (row: any) => ({
352+
field: 'utm_campaign',
353+
value: row.name
354+
}),
334355
},
335356
});
336357

@@ -343,6 +364,10 @@ export function WebsiteOverviewTab({
343364
label: 'Top Pages',
344365
primaryField: 'name',
345366
primaryHeader: 'Page',
367+
getFilter: (row: any) => ({
368+
field: 'path',
369+
value: row.name
370+
}),
346371
},
347372
entry_pages: {
348373
data: (analytics.entry_pages || []).map((page: PageData) => ({
@@ -352,6 +377,10 @@ export function WebsiteOverviewTab({
352377
label: 'Entry Pages',
353378
primaryField: 'name',
354379
primaryHeader: 'Page',
380+
getFilter: (row: any) => ({
381+
field: 'path',
382+
value: row.name
383+
}),
355384
},
356385
exit_pages: {
357386
data: (analytics.exit_pages || []).map((page: PageData) => ({
@@ -361,6 +390,10 @@ export function WebsiteOverviewTab({
361390
label: 'Exit Pages',
362391
primaryField: 'name',
363392
primaryHeader: 'Page',
393+
getFilter: (row: any) => ({
394+
field: 'path',
395+
value: row.name
396+
}),
364397
},
365398
});
366399

@@ -875,6 +908,17 @@ export function WebsiteOverviewTab({
875908
return <UnauthorizedAccessError />;
876909
}
877910

911+
const onAddFilter = useCallback((field: string, value: string, tableTitle?: string) => {
912+
// The field parameter now contains the correct filter field from the tab configuration
913+
const filter = {
914+
field,
915+
operator: 'eq' as const,
916+
value
917+
};
918+
919+
addFilter(filter);
920+
}, [addFilter]);
921+
878922
return (
879923
<div className="space-y-6">
880924
<EventLimitIndicator />
@@ -1059,6 +1103,7 @@ export function WebsiteOverviewTab({
10591103
description="Referrers and campaign data"
10601104
isLoading={isLoading}
10611105
minHeight={350}
1106+
onAddFilter={onAddFilter}
10621107
tabs={referrerTabs}
10631108
title="Traffic Sources"
10641109
/>
@@ -1067,25 +1112,27 @@ export function WebsiteOverviewTab({
10671112
description="Top pages and entry/exit points"
10681113
isLoading={isLoading}
10691114
minHeight={350}
1115+
onAddFilter={onAddFilter}
10701116
tabs={pagesTabs}
10711117
title="Pages"
10721118
/>
10731119
</div>
10741120

10751121
{/* Custom Events Table */}
1076-
<DataTable
1077-
columns={customEventsColumns}
1078-
data={processedCustomEventsData}
1079-
description="User-defined events and interactions with property breakdowns"
1080-
emptyMessage="No custom events tracked yet"
1081-
expandable={true}
1082-
getSubRows={(row: CustomEventData) =>
1083-
row.propertyCategories as unknown as CustomEventData[]
1084-
}
1085-
initialPageSize={8}
1086-
isLoading={isLoading}
1087-
minHeight={350}
1088-
renderSubRow={(subRow: CustomEventData, parentRow: CustomEventData) => {
1122+
<DataTable
1123+
columns={customEventsColumns}
1124+
data={processedCustomEventsData}
1125+
description="User-defined events and interactions with property breakdowns"
1126+
emptyMessage="No custom events tracked yet"
1127+
expandable={true}
1128+
getSubRows={(row: CustomEventData) =>
1129+
row.propertyCategories as unknown as CustomEventData[]
1130+
}
1131+
initialPageSize={8}
1132+
isLoading={isLoading}
1133+
minHeight={350}
1134+
onAddFilter={onAddFilter}
1135+
renderSubRow={(subRow: CustomEventData, parentRow: CustomEventData) => {
10891136
const typedSubRow = subRow as unknown as PropertyCategory;
10901137
const propertyKey = typedSubRow.key;
10911138
const propertyTotal = typedSubRow.total;
@@ -1171,8 +1218,29 @@ export function WebsiteOverviewTab({
11711218
initialPageSize={8}
11721219
isLoading={isLoading}
11731220
minHeight={350}
1221+
onAddFilter={onAddFilter}
11741222
showSearch={false}
11751223
title="Devices"
1224+
tabs={[
1225+
{
1226+
id: 'devices',
1227+
label: 'Devices',
1228+
data: processedDeviceData,
1229+
columns: deviceColumns,
1230+
getFilter: (row: any) => {
1231+
// Map display device names to filter values
1232+
const deviceDisplayToFilterMap: Record<string, string> = {
1233+
'laptop': 'mobile',
1234+
'tablet': 'tablet',
1235+
'desktop': 'desktop',
1236+
};
1237+
return {
1238+
field: 'device_type',
1239+
value: deviceDisplayToFilterMap[row.name] || row.name
1240+
};
1241+
}
1242+
}
1243+
]}
11761244
/>
11771245

11781246
<DataTable
@@ -1182,8 +1250,21 @@ export function WebsiteOverviewTab({
11821250
initialPageSize={8}
11831251
isLoading={isLoading}
11841252
minHeight={350}
1253+
onAddFilter={onAddFilter}
11851254
showSearch={false}
11861255
title="Browsers"
1256+
tabs={[
1257+
{
1258+
id: 'browsers',
1259+
label: 'Browsers',
1260+
data: processedBrowserData,
1261+
columns: browserColumns,
1262+
getFilter: (row: any) => ({
1263+
field: 'browser_name',
1264+
value: row.name
1265+
}),
1266+
}
1267+
]}
11871268
/>
11881269

11891270
<DataTable
@@ -1193,8 +1274,21 @@ export function WebsiteOverviewTab({
11931274
initialPageSize={8}
11941275
isLoading={isLoading}
11951276
minHeight={350}
1277+
onAddFilter={onAddFilter}
11961278
showSearch={false}
11971279
title="Operating Systems"
1280+
tabs={[
1281+
{
1282+
id: 'operating_systems',
1283+
label: 'Operating Systems',
1284+
data: processedOSData,
1285+
columns: osColumns,
1286+
getFilter: (row: any) => ({
1287+
field: 'os_name',
1288+
value: row.name
1289+
}),
1290+
}
1291+
]}
11981292
/>
11991293
</div>
12001294
</div>

apps/dashboard/app/(main)/websites/[id]/_components/utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface FullTabProps extends BaseTabProps {
3333
isRefreshing: boolean;
3434
setIsRefreshing: (value: boolean) => void;
3535
filters: DynamicQueryFilter[];
36+
addFilter: (filter: DynamicQueryFilter) => void;
3637
}
3738

3839
// Common analytics data types

0 commit comments

Comments
 (0)