Render UTC dates in sessions.

This commit is contained in:
Mike Cao 2024-08-16 00:35:08 -07:00
parent c79720ae1d
commit deb9dd60df
10 changed files with 35 additions and 62 deletions

View File

@ -7,16 +7,12 @@ import styles from './SessionActivity.module.css';
export function SessionActivity({ export function SessionActivity({
websiteId, websiteId,
sessionId, sessionId,
startDate,
endDate,
}: { }: {
websiteId: string; websiteId: string;
sessionId: string; sessionId: string;
startDate: string;
endDate: string;
}) { }) {
const { formatDate } = useTimezone(); const { formatDate } = useTimezone();
const { data, isLoading } = useSessionActivity(websiteId, sessionId, startDate, endDate); const { data, isLoading } = useSessionActivity(websiteId, sessionId);
if (isLoading) { if (isLoading) {
return <Loading position="page" />; return <Loading position="page" />;

View File

@ -28,12 +28,7 @@ export default function SessionDetailsPage({
</div> </div>
<div className={styles.content}> <div className={styles.content}>
<SessionStats data={data} /> <SessionStats data={data} />
<SessionActivity <SessionActivity websiteId={websiteId} sessionId={sessionId} />
websiteId={websiteId}
sessionId={sessionId}
startDate={data?.firstAt}
endDate={data?.lastAt}
/>
</div> </div>
<div className={styles.data}> <div className={styles.data}>
<SessionData websiteId={websiteId} sessionId={sessionId} /> <SessionData websiteId={websiteId} sessionId={sessionId} />

View File

@ -1,17 +1,12 @@
import { useApi } from './useApi'; import { useApi } from './useApi';
export function useSessionActivity( export function useSessionActivity(websiteId: string, sessionId: string) {
websiteId: string,
sessionId: string,
startDate: string,
endDate: string,
) {
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
return useQuery({ return useQuery({
queryKey: ['session:activity', { websiteId, sessionId }], queryKey: ['session:activity', { websiteId, sessionId }],
queryFn: () => { queryFn: () => {
return get(`/websites/${websiteId}/sessions/${sessionId}/activity`, { startDate, endDate }); return get(`/websites/${websiteId}/sessions/${sessionId}/activity`);
}, },
}); });
} }

View File

@ -1,19 +1,18 @@
import { useNavigation } from './useNavigation'; import { useNavigation } from './useNavigation';
import { useDateRange } from './useDateRange'; import { useDateRange } from './useDateRange';
import { useTimezone } from './useTimezone'; import { useTimezone } from './useTimezone';
import { zonedTimeToUtc } from 'date-fns-tz';
export function useFilterParams(websiteId: string) { export function useFilterParams(websiteId: string) {
const { dateRange } = useDateRange(websiteId); const { dateRange } = useDateRange(websiteId);
const { startDate, endDate, unit } = dateRange; const { startDate, endDate, unit } = dateRange;
const { timezone } = useTimezone(); const { timezone, toUtc } = useTimezone();
const { const {
query: { url, referrer, title, query, host, os, browser, device, country, region, city, event }, query: { url, referrer, title, query, host, os, browser, device, country, region, city, event },
} = useNavigation(); } = useNavigation();
return { return {
startAt: +zonedTimeToUtc(startDate, timezone), startAt: +toUtc(startDate),
endAt: +zonedTimeToUtc(endDate, timezone), endAt: +toUtc(endDate),
unit, unit,
timezone, timezone,
url, url,

View File

@ -1,6 +1,6 @@
import { setItem } from 'next-basics'; import { setItem } from 'next-basics';
import { TIMEZONE_CONFIG } from 'lib/constants'; import { TIMEZONE_CONFIG } from 'lib/constants';
import { formatInTimeZone } from 'date-fns-tz'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
import useStore, { setTimezone } from 'store/app'; import useStore, { setTimezone } from 'store/app';
const selector = (state: { timezone: string }) => state.timezone; const selector = (state: { timezone: string }) => state.timezone;
@ -23,7 +23,15 @@ export function useTimezone() {
); );
}; };
return { timezone, saveTimezone, formatDate }; const toUtc = (date: Date | string | number) => {
return zonedTimeToUtc(date, timezone);
};
const fromUtc = (date: Date | string | number) => {
return utcToZonedTime(date, timezone);
};
return { timezone, saveTimezone, formatDate, toUtc, fromUtc };
} }
export default useTimezone; export default useTimezone;

View File

@ -8,6 +8,7 @@ import { filtersToArray } from './params';
import { PageParams, QueryFilters, QueryOptions } from './types'; import { PageParams, QueryFilters, QueryOptions } from './types';
export const CLICKHOUSE_DATE_FORMATS = { export const CLICKHOUSE_DATE_FORMATS = {
utc: '%Y-%m-%dT%H:%i:%SZ',
second: '%Y-%m-%d %H:%i:%S', second: '%Y-%m-%d %H:%i:%S',
minute: '%Y-%m-%d %H:%i:00', minute: '%Y-%m-%d %H:%i:00',
hour: '%Y-%m-%d %H:00:00', hour: '%Y-%m-%d %H:00:00',
@ -47,7 +48,7 @@ function getClient() {
return client; return client;
} }
function getDateStringSQL(data: any, unit: string | number, timezone?: string) { function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
if (timezone) { if (timezone) {
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`; return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`;
} }

View File

@ -9,16 +9,12 @@ import { getSessionActivity } from 'queries';
export interface SessionActivityRequestQuery extends PageParams { export interface SessionActivityRequestQuery extends PageParams {
websiteId: string; websiteId: string;
sessionId: string; sessionId: string;
startDate: string;
endDate: string;
} }
const schema = { const schema = {
GET: yup.object().shape({ GET: yup.object().shape({
websiteId: yup.string().uuid().required(), websiteId: yup.string().uuid().required(),
sessionId: yup.string().uuid().required(), sessionId: yup.string().uuid().required(),
startDate: yup.string().required(),
endDate: yup.string().required(),
}), }),
}; };
@ -30,19 +26,14 @@ export default async (
await useAuth(req, res); await useAuth(req, res);
await useValidate(schema, req, res); await useValidate(schema, req, res);
const { websiteId, sessionId, startDate, endDate } = req.query; const { websiteId, sessionId } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
if (!(await canViewWebsite(req.auth, websiteId))) { if (!(await canViewWebsite(req.auth, websiteId))) {
return unauthorized(res); return unauthorized(res);
} }
const data = await getSessionActivity( const data = await getSessionActivity(websiteId, sessionId);
websiteId,
sessionId,
new Date(startDate + 'Z'),
new Date(endDate + 'Z'),
);
return ok(res, data); return ok(res, data);
} }

View File

@ -2,44 +2,32 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
export async function getSessionActivity( export async function getSessionActivity(...args: [websiteId: string, sessionId: string]) {
...args: [websiteId: string, sessionId: string, startDate: Date, endDate: Date]
) {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
}); });
} }
async function relationalQuery( async function relationalQuery(websiteId: string, sessionId: string) {
websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
return prisma.client.websiteEvent.findMany({ return prisma.client.websiteEvent.findMany({
where: { where: {
id: sessionId, id: sessionId,
websiteId, websiteId,
createdAt: { gte: startDate, lte: endDate },
}, },
take: 500,
}); });
} }
async function clickhouseQuery( async function clickhouseQuery(websiteId: string, sessionId: string) {
websiteId: string, const { rawQuery, getDateStringSQL } = clickhouse;
sessionId: string,
startDate: Date,
endDate: Date,
) {
const { rawQuery } = clickhouse;
return rawQuery( return rawQuery(
` `
select select
session_id as id, session_id as id,
website_id as websiteId, website_id as websiteId,
created_at as createdAt, ${getDateStringSQL('created_at')} as createdAt,
url_path as urlPath, url_path as urlPath,
url_query as urlQuery, url_query as urlQuery,
referrer_domain as referrerDomain, referrer_domain as referrerDomain,
@ -50,9 +38,9 @@ async function clickhouseQuery(
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and session_id = {sessionId:UUID} and session_id = {sessionId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
order by created_at desc order by created_at desc
limit 500
`, `,
{ websiteId, sessionId, startDate, endDate }, { websiteId, sessionId },
); );
} }

View File

@ -19,7 +19,7 @@ async function relationalQuery(websiteId: string, sessionId: string) {
} }
async function clickhouseQuery(websiteId: string, sessionId: string) { async function clickhouseQuery(websiteId: string, sessionId: string) {
const { rawQuery } = clickhouse; const { rawQuery, getDateStringSQL } = clickhouse;
return rawQuery( return rawQuery(
` `
@ -34,8 +34,8 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
country, country,
subdivision1, subdivision1,
city, city,
min(min_time) as firstAt, ${getDateStringSQL('min(min_time)')} as firstAt,
max(max_time) as lastAt, ${getDateStringSQL('max(max_time)')} as lastAt,
uniq(visit_id) visits, uniq(visit_id) visits,
sum(views) as views, sum(views) as views,
sum(events) as events, sum(events) as events,

View File

@ -24,7 +24,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
} }
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters } = clickhouse; const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
return pagedQuery( return pagedQuery(
@ -42,8 +42,8 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
country, country,
subdivision1, subdivision1,
city, city,
min(min_time) as firstAt, ${getDateStringSQL('min(min_time)')} as firstAt,
max(max_time) as lastAt, ${getDateStringSQL('max(max_time)')} as lastAt,
uniq(visit_id) as visits, uniq(visit_id) as visits,
sumIf(views, event_type = 1) as views sumIf(views, event_type = 1) as views
from website_event_stats_hourly from website_event_stats_hourly