Skip to content

Commit 206a1f6

Browse files
committed
Refactor error event and name hooks to utilize buildApiParams for cleaner API parameter handling
- Removed direct date handling in `useGetErrorEventsInfinite` and `useGetErrorNamesPaginated` hooks, replacing it with `buildApiParams` for improved readability and maintainability. - Updated `useGetGSCData` to implement a new date range function that accommodates GSC API requirements, ensuring accurate date handling. - Enhanced session and user analytics queries to include additional parameters and improve filter handling, ensuring consistent data retrieval across endpoints.
1 parent 75e06be commit 206a1f6

7 files changed

Lines changed: 62 additions & 43 deletions

File tree

client/src/api/analytics/hooks/errors/useGetErrorEvents.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
import { getTimezone, useStore } from "@/lib/store";
1+
import { useStore } from "@/lib/store";
22
import { useInfiniteQuery } from "@tanstack/react-query";
3-
import { getStartAndEndDate } from "../../../utils";
3+
import { buildApiParams } from "../../../utils";
44
import { ErrorEventsPaginatedResponse, fetchErrorEvents } from "../../endpoints";
55

66
// Hook for infinite scrolling
77
export function useGetErrorEventsInfinite(errorMessage: string, enabled: boolean = true) {
88
const { time, site, filters, timezone } = useStore();
99

10-
const { startDate, endDate } = getStartAndEndDate(time);
11-
1210
return useInfiniteQuery({
1311
queryKey: ["error-events-infinite", time, site, filters, errorMessage, timezone],
1412
queryFn: async ({ pageParam = 1 }) => {
1513
const data = await fetchErrorEvents(site, {
16-
startDate: startDate ?? "",
17-
endDate: endDate ?? "",
18-
timeZone: getTimezone(),
19-
filters,
14+
...buildApiParams(time, { filters }),
2015
errorMessage,
2116
limit: 20,
2217
page: pageParam,

client/src/api/analytics/hooks/errors/useGetErrorNames.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { getTimezone, useStore } from "@/lib/store";
1+
import { useStore } from "@/lib/store";
22
import { useQuery, UseQueryResult } from "@tanstack/react-query";
3-
import { getStartAndEndDate } from "../../../utils";
4-
import {
5-
fetchErrorNames,
6-
ErrorNameItem,
7-
ErrorNamesPaginatedResponse,
8-
ErrorNamesStandardResponse,
9-
} from "../../endpoints";
3+
import { buildApiParams } from "../../../utils";
4+
import { fetchErrorNames, ErrorNamesPaginatedResponse } from "../../endpoints";
105

116
type UseGetErrorNamesOptions = {
127
limit?: number;
@@ -22,16 +17,11 @@ export function useGetErrorNamesPaginated({
2217
}: UseGetErrorNamesOptions): UseQueryResult<{ data: ErrorNamesPaginatedResponse }> {
2318
const { time, site, filters, timezone } = useStore();
2419

25-
const { startDate, endDate } = getStartAndEndDate(time);
26-
2720
return useQuery({
2821
queryKey: ["error-names", time, site, filters, limit, page, useFilters, timezone],
2922
queryFn: async () => {
3023
const data = await fetchErrorNames(site, {
31-
startDate: startDate ?? "",
32-
endDate: endDate ?? "",
33-
timeZone: getTimezone(),
34-
filters: useFilters ? filters : undefined,
24+
...buildApiParams(time, { filters: useFilters ? filters : undefined }),
3525
limit,
3626
page,
3727
});
@@ -48,16 +38,11 @@ export function useGetErrorNames({
4838
}: Omit<UseGetErrorNamesOptions, "page">): UseQueryResult<{ data: ErrorNamesPaginatedResponse }> {
4939
const { time, site, filters, timezone } = useStore();
5040

51-
const { startDate, endDate } = getStartAndEndDate(time);
52-
5341
return useQuery({
5442
queryKey: ["error-names", time, site, filters, limit, timezone],
5543
queryFn: async () => {
5644
const data = await fetchErrorNames(site, {
57-
startDate: startDate ?? "",
58-
endDate: endDate ?? "",
59-
timeZone: getTimezone(),
60-
filters: useFilters ? filters : undefined,
45+
...buildApiParams(time, { filters: useFilters ? filters : undefined }),
6146
limit,
6247
});
6348
return { data };

client/src/api/gsc/hooks/useGetGSCData.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,45 @@
11
import { useQuery } from "@tanstack/react-query";
2-
import { useStore } from "../../../lib/store";
3-
import { buildApiParams } from "../../utils";
4-
import { toQueryParams } from "../../analytics/endpoints/types";
2+
import { DateTime } from "luxon";
3+
import { Time } from "../../../components/DateSelector/types";
4+
import { getTimezone, useStore } from "../../../lib/store";
5+
import { getStartAndEndDate } from "../../utils";
56
import { fetchGSCData, GSCDimension } from "../endpoints";
67

8+
// The GSC API only accepts whole-day date ranges and keeps ~16 months of
9+
// history, so sub-day dashboard ranges are widened to whole days and
10+
// all-time is clamped to Google's maximum lookback.
11+
function getGSCDateRange(time: Time): { startDate: string; endDate: string } {
12+
const today = DateTime.now().setZone(getTimezone());
13+
if (time.mode === "past-minutes") {
14+
return {
15+
startDate: today.minus({ minutes: time.pastMinutesStart }).toISODate() ?? "",
16+
endDate: today.toISODate() ?? "",
17+
};
18+
}
19+
if (time.mode === "all-time") {
20+
return {
21+
startDate: today.minus({ months: 16 }).toISODate() ?? "",
22+
endDate: today.toISODate() ?? "",
23+
};
24+
}
25+
const { startDate, endDate } = getStartAndEndDate(time);
26+
return { startDate: startDate ?? "", endDate: endDate ?? "" };
27+
}
28+
729
/**
830
* Hook to fetch data from Google Search Console for a specific dimension
931
*/
1032
export function useGetGSCData(dimension: GSCDimension) {
1133
const { site, time, timezone } = useStore();
12-
const timeParams = toQueryParams(buildApiParams(time));
34+
const { startDate, endDate } = getGSCDateRange(time);
1335

1436
return useQuery({
15-
queryKey: ["gsc-data", dimension, site, timeParams, timezone],
37+
queryKey: ["gsc-data", dimension, site, startDate, endDate, timezone],
1638
queryFn: () => {
1739
return fetchGSCData(site!, {
1840
dimension,
19-
startDate: timeParams.start_date,
20-
endDate: timeParams.end_date,
41+
startDate,
42+
endDate,
2143
timeZone: timezone,
2244
});
2345
},

server/src/api/analytics/sessions/getSessions.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,12 @@ export async function getSessions(req: FastifyRequest<GetSessionsRequest>, res:
9797
const timeStatement = getTimeStatement(req.query);
9898

9999
// Use composable filter options:
100-
// - sessionLevelParams: pathname and page_title filter at session level (finds sessions that visited a page)
100+
// - sessionLevelParams: per-event fields filter at session level (finds sessions
101+
// containing a matching event) — required for any parameter the aggregated CTE
102+
// below doesn't project, otherwise the outer WHERE hits an unknown identifier
101103
// - fieldMappings: CTE extracts UTM params as separate columns, so we need to map the field names
102104
const filterStatement = getFilterStatement(filters, Number(site), timeStatement, {
103-
sessionLevelParams: ["event_name", "pathname", "page_title", "channel"],
105+
sessionLevelParams: ["event_name", "pathname", "page_title", "querystring", "channel"],
104106
fieldMappings: SESSION_FIELD_MAPPINGS,
105107
});
106108

@@ -145,7 +147,8 @@ export async function getSessions(req: FastifyRequest<GetSessionsRequest>, res:
145147
argMax(ip, timestamp) AS ip,
146148
argMax(lat, timestamp) AS lat,
147149
argMax(lon, timestamp) AS lon,
148-
argMax(tag, timestamp) AS tag
150+
argMax(tag, timestamp) AS tag,
151+
argMax(timezone, timestamp) AS timezone
149152
FROM events
150153
WHERE
151154
site_id = {siteId:Int32}

server/src/api/analytics/users/getUsers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ export async function getUsers(req: FastifyRequest<GetUsersRequest>, res: Fastif
9999

100100
// Generate filter statement and time statement
101101
const timeStatement = getTimeStatement(req.query);
102+
// Applied inside the CTE against raw events (same placement as the count
103+
// queries): the aggregate doesn't project every filterable column
104+
// (pathname, querystring, utm_*, …), and event-level placement keeps the
105+
// returned rows consistent with totalCount.
102106
const filterStatement = getFilterStatement(filters, Number(site), timeStatement);
103107

104108
const query = `
@@ -132,14 +136,15 @@ WITH AggregatedUsers AS (
132136
WHERE
133137
site_id = {siteId:Int32}
134138
${timeStatement}
139+
${filterStatement}
135140
${matchingUserIds ? "AND events.identified_user_id IN ({matchingUserIds:Array(String)})" : ""}
136141
GROUP BY
137142
effective_user_id
138143
)
139144
SELECT
140145
*
141146
FROM AggregatedUsers
142-
WHERE 1 = 1 ${filterStatement}
147+
WHERE 1 = 1
143148
${filterIdentified ? "AND identified_user_id != ''" : ""}
144149
ORDER BY ${actualSortBy} ${actualSortOrder}
145150
LIMIT {limit:Int32} OFFSET {offset:Int32}

server/src/api/analytics/utils/query-validation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,16 @@ const httpTimeParamsSchema = z
415415

416416
/**
417417
* Returns an error message if the request's time query params are present but
418-
* invalid, or null if they are valid or absent.
418+
* invalid, or null if they are valid or absent. Empty-string values count as
419+
* absent: the dashboard sends `start_date=&end_date=` in all-time mode, and
420+
* `?param=` in a query string has always meant "no value" to these endpoints.
419421
*/
420422
export function validateHttpTimeParams(query: unknown): string | null {
421-
const result = httpTimeParamsSchema.safeParse(query ?? {});
423+
const withoutEmpty =
424+
typeof query === "object" && query !== null
425+
? Object.fromEntries(Object.entries(query).filter(([, value]) => value !== ""))
426+
: {};
427+
const result = httpTimeParamsSchema.safeParse(withoutEmpty);
422428
if (result.success) {
423429
return null;
424430
}

server/src/api/sites/getSitesFromOrg.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,11 @@ export async function getSitesFromOrg(
111111
siteTeamMap.set(mapping.siteId, existing);
112112
}
113113

114-
// Enhance sites data with session counts and subscription info
115-
const enhancedSitesData = sitesData.map(site => ({
114+
// Enhance sites data with session counts and subscription info.
115+
// apiKey and privateLinkKey are secrets (ingestion auth / private-link
116+
// dashboard access) and must not be exposed to org members here — the
117+
// client reads them from the admin-gated per-site endpoints instead.
118+
const enhancedSitesData = sitesData.map(({ apiKey, privateLinkKey, ...site }) => ({
116119
...site,
117120
type: site.type || "web",
118121
domain: site.domain || "",

0 commit comments

Comments
 (0)