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,
|
||||
};
|
||||
|
||||
const handleSubmit = values => {
|
||||
const handleSubmit = (values: any) => {
|
||||
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 EventDataTable from './EventDataTable';
|
||||
import EventsTable from './EventsTable';
|
||||
import EventDataValueTable from './EventDataValueTable';
|
||||
import { EventDataMetricsBar } from './EventDataMetricsBar';
|
||||
import { useDateRange, useApi, useNavigation } from 'components/hooks';
|
||||
@ -33,7 +33,7 @@ export default function WebsiteEventData({ websiteId }) {
|
||||
return (
|
||||
<Flexbox className={styles.container} direction="column" gap={20}>
|
||||
<EventDataMetricsBar websiteId={websiteId} />
|
||||
{!event && <EventDataTable data={data} />}
|
||||
{!event && <EventsTable data={data} />}
|
||||
{isLoading && <Loading position="page" />}
|
||||
{event && data && <EventDataValueTable event={event} data={data} />}
|
||||
</Flexbox>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Metadata } from 'next';
|
||||
import EventDataPage from './EventDataPage';
|
||||
import EventsPage from './EventsPage';
|
||||
|
||||
export default async function ({ params: { websiteId } }) {
|
||||
return <EventDataPage websiteId={websiteId} />;
|
||||
return <EventsPage websiteId={websiteId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
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 Pager from 'components/common/Pager';
|
||||
import { FilterQueryResult } from 'lib/types';
|
||||
@ -35,6 +35,7 @@ export function DataTable({
|
||||
const { query } = params || {};
|
||||
const hasData = Boolean(!isLoading && data?.length);
|
||||
const noResults = Boolean(!isLoading && query && !hasData);
|
||||
const { router, renderUrl } = useNavigation();
|
||||
|
||||
const handleSearch = (query: string) => {
|
||||
setParams({ ...params, query, page: params.page ? page : 1 });
|
||||
@ -42,6 +43,7 @@ export function DataTable({
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setParams({ ...params, query, page });
|
||||
router.push(renderUrl({ page }));
|
||||
};
|
||||
|
||||
if (error) {
|
||||
|
@ -2,15 +2,17 @@ import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { useApi } from './useApi';
|
||||
import { PageResult, PageParams, FilterQueryResult } from 'lib/types';
|
||||
import { useNavigation } from '../useNavigation';
|
||||
|
||||
export function useFilterQuery<T = any>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
...options
|
||||
}: 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: '',
|
||||
page: 1,
|
||||
page: +queryParams.page || 1,
|
||||
});
|
||||
|
||||
const { useQuery } = useApi();
|
||||
@ -21,7 +23,7 @@ export function useFilterQuery<T = any>({
|
||||
});
|
||||
|
||||
return {
|
||||
result: data as PageResult<any>,
|
||||
result: data as PageResult<T>,
|
||||
query,
|
||||
params,
|
||||
setParams,
|
||||
|
@ -1,17 +1,19 @@
|
||||
import useApi from './useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { useFilterQuery } from 'components/hooks';
|
||||
|
||||
export function useWebsiteEvents(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { get } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
return useFilterQuery({
|
||||
queryKey: ['websites:events', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/events`, params),
|
||||
queryFn: pageParams =>
|
||||
get(`/websites/${websiteId}/events`, { ...params, ...pageParams, pageSize: 20 }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
@ -130,6 +130,7 @@ export const labels = defineMessages({
|
||||
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
|
||||
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
|
||||
all: { id: 'label.all', defaultMessage: 'All' },
|
||||
session: { id: 'label.session', defaultMessage: 'Session' },
|
||||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
||||
activityLog: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
||||
@ -275,6 +276,7 @@ export const labels = defineMessages({
|
||||
lastSeen: { id: 'label.last-seen', defaultMessage: 'Last seen' },
|
||||
firstSeen: { id: 'label.first-seen', defaultMessage: 'First seen' },
|
||||
properties: { id: 'label.properties', defaultMessage: 'Properties' },
|
||||
path: { id: 'label.path', defaultMessage: 'Path' },
|
||||
});
|
||||
|
||||
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 { PageParams, QueryFilters } from 'lib/types';
|
||||
|
||||
export function getEvents(
|
||||
export function getWebsiteEvents(
|
||||
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
|
||||
) {
|
||||
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;
|
||||
|
||||
@ -19,7 +24,7 @@ export async function getRealtimeData(
|
||||
const { startDate, timezone } = criteria;
|
||||
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
||||
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 }),
|
||||
getPageviewStats(websiteId, filters),
|
||||
getSessionStats(websiteId, filters),
|
||||
|
@ -8,7 +8,7 @@ export * from './analytics/events/getEventDataFields';
|
||||
export * from './analytics/events/getEventDataStats';
|
||||
export * from './analytics/events/getEventDataUsage';
|
||||
export * from './analytics/events/getEventMetrics';
|
||||
export * from './analytics/events/getEvents';
|
||||
export * from './analytics/events/getWebsiteEvents';
|
||||
export * from './analytics/events/getEventUsage';
|
||||
export * from './analytics/events/saveEvent';
|
||||
export * from './analytics/reports/getFunnel';
|
||||
|
Loading…
Reference in New Issue
Block a user