import type { IServiceEvent, IServiceSession } from '@openpanel/db'; import { useSuspenseQuery, useQuery } from '@tanstack/react-query'; import { createFileRoute, Link } from '@tanstack/react-router'; import { EventIcon } from '@/components/events/event-icon'; import FullPageLoadingState from '@/components/full-page-loading-state'; import { PageContainer } from '@/components/page-container'; import { PageHeader } from '@/components/page-header'; import { ProfileAvatar } from '@/components/profiles/profile-avatar'; import { SerieIcon } from '@/components/report-chart/common/serie-icon'; import { ReplayShell } from '@/components/sessions/replay'; import { KeyValueGrid } from '@/components/ui/key-value-grid'; import { Widget, WidgetBody, WidgetHead, WidgetTitle, } from '@/components/widget'; import { useNumber } from '@/hooks/use-numer-formatter'; import { useTRPC } from '@/integrations/trpc/react'; import { formatDateTime } from '@/utils/date'; import { getProfileName } from '@/utils/getters'; import { createProjectTitle } from '@/utils/title'; export const Route = createFileRoute( '/_app/$organizationId/$projectId/sessions_/$sessionId' )({ component: Component, loader: async ({ context, params }) => { await Promise.all([ context.queryClient.prefetchQuery( context.trpc.session.byId.queryOptions({ sessionId: params.sessionId, projectId: params.projectId, }) ), context.queryClient.prefetchQuery( context.trpc.event.events.queryOptions({ projectId: params.projectId, sessionId: params.sessionId, filters: [], columnVisibility: {}, }) ), ]); }, head: () => ({ meta: [{ title: createProjectTitle('Session') }], }), pendingComponent: FullPageLoadingState, }); function sessionToFakeEvent(session: IServiceSession): IServiceEvent { return { ...session, name: 'screen_view', sessionId: session.id, properties: {}, path: session.exitPath, origin: session.exitOrigin, importedAt: undefined, meta: undefined, sdkName: undefined, sdkVersion: undefined, profile: undefined, }; } function VisitedRoutes({ paths }: { paths: string[] }) { const counted = paths.reduce>((acc, p) => { acc[p] = (acc[p] ?? 0) + 1; return acc; }, {}); const sorted = Object.entries(counted).sort((a, b) => b[1] - a[1]); const max = sorted[0]?.[1] ?? 1; if (sorted.length === 0) { return null; } return ( Visited pages
{sorted.map(([path, count]) => (
{path} {count}
))}
); } function EventDistribution({ events }: { events: IServiceEvent[] }) { const counted = events.reduce>((acc, e) => { acc[e.name] = (acc[e.name] ?? 0) + 1; return acc; }, {}); const sorted = Object.entries(counted).sort((a, b) => b[1] - a[1]); const max = sorted[0]?.[1] ?? 1; if (sorted.length === 0) { return null; } return ( Event distribution
{sorted.map(([name, count]) => (
{name.replace(/_/g, ' ')} {count}
))}
); } function Component() { const { projectId, sessionId, organizationId } = Route.useParams(); const trpc = useTRPC(); const number = useNumber(); const { data: session } = useSuspenseQuery( trpc.session.byId.queryOptions({ sessionId, projectId }) ); const { data: eventsData } = useSuspenseQuery( trpc.event.events.queryOptions({ projectId, sessionId, filters: [], columnVisibility: {}, }) ); const events = eventsData?.data ?? []; const isIdentified = session.profileId && session.profileId !== session.deviceId; const { data: profile } = useSuspenseQuery( trpc.profile.byId.queryOptions({ profileId: session.profileId, projectId, }) ); const { data: sessionGroups } = useQuery({ ...trpc.group.listByIds.queryOptions({ projectId, ids: session.groups ?? [], }), enabled: (session.groups?.length ?? 0) > 0, }); const fakeEvent = sessionToFakeEvent(session); return (
{session.country && (
{session.country} {session.city && ` / ${session.city}`}
)} {session.device && (
{session.device}
)} {session.os && (
{session.os}
)} {session.model && (
{session.model}
)} {session.browser && (
{session.browser}
)}
{session.hasReplay && ( )}
{/* Left column */}
{/* Session info */} Session info 0 ? [{ name: 'revenue', value: `$${session.revenue}` }] : []), { name: 'country', value: session.country, event: fakeEvent }, ...(session.city ? [{ name: 'city', value: session.city, event: fakeEvent }] : []), ...(session.os ? [{ name: 'os', value: session.os, event: fakeEvent }] : []), ...(session.browser ? [ { name: 'browser', value: session.browser, event: fakeEvent, }, ] : []), ...(session.device ? [ { name: 'device', value: session.device, event: fakeEvent, }, ] : []), ...(session.brand ? [{ name: 'brand', value: session.brand, event: fakeEvent }] : []), ...(session.model ? [{ name: 'model', value: session.model, event: fakeEvent }] : []), ]} /> {/* Profile card */} {isIdentified && profile && ( Profile
{getProfileName(profile, false) ?? session.profileId} {profile.email && ( {profile.email} )}
)} {/* Group cards */} {sessionGroups && sessionGroups.length > 0 && ( Groups {sessionGroups.map((group) => (
{group.name} {group.id}
{group.type} ))}
)} {/* Visited pages */} e.name === 'screen_view' && e.path) .map((e) => e.path)} /> {/* Event distribution */}
{/* Right column */}
{/* Events list */} Events
{events.map((event) => (
{event.name === 'screen_view' && event.path ? event.path : event.name.replace(/_/g, ' ')}
{formatDateTime(event.createdAt)}
))} {events.length === 0 && (
No events found
)}
); }