mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-06 01:15:42 +01:00
Render UTC dates in sessions.
This commit is contained in:
parent
c79720ae1d
commit
deb9dd60df
@ -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" />;
|
||||||
|
@ -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} />
|
||||||
|
@ -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`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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}')`;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user