New event data screen.

This commit is contained in:
Mike Cao 2024-08-09 01:09:54 -07:00
parent 10c438d1de
commit 8ee37d1246
137 changed files with 4918 additions and 321 deletions

View file

@ -36,16 +36,6 @@ export function WebsiteHeader({
icon: <Icons.Compare />,
path: '/compare',
},
{
label: formatMessage(labels.realtime),
icon: <Icons.Clock />,
path: '/realtime',
},
{
label: formatMessage(labels.reports),
icon: <Icons.Reports />,
path: '/reports',
},
{
label: formatMessage(labels.sessions),
icon: <Icons.User />,
@ -56,6 +46,16 @@ export function WebsiteHeader({
icon: <Lightning />,
path: '/events',
},
{
label: formatMessage(labels.realtime),
icon: <Icons.Clock />,
path: '/realtime',
},
{
label: formatMessage(labels.reports),
icon: <Icons.Reports />,
path: '/reports',
},
];
return (

View file

@ -1,26 +0,0 @@
.container {
display: grid;
grid-template-columns: 1fr 1fr;
justify-content: space-between;
align-items: center;
padding: 10px 0;
min-height: 90px;
margin-bottom: 20px;
background: var(--base50);
z-index: var(--z-index-above);
}
.actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex: 1;
}
@media only screen and (max-width: 992px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
}

View file

@ -1,37 +0,0 @@
import { useApi, useDateRange, useMessages } from 'components/hooks';
import MetricCard from 'components/metrics/MetricCard';
import WebsiteDateFilter from 'components/input/WebsiteDateFilter';
import MetricsBar from 'components/metrics/MetricsBar';
import styles from './EventDataMetricsBar.module.css';
export function EventDataMetricsBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { get, useQuery } = useApi();
const { dateRange } = useDateRange(websiteId);
const { startDate, endDate } = dateRange;
const { data, error, isLoading, isFetched } = useQuery({
queryKey: ['event-data:stats', { websiteId, startDate, endDate }],
queryFn: () =>
get(`/event-data/stats`, {
websiteId,
startAt: +startDate,
endAt: +endDate,
}),
});
return (
<div className={styles.container}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricCard label={formatMessage(labels.events)} value={data?.events} />
<MetricCard label={formatMessage(labels.fields)} value={data?.fields} />
<MetricCard label={formatMessage(labels.totalRecords)} value={data?.records} />
</MetricsBar>
<div className={styles.actions}>
<WebsiteDateFilter websiteId={websiteId} />
</div>
</div>
);
}
export default EventDataMetricsBar;

View file

@ -1,30 +0,0 @@
import { GridTable, GridColumn } from 'react-basics';
import { useMessages } from 'components/hooks';
import PageHeader from 'components/layout/PageHeader';
import Empty from 'components/common/Empty';
import { DATA_TYPES } from 'lib/constants';
export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) {
const { formatMessage, labels } = useMessages();
return (
<>
<PageHeader title={event} />
{data.length <= 0 && <Empty />}
{data.length > 0 && (
<GridTable data={data}>
<GridColumn name="fieldName" label={formatMessage(labels.field)} />
<GridColumn name="dataType" label={formatMessage(labels.type)}>
{row => DATA_TYPES[row.dataType]}
</GridColumn>
<GridColumn name="fieldValue" label={formatMessage(labels.value)} />
<GridColumn name="total" label={formatMessage(labels.totalRecords)} width="200px">
{({ total }) => total.toLocaleString()}
</GridColumn>
</GridTable>
)}
</>
);
}
export default EventDataValueTable;

View file

@ -0,0 +1,14 @@
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(600px, 1fr));
gap: 30px;
}
.table {
align-self: start;
}
.link:hover {
cursor: pointer;
color: var(--primary400);
}

View file

@ -0,0 +1,52 @@
import { GridColumn, GridTable } from 'react-basics';
import { useEventDataProperties, useEventDataValues, useMessages } from 'components/hooks';
import { LoadingPanel } from 'components/common/LoadingPanel';
import styles from './EventProperties.module.css';
import PieChart from 'components/charts/PieChart';
import { useState } from 'react';
import { CHART_COLORS } from 'lib/constants';
export function EventProperties({ websiteId }: { websiteId: string }) {
const [propertyName, setPropertyName] = useState('');
const { formatMessage, labels } = useMessages();
const { data, isLoading, isFetched, error } = useEventDataProperties(websiteId);
const { data: values } = useEventDataValues(websiteId, propertyName);
const chartData =
propertyName && values
? {
labels: values.map(({ value }) => value),
datasets: [
{
data: values.map(({ total }) => total),
backgroundColor: CHART_COLORS,
borderWidth: 0,
},
],
}
: null;
return (
<LoadingPanel isLoading={isLoading} isFetched={isFetched} data={data} error={error}>
<div className={styles.container}>
<GridTable data={data} cardMode={false} className={styles.table}>
<GridColumn name="propertyName" label={formatMessage(labels.property)}>
{row => (
<div className={styles.link} onClick={() => setPropertyName(row.propertyName)}>
{row.propertyName}
</div>
)}
</GridColumn>
<GridColumn name="total" label={formatMessage(labels.count)} />
</GridTable>
{propertyName && (
<div>
<strong>{propertyName}</strong>
<PieChart key={propertyName} type="doughnut" data={chartData} />
</div>
)}
</div>
</LoadingPanel>
);
}
export default EventProperties;

View file

@ -6,8 +6,12 @@ import EventsChart from 'components/metrics/EventsChart';
import { GridRow } from 'components/layout/Grid';
import MetricsTable from 'components/metrics/MetricsTable';
import { useMessages } from 'components/hooks';
import { Item, Tabs } from 'react-basics';
import { useState } from 'react';
import EventProperties from './EventProperties';
export default function EventsPage({ websiteId }) {
const [tab, setTab] = useState('activity');
const { formatMessage, labels } = useMessages();
return (
@ -23,9 +27,18 @@ export default function EventsPage({ websiteId }) {
metric={formatMessage(labels.actions)}
/>
</GridRow>
<GridRow columns="one">
<EventsDataTable websiteId={websiteId} />
</GridRow>
<div>
<Tabs
selectedKey={tab}
onSelect={(value: any) => setTab(value)}
style={{ marginBottom: 30 }}
>
<Item key="activity">{formatMessage(labels.activity)}</Item>
<Item key="properties">{formatMessage(labels.properties)}</Item>
</Tabs>
{tab === 'activity' && <EventsDataTable websiteId={websiteId} />}
{tab === 'properties' && <EventProperties websiteId={websiteId} />}
</div>
</>
);
}

View file

@ -1,7 +0,0 @@
.container a {
color: var(--font-color100);
}
.container a:hover {
color: var(--primary400);
}

View file

@ -1,41 +0,0 @@
import { Flexbox, Loading } from 'react-basics';
import EventsTable from './EventsTable';
import EventDataValueTable from './EventDataValueTable';
import { EventDataMetricsBar } from './EventDataMetricsBar';
import { useDateRange, useApi, useNavigation } from 'components/hooks';
import styles from './WebsiteEventData.module.css';
function useData(websiteId: string, event: string) {
const { dateRange } = useDateRange(websiteId);
const { startDate, endDate } = dateRange;
const { get, useQuery } = useApi();
const { data, error, isLoading } = useQuery({
queryKey: ['event-data:events', { websiteId, startDate, endDate, event }],
queryFn: () =>
get('/event-data/events', {
websiteId,
startAt: +startDate,
endAt: +endDate,
event,
}),
enabled: !!(websiteId && startDate && endDate),
});
return { data, error, isLoading };
}
export default function WebsiteEventData({ websiteId }) {
const {
query: { event },
} = useNavigation();
const { data, isLoading } = useData(websiteId, event);
return (
<Flexbox className={styles.container} direction="column" gap={20}>
<EventDataMetricsBar websiteId={websiteId} />
{!event && <EventsTable data={data} />}
{isLoading && <Loading position="page" />}
{event && data && <EventDataValueTable event={event} data={data} />}
</Flexbox>
);
}

View file

@ -54,7 +54,7 @@ export function RealtimeLog({ data }: { data: RealtimeData }) {
},
];
const getTime = ({ createdAt }) => format(new Date(createdAt), 'h:mm:ss');
const getTime = ({ createdAt, firstAt }) => format(new Date(firstAt || createdAt), 'h:mm:ss');
const getColor = ({ id, sessionId }) => stringToColor(sessionId || id);
@ -181,7 +181,7 @@ export function RealtimeLog({ data }: { data: RealtimeData }) {
<SearchField className={styles.search} value={search} onSearch={setSearch} />
<FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
</div>
<div className={styles.header}>{formatMessage(labels.activityLog)}</div>
<div className={styles.header}>{formatMessage(labels.activity)}</div>
<div className={styles.body}>
{logs?.length === 0 && <Empty />}
{logs?.length > 0 && (

View file

@ -21,7 +21,6 @@ export default function ({ children }) {
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex,nofollow" />
</head>
<body>