From 29a373467ae0e1d7137d2e8b14b8ff50cc464165 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 11:42:09 -0800 Subject: [PATCH 01/11] Revert "Merge pull request #3972 from IndraGunawan/fix-inconsistent-date-format" This reverts commit 5f316a79e508564ea5029eac67a7cdcf3fc35351, reversing changes made to 7bb30443a8db45dea34e01a201b68cc265225899. --- src/lib/prisma.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index cbabe03b..bfd007d1 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -20,6 +20,14 @@ const PRISMA_LOG_OPTIONS = { }; const DATE_FORMATS = { + minute: 'YYYY-MM-DD HH24:MI:00', + hour: 'YYYY-MM-DD HH24:00:00', + day: 'YYYY-MM-DD HH24:00:00', + month: 'YYYY-MM-01 HH24:00:00', + year: 'YYYY-01-01 HH24:00:00', +}; + +const DATE_FORMATS_UTC = { minute: 'YYYY-MM-DD"T"HH24:MI:00"Z"', hour: 'YYYY-MM-DD"T"HH24:00:00"Z"', day: 'YYYY-MM-DD"T"HH24:00:00"Z"', @@ -44,7 +52,7 @@ function getDateSQL(field: string, unit: string, timezone?: string): string { return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${DATE_FORMATS[unit]}')`; } - return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS[unit]}')`; + return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS_UTC[unit]}')`; } function getDateWeeklySQL(field: string, timezone?: string) { From 4867492ca3052cd13f0d431ece9906771bf01f84 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 13:52:46 -0800 Subject: [PATCH 02/11] Fix breakdown alias column not found bug --- src/queries/sql/reports/getBreakdown.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queries/sql/reports/getBreakdown.ts b/src/queries/sql/reports/getBreakdown.ts index 51773d86..c84db769 100644 --- a/src/queries/sql/reports/getBreakdown.ts +++ b/src/queries/sql/reports/getBreakdown.ts @@ -131,5 +131,5 @@ function parseFields(fields: string[]) { } function parseFieldsByName(fields: string[]) { - return `${fields.map(name => name).join(',')}`; + return `${fields.map(name => `"${name}"`).join(',')}`; } From 1498da2d02b3b8f4bdd53a9e0d834fc690ba0f2e Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 15:26:02 -0800 Subject: [PATCH 03/11] fix FilterButton import error --- .../websites/[websiteId]/(reports)/journeys/JourneysPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx b/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx index c2dd8349..f1a8976f 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx @@ -1,10 +1,10 @@ 'use client'; import { Column, Grid, ListItem, Row, SearchField, Select } from '@umami/react-zen'; -import { FilterButtons } from 'dist'; import { useState } from 'react'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { Panel } from '@/components/common/Panel'; import { useDateRange, useMessages } from '@/components/hooks'; +import { FilterButtons } from '@/components/input/FilterButtons'; import { Journey } from './Journey'; const JOURNEY_STEPS = [2, 3, 4, 5, 6, 7]; From a1a092dc1976e8b95f7e34a46eb4a459f73bb1d3 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 17:08:34 -0800 Subject: [PATCH 04/11] Add MetricsBar to Events page. Closes #3830 --- .../[websiteId]/events/EventsPage.tsx | 53 +++++++++- .../[websiteId]/events/stats/route.ts | 34 +++++++ .../hooks/queries/useEventStatsQuery.ts | 37 +++++++ src/components/messages.ts | 1 + .../sql/events/getWebsiteEventStats.ts | 97 +++++++++++++++++++ 5 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/app/api/websites/[websiteId]/events/stats/route.ts create mode 100644 src/components/hooks/queries/useEventStatsQuery.ts create mode 100644 src/queries/sql/events/getWebsiteEventStats.ts diff --git a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx index 55ec0403..f209705e 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx @@ -1,12 +1,16 @@ 'use client'; import { Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen'; -import { type Key, useState } from 'react'; +import locale from 'date-fns/locale/af'; +import { LoadingPanel, MetricCard, MetricsBar } from 'dist'; +import { type Key, useMemo, useState } from 'react'; import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { Panel } from '@/components/common/Panel'; import { useMessages } from '@/components/hooks'; +import { useEventStatsQuery } from '@/components/hooks/queries/useEventStatsQuery'; import { EventsChart } from '@/components/metrics/EventsChart'; import { MetricsTable } from '@/components/metrics/MetricsTable'; +import { formatLongNumber } from '@/lib/format'; import { getItem, setItem } from '@/lib/storage'; import { EventProperties } from './EventProperties'; import { EventsDataTable } from './EventsDataTable'; @@ -15,16 +19,61 @@ const KEY_NAME = 'umami.events.tab'; export function EventsPage({ websiteId }) { const [tab, setTab] = useState(getItem(KEY_NAME) || 'chart'); - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, getErrorMessage } = useMessages(); + const { data, isLoading, isFetching, error } = useEventStatsQuery({ + websiteId, + }); const handleSelect = (value: Key) => { setItem(KEY_NAME, value); setTab(value); }; + const metrics = useMemo(() => { + if (!data) return []; + + const { events, visitors, visits, uniqueEvents } = data || {}; + + return [ + { + value: visitors, + label: formatMessage(labels.visitors), + formatValue: formatLongNumber, + }, + { + value: visits, + label: formatMessage(labels.visits), + formatValue: formatLongNumber, + }, + { + value: events, + label: formatMessage(labels.events), + formatValue: formatLongNumber, + }, + { + value: uniqueEvents, + label: formatMessage(labels.uniqueEvents), + formatValue: formatLongNumber, + }, + ] as any; + }, [data, locale]); + return ( + + + {metrics?.map(({ label, value, formatValue }) => { + return ; + })} + + handleSelect(key)}> diff --git a/src/app/api/websites/[websiteId]/events/stats/route.ts b/src/app/api/websites/[websiteId]/events/stats/route.ts new file mode 100644 index 00000000..61e151d4 --- /dev/null +++ b/src/app/api/websites/[websiteId]/events/stats/route.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; +import { getQueryFilters, parseRequest } from '@/lib/request'; +import { json, unauthorized } from '@/lib/response'; +import { dateRangeParams, filterParams } from '@/lib/schema'; +import { canViewWebsite } from '@/permissions'; +import { getWebsiteEventStats } from '@/queries/sql/events/getWebsiteEventStats'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + ...dateRangeParams, + ...filterParams, + }); + + const { auth, query, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { websiteId } = await params; + + if (!(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const filters = await getQueryFilters(query, websiteId); + + const data = await getWebsiteEventStats(websiteId, filters); + + return json({ data }); +} diff --git a/src/components/hooks/queries/useEventStatsQuery.ts b/src/components/hooks/queries/useEventStatsQuery.ts new file mode 100644 index 00000000..44316ca5 --- /dev/null +++ b/src/components/hooks/queries/useEventStatsQuery.ts @@ -0,0 +1,37 @@ +import type { UseQueryOptions } from '@tanstack/react-query'; +import { useDateParameters } from '@/components/hooks/useDateParameters'; +import { useApi } from '../useApi'; +import { useFilterParameters } from '../useFilterParameters'; + +export interface EventStatsData { + events: number; + visitors: number; + visits: number; + uniqueEvents: number; +} + +type EventStatsApiResponse = { + data: EventStatsData; +}; + +export function useEventStatsQuery( + { websiteId }: { websiteId: string }, + options?: UseQueryOptions, +) { + const { get, useQuery } = useApi(); + const { startAt, endAt } = useDateParameters(); + const filters = useFilterParameters(); + + return useQuery({ + queryKey: ['websites:events:stats', { websiteId, startAt, endAt, ...filters }], + queryFn: () => + get(`/websites/${websiteId}/events/stats`, { + startAt, + endAt, + ...filters, + }), + select: response => response.data, + enabled: !!websiteId, + ...options, + }); +} diff --git a/src/components/messages.ts b/src/components/messages.ts index 3d7388cd..de29c306 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -146,6 +146,7 @@ export const labels = defineMessages({ poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' }, pageViews: { id: 'label.page-views', defaultMessage: 'Page views' }, uniqueVisitors: { id: 'label.unique-visitors', defaultMessage: 'Unique visitors' }, + uniqueEvents: { id: 'label.unique-events', defaultMessage: 'Unique Events' }, bounceRate: { id: 'label.bounce-rate', defaultMessage: 'Bounce rate' }, viewsPerVisit: { id: 'label.views-per-visit', defaultMessage: 'Views per visit' }, visitDuration: { id: 'label.visit-duration', defaultMessage: 'Visit duration' }, diff --git a/src/queries/sql/events/getWebsiteEventStats.ts b/src/queries/sql/events/getWebsiteEventStats.ts new file mode 100644 index 00000000..27179d10 --- /dev/null +++ b/src/queries/sql/events/getWebsiteEventStats.ts @@ -0,0 +1,97 @@ +import clickhouse from '@/lib/clickhouse'; +import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; +import prisma from '@/lib/prisma'; +import type { QueryFilters } from '@/lib/types'; + +const FUNCTION_NAME = 'getWebsiteEventStats'; + +export interface WebsiteEventStatsData { + events: number; + visitors: number; + visits: number; + uniqueEvents: number; +} + +export async function getWebsiteEventStats( + ...args: [websiteId: string, filters: QueryFilters] +): Promise { + return runQuery({ + [PRISMA]: () => relationalQuery(...args), + [CLICKHOUSE]: () => clickhouseQuery(...args), + }); +} + +async function relationalQuery( + websiteId: string, + filters: QueryFilters, +): Promise { + const { parseFilters, rawQuery } = prisma; + const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({ + ...filters, + websiteId, + }); + + return rawQuery( + ` + select + cast(coalesce(sum(t.c), 0) as bigint) as "events", + count(distinct t.session_id) as "visitors", + count(distinct t.visit_id) as "visits", + count(distinct t.event_name) as "uniqueEvents" + from ( + select + website_event.session_id, + website_event.visit_id, + website_event.event_name, + count(*) as "c" + from website_event + ${cohortQuery} + ${joinSessionQuery} + where website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}} + and website_event.event_type = 2 + ${filterQuery} + group by 1, 2, 3 + ) as t + `, + queryParams, + FUNCTION_NAME, + ).then(result => result?.[0]); +} + +async function clickhouseQuery( + websiteId: string, + filters: QueryFilters, +): Promise { + const { rawQuery, parseFilters } = clickhouse; + const { filterQuery, cohortQuery, queryParams } = parseFilters({ + ...filters, + websiteId, + }); + + return rawQuery( + ` + select + sum(t.c) as "events", + uniq(t.session_id) as "visitors", + uniq(t.visit_id) as "visits", + count(distinct t.event_name) as "uniqueEvents" + from ( + select + session_id, + visit_id, + event_name, + count(*) c + from website_event + ${cohortQuery} + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type = 2 + ${filterQuery} + group by session_id, visit_id, event_name + ) as t; + `, + queryParams, + FUNCTION_NAME, + ).then(result => result?.[0]); +} From 57eef5866b8058a50b39983b828fd60de53079dd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 22:43:35 -0800 Subject: [PATCH 05/11] remove event filter for non-event pages --- src/components/input/FilterEditForm.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/input/FilterEditForm.tsx b/src/components/input/FilterEditForm.tsx index 9221e3a2..87acc515 100644 --- a/src/components/input/FilterEditForm.tsx +++ b/src/components/input/FilterEditForm.tsx @@ -22,6 +22,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP const [currentCohort, setCurrentCohort] = useState(cohort); const { isMobile } = useMobile(); const excludeFilters = pathname.includes('/pixels') || pathname.includes('/links'); + const excludeEvent = !pathname.endsWith('/events'); const handleReset = () => { setCurrentFilters([]); @@ -62,7 +63,11 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP value={currentFilters} onChange={setCurrentFilters} exclude={ - excludeFilters ? ['path', 'title', 'hostname', 'distinctId', 'tag', 'event'] : [] + excludeFilters + ? ['path', 'title', 'hostname', 'distinctId', 'tag', 'event'] + : excludeEvent + ? ['event'] + : [] } /> From 2f998ff9d8d40c5a049137be3a2d4c3c570ae62a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 23:26:40 -0800 Subject: [PATCH 06/11] implement showActions to pixels/links for viewonly users. --- src/app/(main)/links/LinksDataTable.tsx | 4 +-- src/app/(main)/links/LinksPage.tsx | 15 ++++++++--- src/app/(main)/links/LinksTable.tsx | 28 ++++++++++++-------- src/app/(main)/pixels/PixelsDataTable.tsx | 4 +-- src/app/(main)/pixels/PixelsPage.tsx | 15 ++++++++--- src/app/(main)/pixels/PixelsTable.tsx | 31 ++++++++++++++--------- src/app/(main)/websites/WebsitesPage.tsx | 15 ++++++++--- 7 files changed, 76 insertions(+), 36 deletions(-) diff --git a/src/app/(main)/links/LinksDataTable.tsx b/src/app/(main)/links/LinksDataTable.tsx index 0b3d660b..87da5984 100644 --- a/src/app/(main)/links/LinksDataTable.tsx +++ b/src/app/(main)/links/LinksDataTable.tsx @@ -2,13 +2,13 @@ import { DataGrid } from '@/components/common/DataGrid'; import { useLinksQuery, useNavigation } from '@/components/hooks'; import { LinksTable } from './LinksTable'; -export function LinksDataTable() { +export function LinksDataTable({ showActions = false }: { showActions?: boolean }) { const { teamId } = useNavigation(); const query = useLinksQuery({ teamId }); return ( - {({ data }) => } + {({ data }) => } ); } diff --git a/src/app/(main)/links/LinksPage.tsx b/src/app/(main)/links/LinksPage.tsx index a6e4c7c4..cdaf8fce 100644 --- a/src/app/(main)/links/LinksPage.tsx +++ b/src/app/(main)/links/LinksPage.tsx @@ -4,21 +4,30 @@ import { LinksDataTable } from '@/app/(main)/links/LinksDataTable'; import { PageBody } from '@/components/common/PageBody'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; -import { useMessages, useNavigation } from '@/components/hooks'; +import { useLoginQuery, useMessages, useNavigation, useTeamMembersQuery } from '@/components/hooks'; +import { ROLES } from '@/lib/constants'; import { LinkAddButton } from './LinkAddButton'; export function LinksPage() { + const { user } = useLoginQuery(); const { formatMessage, labels } = useMessages(); const { teamId } = useNavigation(); + const { data } = useTeamMembersQuery(teamId); + + const showActions = + (teamId && + data?.data.filter(team => team.userId === user.id && team.role !== ROLES.teamViewOnly) + .length > 0) || + (!teamId && user.role !== ROLES.viewOnly); return ( - + {showActions && } - + diff --git a/src/app/(main)/links/LinksTable.tsx b/src/app/(main)/links/LinksTable.tsx index a3b4a86a..62eb0fb8 100644 --- a/src/app/(main)/links/LinksTable.tsx +++ b/src/app/(main)/links/LinksTable.tsx @@ -6,7 +6,11 @@ import { useMessages, useNavigation, useSlug } from '@/components/hooks'; import { LinkDeleteButton } from './LinkDeleteButton'; import { LinkEditButton } from './LinkEditButton'; -export function LinksTable(props: DataTableProps) { +export interface LinksTableProps extends DataTableProps { + showActions?: boolean; +} + +export function LinksTable({ showActions, ...props }: LinksTableProps) { const { formatMessage, labels } = useMessages(); const { websiteId, renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('link'); @@ -36,16 +40,18 @@ export function LinksTable(props: DataTableProps) { {(row: any) => } - - {({ id, name }: any) => { - return ( - - - - - ); - }} - + {showActions && ( + + {({ id, name }: any) => { + return ( + + + + + ); + }} + + )} ); } diff --git a/src/app/(main)/pixels/PixelsDataTable.tsx b/src/app/(main)/pixels/PixelsDataTable.tsx index 51b8c5a0..6a9a9162 100644 --- a/src/app/(main)/pixels/PixelsDataTable.tsx +++ b/src/app/(main)/pixels/PixelsDataTable.tsx @@ -2,13 +2,13 @@ import { DataGrid } from '@/components/common/DataGrid'; import { useNavigation, usePixelsQuery } from '@/components/hooks'; import { PixelsTable } from './PixelsTable'; -export function PixelsDataTable() { +export function PixelsDataTable({ showActions = false }: { showActions?: boolean }) { const { teamId } = useNavigation(); const query = usePixelsQuery({ teamId }); return ( - {({ data }) => } + {({ data }) => } ); } diff --git a/src/app/(main)/pixels/PixelsPage.tsx b/src/app/(main)/pixels/PixelsPage.tsx index 4f6acefe..91ddcdcd 100644 --- a/src/app/(main)/pixels/PixelsPage.tsx +++ b/src/app/(main)/pixels/PixelsPage.tsx @@ -3,22 +3,31 @@ import { Column } from '@umami/react-zen'; import { PageBody } from '@/components/common/PageBody'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; -import { useMessages, useNavigation } from '@/components/hooks'; +import { useLoginQuery, useMessages, useNavigation, useTeamMembersQuery } from '@/components/hooks'; +import { ROLES } from '@/lib/constants'; import { PixelAddButton } from './PixelAddButton'; import { PixelsDataTable } from './PixelsDataTable'; export function PixelsPage() { + const { user } = useLoginQuery(); const { formatMessage, labels } = useMessages(); const { teamId } = useNavigation(); + const { data } = useTeamMembersQuery(teamId); + + const showActions = + (teamId && + data?.data.filter(team => team.userId === user.id && team.role !== ROLES.teamViewOnly) + .length > 0) || + (!teamId && user.role !== ROLES.viewOnly); return ( - + {showActions && } - + diff --git a/src/app/(main)/pixels/PixelsTable.tsx b/src/app/(main)/pixels/PixelsTable.tsx index 48a84589..57aff4db 100644 --- a/src/app/(main)/pixels/PixelsTable.tsx +++ b/src/app/(main)/pixels/PixelsTable.tsx @@ -6,10 +6,15 @@ import { useMessages, useNavigation, useSlug } from '@/components/hooks'; import { PixelDeleteButton } from './PixelDeleteButton'; import { PixelEditButton } from './PixelEditButton'; -export function PixelsTable(props: DataTableProps) { +export interface PixelsTableProps extends DataTableProps { + showActions?: boolean; +} + +export function PixelsTable({ showActions, ...props }: PixelsTableProps) { const { formatMessage, labels } = useMessages(); const { renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('pixel'); + console.log(showActions); return ( @@ -31,18 +36,20 @@ export function PixelsTable(props: DataTableProps) { {(row: any) => } - - {(row: any) => { - const { id, name } = row; + {showActions && ( + + {(row: any) => { + const { id, name } = row; - return ( - - - - - ); - }} - + return ( + + + + + ); + }} + + )} ); } diff --git a/src/app/(main)/websites/WebsitesPage.tsx b/src/app/(main)/websites/WebsitesPage.tsx index 31de7047..6f3548a9 100644 --- a/src/app/(main)/websites/WebsitesPage.tsx +++ b/src/app/(main)/websites/WebsitesPage.tsx @@ -3,22 +3,31 @@ import { Column } from '@umami/react-zen'; import { PageBody } from '@/components/common/PageBody'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; -import { useMessages, useNavigation } from '@/components/hooks'; +import { useLoginQuery, useMessages, useNavigation, useTeamMembersQuery } from '@/components/hooks'; +import { ROLES } from '@/lib/constants'; import { WebsiteAddButton } from './WebsiteAddButton'; import { WebsitesDataTable } from './WebsitesDataTable'; export function WebsitesPage() { + const { user } = useLoginQuery(); const { teamId } = useNavigation(); const { formatMessage, labels } = useMessages(); + const { data } = useTeamMembersQuery(teamId); + + const showActions = + (teamId && + data?.data.filter(team => team.userId === user.id && team.role !== ROLES.teamViewOnly) + .length > 0) || + (!teamId && user.role !== ROLES.viewOnly); return ( - + {showActions && } - + From f9f9125532a8c8bdd3bbef188643724ff7953780 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 23:35:19 -0800 Subject: [PATCH 07/11] Fix user save bug with password field not clearing --- src/app/(main)/admin/users/[userId]/UserEditForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/(main)/admin/users/[userId]/UserEditForm.tsx b/src/app/(main)/admin/users/[userId]/UserEditForm.tsx index 28bf030f..68aa7f6e 100644 --- a/src/app/(main)/admin/users/[userId]/UserEditForm.tsx +++ b/src/app/(main)/admin/users/[userId]/UserEditForm.tsx @@ -30,7 +30,11 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () = }; return ( -
+ From a37de757a098ed7a64525246216b0f30b2c8139c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 26 Jan 2026 23:37:13 -0800 Subject: [PATCH 08/11] fix EventsPage import errors --- src/app/(main)/websites/[websiteId]/events/EventsPage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx index f209705e..f62d8a4c 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx @@ -1,14 +1,16 @@ 'use client'; import { Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen'; import locale from 'date-fns/locale/af'; -import { LoadingPanel, MetricCard, MetricsBar } from 'dist'; import { type Key, useMemo, useState } from 'react'; import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; import { Panel } from '@/components/common/Panel'; import { useMessages } from '@/components/hooks'; import { useEventStatsQuery } from '@/components/hooks/queries/useEventStatsQuery'; import { EventsChart } from '@/components/metrics/EventsChart'; +import { MetricCard } from '@/components/metrics/MetricCard'; +import { MetricsBar } from '@/components/metrics/MetricsBar'; import { MetricsTable } from '@/components/metrics/MetricsTable'; import { formatLongNumber } from '@/lib/format'; import { getItem, setItem } from '@/lib/storage'; From a0886c05941e1873960585a22ce2d5d1b1a5c37c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 27 Jan 2026 10:03:44 -0800 Subject: [PATCH 09/11] Correctly pass in timezone into relational query. Closes #3975 --- src/queries/sql/getWeeklyTraffic.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/queries/sql/getWeeklyTraffic.ts b/src/queries/sql/getWeeklyTraffic.ts index 7bbe78a7..1868b922 100644 --- a/src/queries/sql/getWeeklyTraffic.ts +++ b/src/queries/sql/getWeeklyTraffic.ts @@ -14,7 +14,7 @@ export async function getWeeklyTraffic(...args: [websiteId: string, filters: Que } async function relationalQuery(websiteId: string, filters: QueryFilters) { - const timezone = 'utc'; + const { timezone = 'utc' } = filters; const { rawQuery, getDateWeeklySQL, parseFilters } = prisma; const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({ ...filters, @@ -33,7 +33,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { and website_event.created_at between {{startDate}} and {{endDate}} ${filterQuery} group by time - order by 2 + order by 1 `, queryParams, FUNCTION_NAME, From 67cdfdfb7e7ee3fea9a2699155df8dfbd826f724 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 27 Jan 2026 10:51:49 -0800 Subject: [PATCH 10/11] remove console.log from pixelstable --- src/app/(main)/pixels/PixelsTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/(main)/pixels/PixelsTable.tsx b/src/app/(main)/pixels/PixelsTable.tsx index 57aff4db..018b40eb 100644 --- a/src/app/(main)/pixels/PixelsTable.tsx +++ b/src/app/(main)/pixels/PixelsTable.tsx @@ -14,7 +14,6 @@ export function PixelsTable({ showActions, ...props }: PixelsTableProps) { const { formatMessage, labels } = useMessages(); const { renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('pixel'); - console.log(showActions); return ( From dde1c3a57acbf4303324ddce40c0563056ac1be1 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 27 Jan 2026 17:07:14 -0800 Subject: [PATCH 11/11] Increase website select pageSize to 20. Closes #3913 --- src/components/input/WebsiteSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 04c773a7..330f826a 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -26,7 +26,7 @@ export function WebsiteSelect({ const { user } = useLoginQuery(); const { data, isLoading } = useUserWebsitesQuery( { userId: user?.id, teamId }, - { search, pageSize: 10, includeTeams }, + { search, pageSize: 20, includeTeams }, ); const listItems: { id: string; name: string }[] = data?.data || [];