feat(telemetry): session journey view replaces raw events table
- sessions listed from client_events grouped by session id (7d window) - timeline sheet merges page views, client events, errors, and server-fn activities joined by player within the session window - vitals excluded from timelines; gap markers over 5 minutes
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { Suspense, useState } from "react";
|
||||
import { Plural, Trans, useLingui } from "@lingui/react/macro";
|
||||
import { Text } from "@mantine/core";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { ListSkeleton } from "@/components/skeletons";
|
||||
import { useSessions } from "../../queries";
|
||||
import type { SessionSummary } from "../../types";
|
||||
import { PaginatedList, RelativeTime, TelemetryListItem } from "../shared";
|
||||
import SessionTimelineView from "./session-timeline";
|
||||
|
||||
export const defaultSessionParams = { days: 7, page: 1 } as const;
|
||||
|
||||
const playerName = (player?: SessionSummary["player"]): string | null => {
|
||||
if (!player) return null;
|
||||
return [player.first_name, player.last_name].filter(Boolean).join(" ") || null;
|
||||
};
|
||||
|
||||
const durationLabel = (session: SessionSummary): string => {
|
||||
const ms =
|
||||
Date.parse(session.lastSeen.replace(" ", "T")) -
|
||||
Date.parse(session.started.replace(" ", "T"));
|
||||
const minutes = Math.round(ms / 60000);
|
||||
return minutes < 1 ? "<1m" : `${minutes}m`;
|
||||
};
|
||||
|
||||
export const SessionsList = () => {
|
||||
const { t } = useLingui();
|
||||
const [page, setPage] = useState(1);
|
||||
const [selected, setSelected] = useState<SessionSummary | null>(null);
|
||||
|
||||
const { data } = useSessions({ days: 7, page });
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaginatedList
|
||||
items={data.items}
|
||||
keyOf={(session) => session.session_id}
|
||||
empty={t`No sessions yet`}
|
||||
page={data.page}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={setPage}
|
||||
renderItem={(session) => (
|
||||
<TelemetryListItem
|
||||
title={playerName(session.player) ?? t`Anonymous`}
|
||||
meta={[
|
||||
<RelativeTime key="time" date={session.lastSeen} />,
|
||||
<Plural key="pages" value={session.pageViews} one="# page" other="# pages" />,
|
||||
durationLabel(session),
|
||||
]}
|
||||
error={
|
||||
session.errors > 0 ? (
|
||||
<Plural value={session.errors} one="# error" other="# errors" />
|
||||
) : undefined
|
||||
}
|
||||
onClick={() => setSelected(session)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Sheet
|
||||
title={selected ? (playerName(selected.player) ?? t`Anonymous`) : ""}
|
||||
opened={selected !== null}
|
||||
onChange={() => setSelected(null)}
|
||||
>
|
||||
{selected && (
|
||||
<Suspense fallback={<ListSkeleton rows={8} />}>
|
||||
<SessionTimelineView sessionId={selected.session_id} />
|
||||
</Suspense>
|
||||
)}
|
||||
</Sheet>
|
||||
|
||||
{data.totalItems > 0 && (
|
||||
<Text size="xs" c="dimmed" px="md" py="sm">
|
||||
<Trans>Last 7 days</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SessionsList;
|
||||
Reference in New Issue
Block a user