{columns.map(({ visitorCount, nodes }, columnIndex) => {
diff --git a/src/app/(main)/websites/[websiteId]/reports/retention/Retention.tsx b/src/app/(main)/websites/[websiteId]/reports/retention/Retention.tsx
index 3031eee1..92b681d7 100644
--- a/src/app/(main)/websites/[websiteId]/reports/retention/Retention.tsx
+++ b/src/app/(main)/websites/[websiteId]/reports/retention/Retention.tsx
@@ -1,6 +1,5 @@
import { ReactNode } from 'react';
-import { Grid, Row, Column, Text, Loading, Icon } from '@umami/react-zen';
-import { Empty } from '@/components/common/Empty';
+import { Grid, Row, Column, Text, Icon } from '@umami/react-zen';
import { Users } from '@/components/icons';
import { useMessages, useLocale, useResultQuery } from '@/components/hooks';
import { formatDate } from '@/lib/date';
@@ -28,14 +27,6 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
},
});
- if (isLoading) {
- return
;
- }
-
- if (!data) {
- return
;
- }
-
const rows = data.reduce((arr: any[], row: { date: any; visitors: any; day: any }) => {
const { date, visitors, day } = row;
if (day === 0) {
@@ -44,7 +35,9 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
visitors,
records: days
.reduce((arr, day) => {
- arr[day] = data.find(x => x.date === date && x.day === day);
+ arr[day] = data.find(
+ (x: { date: any; day: number }) => x.date === date && x.day === day,
+ );
return arr;
}, [])
.filter(n => n),
@@ -56,7 +49,7 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
const totalDays = rows.length;
return (
-
+
(
@@ -52,7 +51,7 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
[countryNames, locale],
);
- const chartData = useMemo(() => {
+ const chartData: any = useMemo(() => {
if (!data) return [];
const map = (data.chart as any[]).reduce((obj, { x, t, y }) => {
@@ -114,9 +113,9 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
-
+
-
+
{metrics?.map(({ label, value, formatValue }) => {
return (
@@ -125,13 +124,12 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
diff --git a/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx b/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx
index 02d0b100..fa9383e1 100644
--- a/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx
+++ b/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx
@@ -23,10 +23,9 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
endDate,
},
});
- const isEmpty = !Object.keys(data || {})?.length;
return (
-
+
{UTM_PARAMS.map(param => {
const items = toArray(data?.[param]);
@@ -61,7 +60,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
/>
-
+
diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsMetricsBar.tsx
index b890bbd5..1da45000 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/SessionsMetricsBar.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsMetricsBar.tsx
@@ -3,33 +3,36 @@ import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsi
import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar';
import { formatLongNumber } from '@/lib/format';
+import { LoadingPanel } from '@/components/common/LoadingPanel';
export function SessionsMetricsBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
- const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
+ const { data, isLoading, isFetching, error } = useWebsiteSessionStatsQuery(websiteId);
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
}
diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx
index dba6806e..78c5ccdc 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx
@@ -36,65 +36,70 @@ export function SessionsWeekly({ websiteId }: { websiteId: string }) {
: [];
return (
-
+
-
-
- {Array(24)
- .fill(null)
- .map((_, i) => {
- const label = format(addHours(startOfDay(new Date()), i), 'p', { locale: dateLocale })
- .replace(/\D00 ?/, '')
- .toLowerCase();
- return (
-
-
- {label}
-
-
- );
- })}
-
- {data &&
- daysOfWeek.map((index: number) => {
- const day = data[index];
- return (
-
-
-
- {format(getDayOfWeekAsDate(index), 'EEE', { locale: dateLocale })}
-
-
- {day?.map((count: number, j) => {
- const pct = count / max;
+ {data && (
+ <>
+
+
+ {Array(24)
+ .fill(null)
+ .map((_, i) => {
+ const label = format(addHours(startOfDay(new Date()), i), 'p', {
+ locale: dateLocale,
+ })
+ .replace(/\D00 ?/, '')
+ .toLowerCase();
return (
-
-
-
-
-
-
- {`${formatMessage(
- labels.visitors,
- )}: ${count}`}
-
+
+
+ {label}
+
+
);
})}
-
- );
- })}
+
+ {daysOfWeek.map((index: number) => {
+ const day = data[index];
+ return (
+
+
+
+ {format(getDayOfWeekAsDate(index), 'EEE', { locale: dateLocale })}
+
+
+ {day?.map((count: number, j) => {
+ const pct = count / max;
+ return (
+
+
+
+
+
+
+ {`${formatMessage(
+ labels.visitors,
+ )}: ${count}`}
+
+ );
+ })}
+
+ );
+ })}
+ >
+ )}
);
diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx
index dab2c87b..a5fc93aa 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx
@@ -1,8 +1,20 @@
import { isSameDay } from 'date-fns';
-import { Icon, StatusLight, Column, Row, Heading, Text, Button } from '@umami/react-zen';
+import {
+ Icon,
+ StatusLight,
+ Column,
+ Row,
+ Heading,
+ Text,
+ Button,
+ DialogTrigger,
+ Popover,
+ Dialog,
+} from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Bolt, Eye, FileText } from '@/components/icons';
import { useSessionActivityQuery, useTimezone } from '@/components/hooks';
+import { EventData } from '@/components/metrics/EventData';
export function SessionActivity({
websiteId,
@@ -25,7 +37,7 @@ export function SessionActivity({
let lastDay = null;
return (
-
+
{data?.map(({ eventId, createdAt, urlPath, eventName, visitId, hasData }) => {
const showHeader = !lastDay || !isSameDay(new Date(lastDay), new Date(createdAt));
@@ -41,15 +53,7 @@ export function SessionActivity({
{eventName ? : }
{eventName || urlPath}
- {hasData > 0 && (
-
- )}
+ {hasData > 0 && }
@@ -59,3 +63,22 @@ export function SessionActivity({
);
}
+
+const PropertiesButton = props => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionData.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionData.tsx
index 70e1a171..849e0b7d 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionData.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionData.tsx
@@ -6,10 +6,9 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
export function SessionData({ websiteId, sessionId }: { websiteId: string; sessionId: string }) {
const { data, isLoading, error } = useSessionDataQuery(websiteId, sessionId);
- const isEmpty = !data?.length;
return (
-
+
{!data?.length && }
{data?.map(({ dataKey, dataType, stringValue }) => {
diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx
index 27e05860..7c00903a 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx
@@ -20,7 +20,7 @@ export function SessionDetailsPage({
const { formatMessage, labels } = useMessages();
return (
-
+
@@ -28,7 +28,6 @@ export function SessionDetailsPage({
-
diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionStats.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionStats.tsx
index 5aa3716d..e25be9ad 100644
--- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionStats.tsx
+++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionStats.tsx
@@ -7,7 +7,7 @@ export function SessionStats({ data }) {
const { formatMessage, labels } = useMessages();
return (
-
+
diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx
index 8d5141de..77be5201 100644
--- a/src/app/Providers.tsx
+++ b/src/app/Providers.tsx
@@ -13,6 +13,7 @@ const client = new QueryClient({
queries: {
retry: false,
refetchOnWindowFocus: false,
+ staleTime: 1000 * 60,
},
},
});
diff --git a/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts
new file mode 100644
index 00000000..8eeb7171
--- /dev/null
+++ b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts
@@ -0,0 +1,25 @@
+import { parseRequest } from '@/lib/request';
+import { unauthorized, json } from '@/lib/response';
+import { canViewWebsite } from '@/lib/auth';
+import { getEventData } from '@/queries/sql/events/getEventData';
+
+export async function GET(
+ request: Request,
+ { params }: { params: Promise<{ websiteId: string; eventId: string }> },
+) {
+ const { auth, error } = await parseRequest(request);
+
+ if (error) {
+ return error();
+ }
+
+ const { websiteId, eventId } = await params;
+
+ if (!(await canViewWebsite(auth, websiteId))) {
+ return unauthorized();
+ }
+
+ const data = await getEventData(eventId);
+
+ return json(data);
+}
diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts
index c146271f..70c0110e 100644
--- a/src/app/api/websites/[websiteId]/stats/route.ts
+++ b/src/app/api/websites/[websiteId]/stats/route.ts
@@ -45,19 +45,11 @@ export async function GET(
endDate,
});
- const prevPeriod = await getWebsiteStats(websiteId, {
+ const previous = await getWebsiteStats(websiteId, {
...filters,
startDate: compareStartDate,
endDate: compareEndDate,
});
- const stats = Object.keys(metrics[0]).reduce((obj, key) => {
- obj[key] = {
- value: Number(metrics[0][key]) || 0,
- prev: Number(prevPeriod[0][key]) || 0,
- };
- return obj;
- }, {});
-
- return json(stats);
+ return json({ ...metrics, previous });
}
diff --git a/src/app/sso/SSOPage.tsx b/src/app/sso/SSOPage.tsx
index 4d737f94..b8893a6c 100644
--- a/src/app/sso/SSOPage.tsx
+++ b/src/app/sso/SSOPage.tsx
@@ -18,5 +18,5 @@ export function SSOPage() {
}
}, [router, url, token]);
- return ;
+ return ;
}
diff --git a/src/components/common/DataGrid.tsx b/src/components/common/DataGrid.tsx
index def56509..1a70efef 100644
--- a/src/components/common/DataGrid.tsx
+++ b/src/components/common/DataGrid.tsx
@@ -1,7 +1,6 @@
import { ReactNode } from 'react';
-import { Loading, SearchField, Row, Column } from '@umami/react-zen';
+import { SearchField, Row, Column } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks';
-import { Empty } from '@/components/common/Empty';
import { Pager } from '@/components/common/Pager';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { PagedQueryResult } from '@/lib/types';
@@ -24,16 +23,14 @@ export function DataGrid({
allowSearch = true,
allowPaging = true,
autoFocus,
- renderEmpty,
children,
}: DataTableProps) {
- const { formatMessage, labels, messages } = useMessages();
+ const { formatMessage, labels } = useMessages();
const { result, params, setParams, query } = queryResult || {};
- const { error, isLoading, isFetched } = query || {};
+ const { error, isLoading, isFetching } = query || {};
const { page, pageSize, count, data } = result || {};
const { search } = params || {};
const hasData = Boolean(!isLoading && data?.length);
- const noResults = Boolean(search && !hasData);
const { router, renderUrl } = useNavigation();
const handleSearch = (search: string) => {
@@ -46,7 +43,7 @@ export function DataGrid({
};
return (
-
+
{allowSearch && (hasData || search) && (
)}
-
+
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
- {isLoading && }
- {!isLoading && !hasData && !search && (renderEmpty ? renderEmpty() : )}
- {!isLoading && noResults && }
{allowPaging && hasData && (
diff --git a/src/components/common/LoadingPanel.tsx b/src/components/common/LoadingPanel.tsx
index 4f5be375..fd5561a2 100644
--- a/src/components/common/LoadingPanel.tsx
+++ b/src/components/common/LoadingPanel.tsx
@@ -1,32 +1,59 @@
import { ReactNode } from 'react';
-import { Spinner, Dots, Column, type ColumnProps } from '@umami/react-zen';
+import { Loading, Column, type ColumnProps } from '@umami/react-zen';
import { ErrorMessage } from '@/components/common/ErrorMessage';
import { Empty } from '@/components/common/Empty';
+export interface LoadingPanelProps extends ColumnProps {
+ data?: any;
+ error?: Error;
+ isEmpty?: boolean;
+ isLoading?: boolean;
+ isFetching?: boolean;
+ loadingIcon?: 'dots' | 'spinner';
+ renderEmpty?: () => ReactNode;
+ children: ReactNode;
+}
+
export function LoadingPanel({
+ data,
error,
isEmpty,
- isFetched,
isLoading,
+ isFetching,
loadingIcon = 'dots',
renderEmpty = () => ,
children,
...props
-}: {
- error?: Error;
- isEmpty?: boolean;
- isFetched?: boolean;
- isLoading?: boolean;
- loadingIcon?: 'dots' | 'spinner';
- renderEmpty?: () => ReactNode;
- children: ReactNode;
-} & ColumnProps) {
+}: LoadingPanelProps) {
+ const empty = isEmpty ?? checkEmpty(data);
+
return (
-
- {isLoading && !isFetched && (loadingIcon === 'dots' ? : )}
+
+ {/* Show loading spinner only if no data exists */}
+ {(isLoading || isFetching) && !data && }
+
+ {/* Show error */}
{error && }
- {!error && !isLoading && isEmpty && renderEmpty()}
- {!error && !isLoading && !isEmpty && children}
+
+ {/* Show empty state (once loaded) */}
+ {!error && !isLoading && !isFetching && empty && renderEmpty()}
+
+ {/* Show main content when data exists */}
+ {!error && !empty && children}
);
}
+
+function checkEmpty(data: any) {
+ if (!data) return false;
+
+ if (Array.isArray(data)) {
+ return data.length <= 0;
+ }
+
+ if (typeof data === 'object') {
+ return Object.keys(data).length <= 0;
+ }
+
+ return !!data;
+}
diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts
index b815a28e..28a55345 100644
--- a/src/components/hooks/index.ts
+++ b/src/components/hooks/index.ts
@@ -1,5 +1,6 @@
'use client';
export * from './queries/useActiveUsersQuery';
+export * from './queries/useEventDataQuery';
export * from './queries/useEventDataEventsQuery';
export * from './queries/useEventDataPropertiesQuery';
export * from './queries/useEventDataValuesQuery';
@@ -22,7 +23,6 @@ export * from './queries/useTeamWebsitesQuery';
export * from './queries/useTeamMembersQuery';
export * from './queries/useUserQuery';
export * from './queries/useUsersQuery';
-export * from './queries/useUTMQuery';
export * from './queries/useWebsiteQuery';
export * from './queries/useWebsites';
export * from './queries/useWebsiteEventsQuery';
diff --git a/src/components/hooks/queries/useActiveUsersQuery.ts b/src/components/hooks/queries/useActiveUsersQuery.ts
index 3b4b025d..9335b75a 100644
--- a/src/components/hooks/queries/useActiveUsersQuery.ts
+++ b/src/components/hooks/queries/useActiveUsersQuery.ts
@@ -1,10 +1,7 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
+import { ReactQueryOptions } from '@/lib/types';
-export function useActyiveUsersQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useActyiveUsersQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
return useQuery({
queryKey: ['websites:active', websiteId],
diff --git a/src/components/hooks/queries/useEventDataEventsQuery.ts b/src/components/hooks/queries/useEventDataEventsQuery.ts
index b03906b5..73427b1d 100644
--- a/src/components/hooks/queries/useEventDataEventsQuery.ts
+++ b/src/components/hooks/queries/useEventDataEventsQuery.ts
@@ -1,11 +1,8 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
-export function useEventDataEventsQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useEventDataPropertiesQuery.ts b/src/components/hooks/queries/useEventDataPropertiesQuery.ts
index 97f422eb..0e6735b6 100644
--- a/src/components/hooks/queries/useEventDataPropertiesQuery.ts
+++ b/src/components/hooks/queries/useEventDataPropertiesQuery.ts
@@ -1,11 +1,8 @@
-import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
-export function useEventDataPropertiesQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useEventDataQuery.ts b/src/components/hooks/queries/useEventDataQuery.ts
new file mode 100644
index 00000000..0901cdd1
--- /dev/null
+++ b/src/components/hooks/queries/useEventDataQuery.ts
@@ -0,0 +1,19 @@
+import { useApi } from '../useApi';
+import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
+
+export function useEventDataQuery(
+ websiteId: string,
+ eventId: string,
+ options?: ReactQueryOptions,
+) {
+ const { get, useQuery } = useApi();
+ const params = useFilterParams(websiteId);
+
+ return useQuery({
+ queryKey: ['websites:event-data', { websiteId, eventId, ...params }],
+ queryFn: () => get(`/websites/${websiteId}/event-data/${eventId}`, { ...params }),
+ enabled: !!(websiteId && eventId),
+ ...options,
+ });
+}
diff --git a/src/components/hooks/queries/useEventDataValuesQuery.ts b/src/components/hooks/queries/useEventDataValuesQuery.ts
index cc9e55b5..6871214e 100644
--- a/src/components/hooks/queries/useEventDataValuesQuery.ts
+++ b/src/components/hooks/queries/useEventDataValuesQuery.ts
@@ -1,12 +1,12 @@
-import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
export function useEventDataValuesQuery(
websiteId: string,
eventName: string,
propertyName: string,
- options?: Omit,
+ options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useLoginQuery.ts b/src/components/hooks/queries/useLoginQuery.ts
index 411e0d51..23949c0d 100644
--- a/src/components/hooks/queries/useLoginQuery.ts
+++ b/src/components/hooks/queries/useLoginQuery.ts
@@ -1,4 +1,3 @@
-import { UseQueryResult } from '@tanstack/react-query';
import { useApp, setUser } from '@/store/app';
import { useApi } from '../useApi';
@@ -7,7 +6,7 @@ const selector = (state: { user: any }) => state.user;
export function useLoginQuery(): {
user: any;
setUser: (data: any) => void;
-} & UseQueryResult {
+} {
const { post, useQuery } = useApi();
const user = useApp(selector);
diff --git a/src/components/hooks/queries/useReportsQuery.ts b/src/components/hooks/queries/useReportsQuery.ts
index 002f6f9b..8c05794f 100644
--- a/src/components/hooks/queries/useReportsQuery.ts
+++ b/src/components/hooks/queries/useReportsQuery.ts
@@ -1,8 +1,12 @@
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
+import { ReactQueryOptions } from '@/lib/types';
-export function useReportsQuery({ websiteId, type }: { websiteId: string; type?: string }) {
+export function useReportsQuery(
+ { websiteId, type }: { websiteId: string; type?: string },
+ options?: ReactQueryOptions,
+) {
const { modified } = useModified(`reports:${type}`);
const { get } = useApi();
@@ -10,5 +14,6 @@ export function useReportsQuery({ websiteId, type }: { websiteId: string; type?:
queryKey: ['reports', { websiteId, type, modified }],
queryFn: async () => get('/reports', { websiteId, type }),
enabled: !!websiteId && !!type,
+ ...options,
});
}
diff --git a/src/components/hooks/queries/useResultQuery.ts b/src/components/hooks/queries/useResultQuery.ts
index 3ca6e23a..be84193d 100644
--- a/src/components/hooks/queries/useResultQuery.ts
+++ b/src/components/hooks/queries/useResultQuery.ts
@@ -1,10 +1,10 @@
import { useApi } from '@/components/hooks';
-import { UseQueryOptions, QueryKey } from '@tanstack/react-query';
+import { ReactQueryOptions } from '@/lib/types';
export function useResultQuery(
type: string,
params?: { [key: string]: any },
- options?: Omit, 'queryKey' | 'queryFn'>,
+ options?: ReactQueryOptions,
) {
const { post, useQuery } = useApi();
diff --git a/src/components/hooks/queries/useSessionDataPropertiesQuery.ts b/src/components/hooks/queries/useSessionDataPropertiesQuery.ts
index b694cf37..470f8a09 100644
--- a/src/components/hooks/queries/useSessionDataPropertiesQuery.ts
+++ b/src/components/hooks/queries/useSessionDataPropertiesQuery.ts
@@ -1,11 +1,8 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
-export function useSessionDataPropertiesQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useSessionDataValuesQuery.ts b/src/components/hooks/queries/useSessionDataValuesQuery.ts
index 8a75b1a3..e9b846e8 100644
--- a/src/components/hooks/queries/useSessionDataValuesQuery.ts
+++ b/src/components/hooks/queries/useSessionDataValuesQuery.ts
@@ -1,11 +1,11 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
export function useSessionDataValuesQuery(
websiteId: string,
propertyName: string,
- options?: Omit,
+ options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useUTMQuery.ts b/src/components/hooks/queries/useUTMQuery.ts
deleted file mode 100644
index f89200a7..00000000
--- a/src/components/hooks/queries/useUTMQuery.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useApi } from '../useApi';
-import { useFilterParams } from '../useFilterParams';
-import { UseQueryOptions } from '@tanstack/react-query';
-
-export function useUTMQuery(
- websiteId: string,
- queryParams?: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
- options?: Omit void }, 'queryKey' | 'queryFn'>,
-) {
- const { get, useQuery } = useApi();
- const filterParams = useFilterParams(websiteId);
-
- return useQuery({
- queryKey: ['utm', websiteId, { ...filterParams, ...queryParams }],
- queryFn: () =>
- get(`/websites/${websiteId}/utm`, { websiteId, ...filterParams, ...queryParams }),
- enabled: !!websiteId,
- ...options,
- });
-}
diff --git a/src/components/hooks/queries/useWebsiteEventsQuery.ts b/src/components/hooks/queries/useWebsiteEventsQuery.ts
index c36db405..8699ab31 100644
--- a/src/components/hooks/queries/useWebsiteEventsQuery.ts
+++ b/src/components/hooks/queries/useWebsiteEventsQuery.ts
@@ -1,12 +1,9 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
import { usePagedQuery } from '../usePagedQuery';
+import { ReactQueryOptions } from '@/lib/types';
-export function useWebsiteEventsQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions) {
const { get } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useWebsiteEventsSeriesQuery.ts b/src/components/hooks/queries/useWebsiteEventsSeriesQuery.ts
index 6e0267f4..c2b2a1e2 100644
--- a/src/components/hooks/queries/useWebsiteEventsSeriesQuery.ts
+++ b/src/components/hooks/queries/useWebsiteEventsSeriesQuery.ts
@@ -1,11 +1,8 @@
import { useApi } from '../useApi';
-import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
-export function useWebsiteEventsSeriesQuery(
- websiteId: string,
- options?: Omit,
-) {
+export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
diff --git a/src/components/hooks/queries/useWebsiteMetricsQuery.ts b/src/components/hooks/queries/useWebsiteMetricsQuery.ts
index 801a7e51..89adf818 100644
--- a/src/components/hooks/queries/useWebsiteMetricsQuery.ts
+++ b/src/components/hooks/queries/useWebsiteMetricsQuery.ts
@@ -1,18 +1,24 @@
-import { UseQueryOptions } from '@tanstack/react-query';
+import { keepPreviousData } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
import { useSearchParams } from 'next/navigation';
+import { ReactQueryOptions } from '@/lib/types';
+
+export type WebsiteMetricsData = {
+ x: string;
+ y: number;
+}[];
export function useWebsiteMetricsQuery(
websiteId: string,
queryParams: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
- options?: Omit void }, 'queryKey' | 'queryFn'>,
+ options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const filterParams = useFilterParams(websiteId);
const searchParams = useSearchParams();
- return useQuery({
+ return useQuery({
queryKey: [
'websites:metrics',
{
@@ -21,18 +27,14 @@ export function useWebsiteMetricsQuery(
...queryParams,
},
],
- queryFn: async () => {
- const data = await get(`/websites/${websiteId}/metrics`, {
+ queryFn: async () =>
+ get(`/websites/${websiteId}/metrics`, {
...filterParams,
[searchParams.get('view')]: undefined,
...queryParams,
- });
-
- options?.onDataLoad?.(data);
-
- return data;
- },
+ }),
enabled: !!websiteId,
+ placeholderData: keepPreviousData,
...options,
});
}
diff --git a/src/components/hooks/queries/useWebsitePageviewsQuery.ts b/src/components/hooks/queries/useWebsitePageviewsQuery.ts
index a2e71df3..d4793052 100644
--- a/src/components/hooks/queries/useWebsitePageviewsQuery.ts
+++ b/src/components/hooks/queries/useWebsitePageviewsQuery.ts
@@ -1,18 +1,22 @@
-import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
+import { ReactQueryOptions } from '@/lib/types';
+
+export interface WebsitePageviewsData {
+ pageviews: { x: string; y: number }[];
+ sessions: { x: string; y: number }[];
+}
export function useWebsitePageviewsQuery(
- websiteId: string,
- compare?: string,
- options?: Omit,
+ { websiteId, compareMode }: { websiteId: string; compareMode?: string },
+ options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
- const params = useFilterParams(websiteId);
+ const filterParams = useFilterParams(websiteId);
- return useQuery({
- queryKey: ['websites:pageviews', { websiteId, ...params, compare }],
- queryFn: () => get(`/websites/${websiteId}/pageviews`, { ...params, compare }),
+ return useQuery({
+ queryKey: ['websites:pageviews', { websiteId, compareMode, ...filterParams }],
+ queryFn: () => get(`/websites/${websiteId}/pageviews`, { compareMode, ...filterParams }),
enabled: !!websiteId,
...options,
});
diff --git a/src/components/hooks/queries/useWebsiteStatsQuery.ts b/src/components/hooks/queries/useWebsiteStatsQuery.ts
index 5c761fce..2d5595e0 100644
--- a/src/components/hooks/queries/useWebsiteStatsQuery.ts
+++ b/src/components/hooks/queries/useWebsiteStatsQuery.ts
@@ -1,15 +1,31 @@
+import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
+export interface WebsiteStatsData {
+ pageviews: number;
+ visitors: number;
+ visits: number;
+ bounces: number;
+ totaltime: number;
+ previous: {
+ pageviews: number;
+ visitors: number;
+ visits: number;
+ bounces: number;
+ totaltime: number;
+ };
+}
+
export function useWebsiteStatsQuery(
websiteId: string,
compare?: string,
- options?: { [key: string]: string },
+ options?: UseQueryOptions,
) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
- return useQuery({
+ return useQuery({
queryKey: ['websites:stats', { websiteId, ...params, compare }],
queryFn: () => get(`/websites/${websiteId}/stats`, { ...params, compare }),
enabled: !!websiteId,
diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx
index d8ac0b08..4503048b 100644
--- a/src/components/input/DateFilter.tsx
+++ b/src/components/input/DateFilter.tsx
@@ -100,13 +100,13 @@ export function DateFilter({
};
return (
-
+
);
}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index be790395..670893a7 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
+import { UseQueryOptions } from '@tanstack/react-query';
import {
COLLECTION_TYPE,
DATA_TYPE,
@@ -208,3 +209,5 @@ export interface InputItem {
icon: any;
seperator?: boolean;
}
+
+export type ReactQueryOptions = Omit, 'queryKey' | 'queryFn'>;
diff --git a/src/queries/sql/events/getEventData.ts b/src/queries/sql/events/getEventData.ts
new file mode 100644
index 00000000..e7f27ea9
--- /dev/null
+++ b/src/queries/sql/events/getEventData.ts
@@ -0,0 +1,57 @@
+import { EventData } from '@/generated/prisma';
+import prisma from '@/lib/prisma';
+import clickhouse from '@/lib/clickhouse';
+import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
+
+export async function getEventData(...args: [eventId: string]): Promise {
+ return runQuery({
+ [PRISMA]: () => relationalQuery(...args),
+ [CLICKHOUSE]: () => clickhouseQuery(...args),
+ });
+}
+
+async function relationalQuery(eventId: string) {
+ const { rawQuery } = prisma;
+
+ return rawQuery(
+ `
+ select website_id as websiteId,
+ session_id as sessionId,
+ event_id as eventId,
+ url_path as urlPath,
+ event_name as eventName,
+ data_key as dataKey,
+ string_value as stringValue,
+ number_value as numberValue,
+ date_value as dateValue,
+ data_type as dataType,
+ created_at as createdAt
+ from event_data
+ where event_id = {{eventId::uuid}}
+ `,
+ { eventId },
+ );
+}
+
+async function clickhouseQuery(eventId: string): Promise {
+ const { rawQuery } = clickhouse;
+
+ return rawQuery(
+ `
+ select website_id as websiteId,
+ session_id as sessionId,
+ event_id as eventId,
+ url_path as urlPath,
+ event_name as eventName,
+ data_key as dataKey,
+ string_value as stringValue,
+ number_value as numberValue,
+ date_value as dateValue,
+ data_type as dataType,
+ created_at as createdAt
+ from event_data
+ where event_id = {eventId:UUID}
+ `,
+ { eventId },
+ );
+}
diff --git a/src/queries/sql/getWebsiteStats.ts b/src/queries/sql/getWebsiteStats.ts
index 80f1d578..421515c9 100644
--- a/src/queries/sql/getWebsiteStats.ts
+++ b/src/queries/sql/getWebsiteStats.ts
@@ -117,5 +117,5 @@ async function clickhouseQuery(
`;
}
- return rawQuery(sql, params);
+ return rawQuery(sql, params).then(result => result?.[0]);
}