Skip to content

Commit 949003d

Browse files
committed
nits
1 parent 691906b commit 949003d

File tree

3 files changed

+150
-127
lines changed

3 files changed

+150
-127
lines changed

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

Lines changed: 93 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
import type { Filter, SimpleQueryConfig, TimeUnit } from "../types";
22

33
export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
4-
profile_list: {
5-
customSql: (
6-
websiteId: string,
7-
startDate: string,
8-
endDate: string,
9-
_filters?: Filter[],
10-
_granularity?: TimeUnit,
11-
limit?: number,
12-
offset?: number,
13-
_timezone?: string,
14-
filterConditions?: string[],
15-
filterParams?: Record<string, Filter["value"]>
16-
) => {
17-
const combinedWhereClause = filterConditions?.length
18-
? `AND ${filterConditions.join(" AND ")}`
19-
: "";
4+
profile_list: {
5+
customSql: (
6+
websiteId: string,
7+
startDate: string,
8+
endDate: string,
9+
_filters?: Filter[],
10+
_granularity?: TimeUnit,
11+
limit?: number,
12+
offset?: number,
13+
_timezone?: string,
14+
filterConditions?: string[],
15+
filterParams?: Record<string, Filter["value"]>
16+
) => {
17+
const combinedWhereClause = filterConditions?.length
18+
? `AND ${filterConditions.join(" AND ")}`
19+
: "";
2020

21-
return {
22-
sql: `
21+
return {
22+
sql: `
2323
WITH visitor_profiles AS (
2424
SELECT
2525
anonymous_id as visitor_id,
2626
MIN(time) as first_visit,
2727
MAX(time) as last_visit,
2828
COUNT(DISTINCT session_id) as session_count,
2929
COUNT(*) as total_events,
30-
COUNT(DISTINCT path) as unique_pages,
30+
COUNT(DISTINCT CASE WHEN event_name = 'screen_view' THEN path ELSE NULL END) as unique_pages,
3131
any(user_agent) as user_agent,
3232
any(country) as country,
3333
any(region) as region,
@@ -53,7 +53,7 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
5353
MAX(e.time) as session_end,
5454
LEAST(dateDiff('second', MIN(e.time), MAX(e.time)), 28800) as duration,
5555
COUNT(*) as page_views,
56-
COUNT(DISTINCT e.path) as unique_pages,
56+
COUNT(DISTINCT CASE WHEN e.event_name = 'screen_view' THEN e.path ELSE NULL END) as unique_pages,
5757
any(e.user_agent) as user_agent,
5858
any(e.country) as country,
5959
any(e.region) as region,
@@ -115,41 +115,41 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
115115
LEFT JOIN visitor_sessions vs ON vp.visitor_id = vs.visitor_id
116116
ORDER BY vp.last_visit DESC, vs.session_start DESC
117117
`,
118-
params: {
119-
websiteId,
120-
startDate,
121-
endDate: `${endDate} 23:59:59`,
122-
limit: limit || 25,
123-
offset: offset || 0,
124-
...filterParams,
125-
},
126-
};
127-
},
128-
},
118+
params: {
119+
websiteId,
120+
startDate,
121+
endDate: `${endDate} 23:59:59`,
122+
limit: limit || 25,
123+
offset: offset || 0,
124+
...filterParams,
125+
},
126+
};
127+
},
128+
},
129129

130-
profile_detail: {
131-
customSql: (
132-
websiteId: string,
133-
startDate: string,
134-
endDate: string,
135-
filters?: Filter[],
136-
_granularity?: TimeUnit,
137-
_limit?: number,
138-
_offset?: number,
139-
_timezone?: string,
140-
_filterConditions?: string[],
141-
_filterParams?: Record<string, Filter["value"]>
142-
) => {
143-
const visitorId = filters?.find((f) => f.field === "anonymous_id")?.value;
130+
profile_detail: {
131+
customSql: (
132+
websiteId: string,
133+
startDate: string,
134+
endDate: string,
135+
filters?: Filter[],
136+
_granularity?: TimeUnit,
137+
_limit?: number,
138+
_offset?: number,
139+
_timezone?: string,
140+
_filterConditions?: string[],
141+
_filterParams?: Record<string, Filter["value"]>
142+
) => {
143+
const visitorId = filters?.find((f) => f.field === "anonymous_id")?.value;
144144

145-
if (!visitorId || typeof visitorId !== "string") {
146-
throw new Error(
147-
"anonymous_id filter is required for profile_detail query"
148-
);
149-
}
145+
if (!visitorId || typeof visitorId !== "string") {
146+
throw new Error(
147+
"anonymous_id filter is required for profile_detail query"
148+
);
149+
}
150150

151-
return {
152-
sql: `
151+
return {
152+
sql: `
153153
SELECT
154154
anonymous_id as visitor_id,
155155
MIN(time) as first_visit,
@@ -171,36 +171,36 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
171171
AND time <= toDateTime({endDate:String})
172172
GROUP BY anonymous_id
173173
`,
174-
params: {
175-
websiteId,
176-
visitorId,
177-
startDate,
178-
endDate: `${endDate} 23:59:59`,
179-
},
180-
};
181-
},
182-
},
174+
params: {
175+
websiteId,
176+
visitorId,
177+
startDate,
178+
endDate: `${endDate} 23:59:59`,
179+
},
180+
};
181+
},
182+
},
183183

184-
profile_sessions: {
185-
customSql: (
186-
websiteId: string,
187-
startDate: string,
188-
endDate: string,
189-
filters?: Filter[],
190-
_granularity?: TimeUnit,
191-
limit = 100,
192-
offset = 0
193-
) => {
194-
const visitorId = filters?.find((f) => f.field === "anonymous_id")?.value;
184+
profile_sessions: {
185+
customSql: (
186+
websiteId: string,
187+
startDate: string,
188+
endDate: string,
189+
filters?: Filter[],
190+
_granularity?: TimeUnit,
191+
limit = 100,
192+
offset = 0
193+
) => {
194+
const visitorId = filters?.find((f) => f.field === "anonymous_id")?.value;
195195

196-
if (!visitorId || typeof visitorId !== "string") {
197-
throw new Error(
198-
"anonymous_id filter is required for profile_sessions query"
199-
);
200-
}
196+
if (!visitorId || typeof visitorId !== "string") {
197+
throw new Error(
198+
"anonymous_id filter is required for profile_sessions query"
199+
);
200+
}
201201

202-
return {
203-
sql: `
202+
return {
203+
sql: `
204204
WITH user_sessions AS (
205205
SELECT
206206
session_id,
@@ -272,18 +272,18 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
272272
LEFT JOIN session_events se ON us.session_id = se.session_id
273273
ORDER BY us.first_visit DESC
274274
`,
275-
params: {
276-
websiteId,
277-
visitorId,
278-
startDate,
279-
endDate: `${endDate} 23:59:59`,
280-
limit,
281-
offset,
282-
},
283-
};
284-
},
285-
plugins: {
286-
normalizeGeo: true,
287-
},
288-
},
275+
params: {
276+
websiteId,
277+
visitorId,
278+
startDate,
279+
endDate: `${endDate} 23:59:59`,
280+
limit,
281+
offset,
282+
},
283+
};
284+
},
285+
plugins: {
286+
normalizeGeo: true,
287+
},
288+
},
289289
};

apps/api/src/query/expressions.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -635,26 +635,26 @@ export function buildWith(ctes: Array<{ name: string; sql: string }>): string {
635635
type FieldDefinitionType =
636636
| { type: "column"; source: string; alias?: string }
637637
| {
638-
type: "aggregate";
639-
fn: AggregateFn;
640-
source?: string;
641-
condition?: string;
642-
alias: string;
643-
}
638+
type: "aggregate";
639+
fn: AggregateFn;
640+
source?: string;
641+
condition?: string;
642+
alias: string;
643+
}
644644
| { type: "expression"; expression: string | SqlExpression; alias: string }
645645
| {
646-
type: "window";
647-
fn: AggregateFn;
648-
source?: string;
649-
over: { partitionBy?: string[]; orderBy?: string };
650-
alias: string;
651-
}
646+
type: "window";
647+
fn: AggregateFn;
648+
source?: string;
649+
over: { partitionBy?: string[]; orderBy?: string };
650+
alias: string;
651+
}
652652
| {
653-
type: "computed";
654-
metric: "bounceRate" | "percentageOfTotal" | "pagesPerSession";
655-
inputs: string[];
656-
alias: string;
657-
};
653+
type: "computed";
654+
metric: "bounceRate" | "percentageOfTotal" | "pagesPerSession";
655+
inputs: string[];
656+
alias: string;
657+
};
658658

659659
type AliasedExpressionType = { expression: SqlExpression; alias: string };
660660
type ConfigFieldType = string | FieldDefinitionType | AliasedExpressionType;
@@ -743,9 +743,12 @@ function compileAggregate(
743743
case "quantileIf":
744744
// For quantile with condition, source should be "level)(column"
745745
// e.g., source = "0.50)(metric_value" produces quantileIf(0.50)(metric_value, condition)
746-
return source
747-
? `quantileIf(${source}, ${condition})`
748-
: `quantileIf(0.50)(1, ${condition})`;
746+
if (!source) {
747+
throw new Error(
748+
"quantileIf aggregate function requires a source column (e.g., '0.50)(metric_value')"
749+
);
750+
}
751+
return `quantileIf(${source}, ${condition})`;
749752
default:
750753
// For other aggregates, apply condition as WHERE in subquery pattern
751754
return source
@@ -780,7 +783,12 @@ function compileAggregate(
780783
return `groupArray(${source || "*"})`;
781784
case "quantile":
782785
// source should be "level)(column" e.g., "0.50)(metric_value"
783-
return source ? `quantile(${source})` : "quantile(0.50)(*)";
786+
if (!source) {
787+
throw new Error(
788+
"quantile aggregate function requires a source column (e.g., '0.50)(metric_value')"
789+
);
790+
}
791+
return `quantile(${source})`;
784792
// Conditional variants without condition just use base
785793
case "countIf":
786794
return source ? `count(${source})` : "count()";
@@ -797,7 +805,12 @@ function compileAggregate(
797805
case "maxIf":
798806
return `max(${source || "*"})`;
799807
case "quantileIf":
800-
return source ? `quantile(${source})` : "quantile(0.50)(*)";
808+
if (!source) {
809+
throw new Error(
810+
"quantileIf aggregate function requires a source column (e.g., '0.50)(metric_value')"
811+
);
812+
}
813+
return `quantile(${source})`;
801814
default:
802815
return `${fn}(${source || "*"})`;
803816
}

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,18 +216,28 @@ export function WebsiteAudienceTab({
216216
[]
217217
);
218218

219-
const displayNames =
220-
typeof window !== "undefined"
221-
? new Intl.DisplayNames([navigator.language || "en"], {
222-
type: "language",
223-
})
224-
: null;
225-
226-
const countryColumns = createGeoColumns({ type: "country" });
227-
const regionColumns = createGeoColumns({ type: "region" });
228-
const cityColumns = createGeoColumns({ type: "city" });
229-
const timezoneColumns = createTimezoneColumns();
230-
const languageColumns = createLanguageColumns(displayNames);
219+
// Memoize displayNames to prevent recreation on every render
220+
const displayNames = useMemo(() => {
221+
if (typeof window === "undefined") {
222+
return null;
223+
}
224+
return new Intl.DisplayNames([navigator.language || "en"], {
225+
type: "language",
226+
});
227+
}, []);
228+
229+
// Memoize column sets to prevent recreation on every render
230+
const countryColumns = useMemo(
231+
() => createGeoColumns({ type: "country" }),
232+
[]
233+
);
234+
const regionColumns = useMemo(() => createGeoColumns({ type: "region" }), []);
235+
const cityColumns = useMemo(() => createGeoColumns({ type: "city" }), []);
236+
const timezoneColumns = useMemo(() => createTimezoneColumns(), []);
237+
const languageColumns = useMemo(
238+
() => createLanguageColumns(displayNames),
239+
[displayNames]
240+
);
231241

232242
const geographicTabs = useMemo(
233243
() => [

0 commit comments

Comments
 (0)