mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-06 01:15:42 +01:00
Redo events tab to show all events.
This commit is contained in:
parent
10f65cae68
commit
9c32057841
@ -48,7 +48,7 @@ export function EventDataParameters() {
|
|||||||
groups,
|
groups,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = values => {
|
const handleSubmit = (values: any) => {
|
||||||
runReport(values);
|
runReport(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import WebsiteHeader from '../WebsiteHeader';
|
|
||||||
import WebsiteEventData from './WebsiteEventData';
|
|
||||||
|
|
||||||
export default function EventDataPage({ websiteId }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
|
||||||
<WebsiteEventData websiteId={websiteId} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { GridTable, GridColumn } from 'react-basics';
|
|
||||||
import { useMessages, useNavigation } from 'components/hooks';
|
|
||||||
import Empty from 'components/common/Empty';
|
|
||||||
import { DATA_TYPES } from 'lib/constants';
|
|
||||||
|
|
||||||
export function EventDataTable({ data = [] }) {
|
|
||||||
const { formatMessage, labels } = useMessages();
|
|
||||||
const { renderUrl } = useNavigation();
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return <Empty />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GridTable data={data}>
|
|
||||||
<GridColumn name="eventName" label={formatMessage(labels.event)}>
|
|
||||||
{row => (
|
|
||||||
<Link href={renderUrl({ event: row.eventName })} shallow={true}>
|
|
||||||
{row.eventName}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</GridColumn>
|
|
||||||
<GridColumn name="fieldName" label={formatMessage(labels.field)}>
|
|
||||||
{row => row.fieldName}
|
|
||||||
</GridColumn>
|
|
||||||
<GridColumn name="dataType" label={formatMessage(labels.type)}>
|
|
||||||
{row => DATA_TYPES[row.dataType]}
|
|
||||||
</GridColumn>
|
|
||||||
<GridColumn name="total" label={formatMessage(labels.totalRecords)}>
|
|
||||||
{({ total }) => total.toLocaleString()}
|
|
||||||
</GridColumn>
|
|
||||||
</GridTable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EventDataTable;
|
|
@ -0,0 +1,25 @@
|
|||||||
|
import { useWebsiteEvents } from 'components/hooks';
|
||||||
|
import EventsTable from './EventsTable';
|
||||||
|
import DataTable from 'components/common/DataTable';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function EventsDataTable({
|
||||||
|
websiteId,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
websiteId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}) {
|
||||||
|
const queryResult = useWebsiteEvents(websiteId);
|
||||||
|
|
||||||
|
if (queryResult?.result?.data?.length === 0) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable queryResult={queryResult} allowSearch={false}>
|
||||||
|
{({ data }) => <EventsTable data={data} />}
|
||||||
|
</DataTable>
|
||||||
|
);
|
||||||
|
}
|
12
src/app/(main)/websites/[websiteId]/events/EventsPage.tsx
Normal file
12
src/app/(main)/websites/[websiteId]/events/EventsPage.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
'use client';
|
||||||
|
import WebsiteHeader from '../WebsiteHeader';
|
||||||
|
import EventsDataTable from './EventsDataTable';
|
||||||
|
|
||||||
|
export default function EventsPage({ websiteId }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
|
<EventsDataTable websiteId={websiteId} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
43
src/app/(main)/websites/[websiteId]/events/EventsTable.tsx
Normal file
43
src/app/(main)/websites/[websiteId]/events/EventsTable.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { GridTable, GridColumn } from 'react-basics';
|
||||||
|
import { useLocale, useMessages } from 'components/hooks';
|
||||||
|
import Empty from 'components/common/Empty';
|
||||||
|
import { formatDistance } from 'date-fns';
|
||||||
|
import Profile from 'components/common/Profile';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function EventsTable({ data = [] }) {
|
||||||
|
const { dateLocale } = useLocale();
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return <Empty />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridTable data={data}>
|
||||||
|
<GridColumn name="id" label="ID" />
|
||||||
|
<GridColumn name="session" label={formatMessage(labels.session)}>
|
||||||
|
{row => (
|
||||||
|
<Link href={`/sessions/`}>
|
||||||
|
<Profile seed={row.sessionId} size={64} />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn name="eventName" label={formatMessage(labels.event)}>
|
||||||
|
{row => formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)}
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn name="eventName" label={formatMessage(labels.name)} />
|
||||||
|
<GridColumn name="urlPath" label={formatMessage(labels.path)} />
|
||||||
|
<GridColumn name="created" label={formatMessage(labels.created)}>
|
||||||
|
{row =>
|
||||||
|
formatDistance(new Date(row.createdAt), new Date(), {
|
||||||
|
addSuffix: true,
|
||||||
|
locale: dateLocale,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</GridColumn>
|
||||||
|
</GridTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EventsTable;
|
@ -1,5 +1,5 @@
|
|||||||
import { Flexbox, Loading } from 'react-basics';
|
import { Flexbox, Loading } from 'react-basics';
|
||||||
import EventDataTable from './EventDataTable';
|
import EventsTable from './EventsTable';
|
||||||
import EventDataValueTable from './EventDataValueTable';
|
import EventDataValueTable from './EventDataValueTable';
|
||||||
import { EventDataMetricsBar } from './EventDataMetricsBar';
|
import { EventDataMetricsBar } from './EventDataMetricsBar';
|
||||||
import { useDateRange, useApi, useNavigation } from 'components/hooks';
|
import { useDateRange, useApi, useNavigation } from 'components/hooks';
|
||||||
@ -33,7 +33,7 @@ export default function WebsiteEventData({ websiteId }) {
|
|||||||
return (
|
return (
|
||||||
<Flexbox className={styles.container} direction="column" gap={20}>
|
<Flexbox className={styles.container} direction="column" gap={20}>
|
||||||
<EventDataMetricsBar websiteId={websiteId} />
|
<EventDataMetricsBar websiteId={websiteId} />
|
||||||
{!event && <EventDataTable data={data} />}
|
{!event && <EventsTable data={data} />}
|
||||||
{isLoading && <Loading position="page" />}
|
{isLoading && <Loading position="page" />}
|
||||||
{event && data && <EventDataValueTable event={event} data={data} />}
|
{event && data && <EventDataValueTable event={event} data={data} />}
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import EventDataPage from './EventDataPage';
|
import EventsPage from './EventsPage';
|
||||||
|
|
||||||
export default async function ({ params: { websiteId } }) {
|
export default async function ({ params: { websiteId } }) {
|
||||||
return <EventDataPage websiteId={websiteId} />;
|
return <EventsPage websiteId={websiteId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Banner, Loading, SearchField } from 'react-basics';
|
import { Banner, Loading, SearchField } from 'react-basics';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useNavigation } from 'components/hooks';
|
||||||
import Empty from 'components/common/Empty';
|
import Empty from 'components/common/Empty';
|
||||||
import Pager from 'components/common/Pager';
|
import Pager from 'components/common/Pager';
|
||||||
import { FilterQueryResult } from 'lib/types';
|
import { FilterQueryResult } from 'lib/types';
|
||||||
@ -35,6 +35,7 @@ export function DataTable({
|
|||||||
const { query } = params || {};
|
const { query } = params || {};
|
||||||
const hasData = Boolean(!isLoading && data?.length);
|
const hasData = Boolean(!isLoading && data?.length);
|
||||||
const noResults = Boolean(!isLoading && query && !hasData);
|
const noResults = Boolean(!isLoading && query && !hasData);
|
||||||
|
const { router, renderUrl } = useNavigation();
|
||||||
|
|
||||||
const handleSearch = (query: string) => {
|
const handleSearch = (query: string) => {
|
||||||
setParams({ ...params, query, page: params.page ? page : 1 });
|
setParams({ ...params, query, page: params.page ? page : 1 });
|
||||||
@ -42,6 +43,7 @@ export function DataTable({
|
|||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
setParams({ ...params, query, page });
|
setParams({ ...params, query, page });
|
||||||
|
router.push(renderUrl({ page }));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -2,15 +2,17 @@ import { UseQueryOptions } from '@tanstack/react-query';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
import { PageResult, PageParams, FilterQueryResult } from 'lib/types';
|
import { PageResult, PageParams, FilterQueryResult } from 'lib/types';
|
||||||
|
import { useNavigation } from '../useNavigation';
|
||||||
|
|
||||||
export function useFilterQuery<T = any>({
|
export function useFilterQuery<T = any>({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn,
|
queryFn,
|
||||||
...options
|
...options
|
||||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
|
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
|
||||||
const [params, setParams] = useState<T | PageParams>({
|
const { query: queryParams } = useNavigation();
|
||||||
|
const [params, setParams] = useState<PageParams>({
|
||||||
query: '',
|
query: '',
|
||||||
page: 1,
|
page: +queryParams.page || 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { useQuery } = useApi();
|
const { useQuery } = useApi();
|
||||||
@ -21,7 +23,7 @@ export function useFilterQuery<T = any>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: data as PageResult<any>,
|
result: data as PageResult<T>,
|
||||||
query,
|
query,
|
||||||
params,
|
params,
|
||||||
setParams,
|
setParams,
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
|
import { useFilterParams } from '../useFilterParams';
|
||||||
|
import { useFilterQuery } from 'components/hooks';
|
||||||
|
|
||||||
export function useWebsiteEvents(
|
export function useWebsiteEvents(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const params = useFilterParams(websiteId);
|
||||||
|
|
||||||
return useQuery({
|
return useFilterQuery({
|
||||||
queryKey: ['websites:events', { websiteId, ...params }],
|
queryKey: ['websites:events', { websiteId, ...params }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/events`, params),
|
queryFn: pageParams =>
|
||||||
|
get(`/websites/${websiteId}/events`, { ...params, ...pageParams, pageSize: 20 }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
@ -130,6 +130,7 @@ export const labels = defineMessages({
|
|||||||
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
|
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
|
||||||
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
|
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
|
||||||
all: { id: 'label.all', defaultMessage: 'All' },
|
all: { id: 'label.all', defaultMessage: 'All' },
|
||||||
|
session: { id: 'label.session', defaultMessage: 'Session' },
|
||||||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||||
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
||||||
activityLog: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
activityLog: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
||||||
@ -275,6 +276,7 @@ export const labels = defineMessages({
|
|||||||
lastSeen: { id: 'label.last-seen', defaultMessage: 'Last seen' },
|
lastSeen: { id: 'label.last-seen', defaultMessage: 'Last seen' },
|
||||||
firstSeen: { id: 'label.first-seen', defaultMessage: 'First seen' },
|
firstSeen: { id: 'label.first-seen', defaultMessage: 'First seen' },
|
||||||
properties: { id: 'label.properties', defaultMessage: 'Properties' },
|
properties: { id: 'label.properties', defaultMessage: 'Properties' },
|
||||||
|
path: { id: 'label.path', defaultMessage: 'Path' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
42
src/pages/api/websites/[websiteId]/events/index.ts
Normal file
42
src/pages/api/websites/[websiteId]/events/index.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as yup from 'yup';
|
||||||
|
import { canViewWebsite } from 'lib/auth';
|
||||||
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
|
import { pageInfo } from 'lib/schema';
|
||||||
|
import { getWebsiteEvents } from 'queries';
|
||||||
|
|
||||||
|
export interface ReportsRequestQuery extends PageParams {
|
||||||
|
websiteId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
GET: yup.object().shape({
|
||||||
|
websiteId: yup.string().uuid().required(),
|
||||||
|
...pageInfo,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (
|
||||||
|
req: NextApiRequestQueryBody<ReportsRequestQuery, any>,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) => {
|
||||||
|
await useCors(req, res);
|
||||||
|
await useAuth(req, res);
|
||||||
|
await useValidate(schema, req, res);
|
||||||
|
|
||||||
|
const { websiteId } = req.query;
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
if (!(await canViewWebsite(req.auth, websiteId))) {
|
||||||
|
return unauthorized(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await getWebsiteEvents(websiteId, {}, req.query);
|
||||||
|
|
||||||
|
return ok(res, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodNotAllowed(res);
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
|||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { PageParams, QueryFilters } from 'lib/types';
|
import { PageParams, QueryFilters } from 'lib/types';
|
||||||
|
|
||||||
export function getEvents(
|
export function getWebsiteEvents(
|
||||||
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
|
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
|
||||||
) {
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
@ -1,4 +1,9 @@
|
|||||||
import { getWebsiteSessions, getEvents, getPageviewStats, getSessionStats } from 'queries/index';
|
import {
|
||||||
|
getWebsiteSessions,
|
||||||
|
getWebsiteEvents,
|
||||||
|
getPageviewStats,
|
||||||
|
getSessionStats,
|
||||||
|
} from 'queries/index';
|
||||||
|
|
||||||
const MAX_SIZE = 50;
|
const MAX_SIZE = 50;
|
||||||
|
|
||||||
@ -19,7 +24,7 @@ export async function getRealtimeData(
|
|||||||
const { startDate, timezone } = criteria;
|
const { startDate, timezone } = criteria;
|
||||||
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
||||||
const [events, sessions, pageviews, sessionviews] = await Promise.all([
|
const [events, sessions, pageviews, sessionviews] = await Promise.all([
|
||||||
getEvents(websiteId, { startDate, timezone }, { pageSize: 10000 }),
|
getWebsiteEvents(websiteId, { startDate, timezone }, { pageSize: 10000 }),
|
||||||
getWebsiteSessions(websiteId, { startDate, timezone }, { pageSize: 10000 }),
|
getWebsiteSessions(websiteId, { startDate, timezone }, { pageSize: 10000 }),
|
||||||
getPageviewStats(websiteId, filters),
|
getPageviewStats(websiteId, filters),
|
||||||
getSessionStats(websiteId, filters),
|
getSessionStats(websiteId, filters),
|
||||||
|
@ -8,7 +8,7 @@ export * from './analytics/events/getEventDataFields';
|
|||||||
export * from './analytics/events/getEventDataStats';
|
export * from './analytics/events/getEventDataStats';
|
||||||
export * from './analytics/events/getEventDataUsage';
|
export * from './analytics/events/getEventDataUsage';
|
||||||
export * from './analytics/events/getEventMetrics';
|
export * from './analytics/events/getEventMetrics';
|
||||||
export * from './analytics/events/getEvents';
|
export * from './analytics/events/getWebsiteEvents';
|
||||||
export * from './analytics/events/getEventUsage';
|
export * from './analytics/events/getEventUsage';
|
||||||
export * from './analytics/events/saveEvent';
|
export * from './analytics/events/saveEvent';
|
||||||
export * from './analytics/reports/getFunnel';
|
export * from './analytics/reports/getFunnel';
|
||||||
|
Loading…
Reference in New Issue
Block a user