diff --git a/package.json b/package.json index af873918..dd43d889 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ ], "dependencies": { "@clickhouse/client": "^1.3.0", + "@date-fns/utc": "^1.2.0", "@dicebear/collection": "^9.2.1", "@dicebear/core": "^9.2.1", "@fontsource/inter": "^4.5.15", diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index 261f8e47..c09a797c 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -1,13 +1,14 @@ import { GridTable, GridColumn, Icon } from 'react-basics'; -import { useLocale, useMessages, useTeamUrl } from 'components/hooks'; +import { useLocale, useMessages, useTeamUrl, useTimezone } from 'components/hooks'; import Empty from 'components/common/Empty'; import Avatar from 'components/common/Avatar'; import Link from 'next/link'; import Icons from 'components/icons'; -import { formatDate } from 'lib/date'; +import { formatInTimeZone } from 'date-fns-tz'; export function EventsTable({ data = [] }) { - const { locale } = useLocale(); + const { dateLocale } = useLocale(); + const { timezone } = useTimezone(); const { formatMessage, labels } = useMessages(); const { renderTeamUrl } = useTeamUrl(); @@ -36,7 +37,11 @@ export function EventsTable({ data = [] }) { }} - {row => formatDate(new Date(row.createdAt), 'PPPpp', locale)} + {row => + formatInTimeZone(new Date(row.createdAt.split(' ').join('T') + 'Z'), timezone, 'PPPpp', { + locale: dateLocale, + }) + } ); diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx index ad4a8ac9..0f346f8b 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx @@ -1,13 +1,14 @@ import Link from 'next/link'; import { GridColumn, GridTable } from 'react-basics'; -import { useFormat, useLocale, useMessages } from 'components/hooks'; +import { useFormat, useLocale, useMessages, useTimezone } from 'components/hooks'; import Avatar from 'components/common/Avatar'; import styles from './SessionsTable.module.css'; -import { formatDate } from 'lib/date'; import TypeIcon from 'components/common/TypeIcon'; +import { formatInTimeZone } from 'date-fns-tz'; export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) { - const { locale } = useLocale(); + const { dateLocale } = useLocale(); + const { timezone } = useTimezone(); const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); @@ -52,7 +53,11 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean )} - {row => formatDate(new Date(row.lastAt), 'PPPpp', locale)} + {row => + formatInTimeZone(new Date(row.createdAt.split(' ').join('T') + 'Z'), timezone, 'PPPpp', { + locale: dateLocale, + }) + } ); diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index c25eff4e..bf3da950 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -9,12 +9,12 @@ import { filtersToArray } from './params'; import { PageParams, QueryFilters, QueryOptions } from './types'; export const CLICKHOUSE_DATE_FORMATS = { - second: '%Y-%m-%dT%H:%i:%S', - minute: '%Y-%m-%dT%H:%i:00', - hour: '%Y-%m-%dT%H:00:00', - day: '%Y-%m-%dT00:00:00', - month: '%Y-%m-01T00:00:00', - year: '%Y-01-01T00:00:00', + second: '%Y-%m-%d %H:%i:%S', + minute: '%Y-%m-%d %H:%i:00', + hour: '%Y-%m-%d %H:00:00', + day: '%Y-%m-%d', + month: '%Y-%m-01', + year: '%Y-01-01', }; const log = debug('umami:clickhouse'); @@ -101,12 +101,18 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) } function getDateQuery(filters: QueryFilters = {}) { - const { startDate, endDate } = filters; + const { startDate, endDate, timezone } = filters; if (startDate) { if (endDate) { + if (timezone) { + return `and created_at between toTimezone({startDate:DateTime64},{timezone:String}) and toTimezone({endDate:DateTime64},{timezone:String})`; + } return `and created_at between {startDate:DateTime64} and {endDate:DateTime64}`; } else { + if (timezone) { + return `and created_at >= toTimezone({startDate:DateTime64},{timezone:String})`; + } return `and created_at >= {startDate:DateTime64}`; } } diff --git a/src/queries/analytics/events/getWebsiteEvents.ts b/src/queries/analytics/events/getWebsiteEvents.ts index 2fe2bda2..296602ea 100644 --- a/src/queries/analytics/events/getWebsiteEvents.ts +++ b/src/queries/analytics/events/getWebsiteEvents.ts @@ -26,7 +26,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar } async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { - const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse; + const { pagedQuery, parseFilters } = clickhouse; const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); const { query } = pageParams; @@ -36,7 +36,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar event_id as id, website_id as websiteId, session_id as sessionId, - ${getDateStringSQL('created_at', 'second', filters.timezone)} as createdAt, + created_at as createdAt, url_path as urlPath, url_query as urlQuery, referrer_path as referrerPath, diff --git a/yarn.lock b/yarn.lock index 9cfe709f..e7456af7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1381,6 +1381,11 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@date-fns/utc@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@date-fns/utc/-/utc-1.2.0.tgz#fb705b025b6769840608782c8fa7f3919d1b3337" + integrity sha512-YLq+crMPJiBmIdkRmv9nZuZy1mVtMlDcUKlg4mvI0UsC/dZeIaGoGB5p/C4FrpeOhZ7zBTK03T58S0DFkRNMnw== + "@dicebear/adventurer-neutral@9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@dicebear/adventurer-neutral/-/adventurer-neutral-9.2.1.tgz#0416ff71d0dd3ff391db9c9fda2acb166b074c5f"