Skip to content

Commit f128512

Browse files
committed
refactor: session cleanuo
1 parent e2a180c commit f128512

File tree

8 files changed

+345
-298
lines changed

8 files changed

+345
-298
lines changed

apps/api/src/query/builders/sessions.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
1414
where: ["event_name = 'screen_view'"],
1515
timeField: 'time',
1616
customizable: true,
17-
},
17+
} satisfies SimpleQueryConfig,
1818

1919
session_duration_distribution: {
2020
table: Analytics.events,
@@ -35,7 +35,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
3535
orderBy: 'sessions DESC',
3636
timeField: 'time',
3737
customizable: true,
38-
},
38+
} satisfies SimpleQueryConfig,
3939

4040
sessions_by_device: {
4141
table: Analytics.events,
@@ -50,7 +50,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
5050
orderBy: 'sessions DESC',
5151
timeField: 'time',
5252
customizable: true,
53-
},
53+
} satisfies SimpleQueryConfig,
5454

5555
sessions_by_browser: {
5656
table: Analytics.events,
@@ -66,7 +66,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
6666
limit: 100,
6767
timeField: 'time',
6868
customizable: true,
69-
},
69+
} satisfies SimpleQueryConfig,
7070

7171
sessions_time_series: {
7272
table: Analytics.events,
@@ -81,7 +81,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
8181
orderBy: 'date ASC',
8282
timeField: 'time',
8383
customizable: true,
84-
},
84+
} satisfies SimpleQueryConfig,
8585

8686
session_flow: {
8787
table: Analytics.events,
@@ -96,7 +96,7 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
9696
limit: 100,
9797
timeField: 'time',
9898
customizable: true,
99-
},
99+
} satisfies SimpleQueryConfig,
100100

101101
session_list: {
102102
customSql: (
@@ -105,8 +105,8 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
105105
endDate: string,
106106
_filters?: unknown[],
107107
_granularity?: unknown,
108-
limit?: number,
109-
offset?: number,
108+
limit = 25,
109+
offset = 0,
110110
_timezone?: string,
111111
filterConditions?: string[],
112112
filterParams?: Record<string, Filter['value']>
@@ -122,7 +122,6 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
122122
MAX(time) as last_visit,
123123
countIf(event_name = 'screen_view') as page_views,
124124
any(anonymous_id) as visitor_id,
125-
any(user_agent) as user_agent,
126125
any(country) as country,
127126
any(referrer) as referrer,
128127
any(device_type) as device_type,
@@ -167,7 +166,6 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
167166
sl.last_visit,
168167
sl.page_views,
169168
sl.visitor_id,
170-
sl.user_agent,
171169
sl.country,
172170
sl.referrer,
173171
sl.device_type,
@@ -183,8 +181,8 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
183181
websiteId,
184182
startDate,
185183
endDate: `${endDate} 23:59:59`,
186-
limit: limit || 25,
187-
offset: offset || 0,
184+
limit,
185+
offset,
188186
...filterParams,
189187
},
190188
};
@@ -206,11 +204,10 @@ export const SessionsBuilders: Record<string, SimpleQueryConfig> = {
206204
'device_type',
207205
'browser_name',
208206
'country',
209-
'user_agent',
210207
],
211208
where: ['session_id = ?'],
212209
orderBy: 'time ASC',
213210
timeField: 'time',
214211
customizable: true,
215-
},
212+
} satisfies SimpleQueryConfig,
216213
};
Lines changed: 108 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import type { SessionEvent } from '@databuddy/shared';
34
import { FileTextIcon, SparkleIcon } from '@phosphor-icons/react';
45
import { Badge } from '@/components/ui/badge';
56
import {
@@ -9,20 +10,110 @@ import {
910
getEventIconAndColor,
1011
} from './session-utils';
1112

12-
interface SessionEvent {
13-
event_id?: string;
14-
time: string;
15-
event_name: string;
16-
path?: string;
17-
error_message?: string;
18-
error_type?: string;
19-
properties?: Record<string, unknown>;
20-
}
21-
2213
interface SessionEventTimelineProps {
2314
events: SessionEvent[];
2415
}
2516

17+
function EventProperties({
18+
properties,
19+
}: {
20+
properties: Record<string, unknown>;
21+
}) {
22+
return (
23+
<div className="mt-3 rounded-lg border-2 border-accent/20 bg-accent/10 p-3">
24+
<div className="mb-2 flex items-center gap-2">
25+
<SparkleIcon className="h-4 w-4 text-accent-foreground" />
26+
<span className="font-semibold text-accent-foreground text-sm">
27+
Event Properties
28+
</span>
29+
</div>
30+
<div className="space-y-2">
31+
{Object.entries(properties).map(([key, value]) => (
32+
<div
33+
className="flex items-center gap-3 rounded border border-accent/20 bg-card/60 p-2"
34+
key={key}
35+
>
36+
<span className="min-w-0 truncate font-mono font-semibold text-accent-foreground text-xs">
37+
{key}
38+
</span>
39+
<span className="rounded bg-accent/20 px-2 py-1 font-medium text-accent-foreground text-xs">
40+
{formatPropertyValue(value)}
41+
</span>
42+
</div>
43+
))}
44+
</div>
45+
</div>
46+
);
47+
}
48+
49+
// Helper component for individual event
50+
function EventItem({
51+
event,
52+
eventIndex,
53+
}: {
54+
event: SessionEvent;
55+
eventIndex: number;
56+
}) {
57+
const hasProperties = Boolean(
58+
event.properties && Object.keys(event.properties).length > 0
59+
);
60+
const { icon, color, bgColor, borderColor, badgeColor } =
61+
getEventIconAndColor(event.event_name, hasProperties);
62+
const displayPath = getDisplayPath(event.path || '');
63+
const fullPath = cleanUrl(event.path || '');
64+
65+
const eventTitle = event.event_name;
66+
const titleColor = hasProperties
67+
? 'text-accent-foreground'
68+
: 'text-foreground';
69+
70+
return (
71+
<div
72+
className={`group flex items-start gap-3 rounded-lg border-2 p-4 ${bgColor} ${borderColor} ${hasProperties ? 'shadow-sm' : ''}`}
73+
key={event.event_id || eventIndex}
74+
>
75+
<div
76+
className={`flex h-8 w-8 items-center justify-center rounded-full border-2 bg-card font-bold text-xs ${color} flex-shrink-0 shadow-sm`}
77+
>
78+
{eventIndex + 1}
79+
</div>
80+
<div className="flex min-w-0 flex-1 items-start gap-3">
81+
<div className={`${color} mt-1 flex-shrink-0`}>{icon}</div>
82+
<div className="min-w-0 flex-1">
83+
<div className="mb-2 flex items-start justify-between gap-2">
84+
<div className="flex min-w-0 flex-wrap items-center gap-2">
85+
<span className={`font-semibold text-sm ${titleColor}`}>
86+
{eventTitle}
87+
</span>
88+
{displayPath && (
89+
<Badge
90+
className="font-mono text-xs"
91+
title={fullPath}
92+
variant="secondary"
93+
>
94+
{displayPath}
95+
</Badge>
96+
)}
97+
{hasProperties && (
98+
<Badge className={`font-medium text-xs ${badgeColor}`}>
99+
Custom Event
100+
</Badge>
101+
)}
102+
</div>
103+
<div className="flex-shrink-0 whitespace-nowrap font-medium text-muted-foreground text-xs">
104+
{new Date(event.time).toLocaleTimeString()}
105+
</div>
106+
</div>
107+
108+
{hasProperties && event.properties && (
109+
<EventProperties properties={event.properties} />
110+
)}
111+
</div>
112+
</div>
113+
</div>
114+
);
115+
}
116+
26117
export function SessionEventTimeline({ events }: SessionEventTimelineProps) {
27118
if (!events?.length) {
28119
return (
@@ -35,106 +126,13 @@ export function SessionEventTimeline({ events }: SessionEventTimelineProps) {
35126

36127
return (
37128
<div className="max-h-96 space-y-3 overflow-y-auto">
38-
{events.map((event, eventIndex) => {
39-
const hasProperties = Boolean(
40-
event.properties && Object.keys(event.properties).length > 0
41-
);
42-
const { icon, color, bgColor, borderColor, badgeColor } =
43-
getEventIconAndColor(
44-
event.event_name,
45-
Boolean(event.error_message),
46-
hasProperties
47-
);
48-
const displayPath = getDisplayPath(event.path || '');
49-
const fullPath = cleanUrl(event.path || '');
50-
51-
return (
52-
<div
53-
className={`group flex items-start gap-3 rounded-lg border-2 p-4 ${bgColor} ${borderColor} ${hasProperties ? 'shadow-sm' : ''}`}
54-
key={event.event_id || eventIndex}
55-
>
56-
<div
57-
className={`flex h-8 w-8 items-center justify-center rounded-full border-2 bg-card font-bold text-xs ${color} flex-shrink-0 shadow-sm`}
58-
>
59-
{eventIndex + 1}
60-
</div>
61-
<div className="flex min-w-0 flex-1 items-start gap-3">
62-
<div className={`${color} mt-1 flex-shrink-0`}>{icon}</div>
63-
<div className="min-w-0 flex-1">
64-
<div className="mb-2 flex items-start justify-between gap-2">
65-
<div className="flex min-w-0 flex-wrap items-center gap-2">
66-
<span
67-
className={`font-semibold text-sm ${
68-
event.error_message
69-
? 'text-destructive'
70-
: hasProperties
71-
? 'text-accent-foreground'
72-
: 'text-foreground'
73-
}`}
74-
>
75-
{event.error_message ? 'Error' : event.event_name}
76-
</span>
77-
{displayPath && (
78-
<Badge
79-
className="font-mono text-xs"
80-
title={fullPath}
81-
variant="secondary"
82-
>
83-
{displayPath}
84-
</Badge>
85-
)}
86-
{hasProperties && (
87-
<Badge className={`font-medium text-xs ${badgeColor}`}>
88-
Custom Event
89-
</Badge>
90-
)}
91-
</div>
92-
<div className="flex-shrink-0 whitespace-nowrap font-medium text-muted-foreground text-xs">
93-
{new Date(event.time).toLocaleTimeString()}
94-
</div>
95-
</div>
96-
97-
{hasProperties && (
98-
<div className="mt-3 rounded-lg border-2 border-accent/20 bg-accent/10 p-3">
99-
<div className="mb-2 flex items-center gap-2">
100-
<SparkleIcon className="h-4 w-4 text-accent-foreground" />
101-
<span className="font-semibold text-accent-foreground text-sm">
102-
Event Properties
103-
</span>
104-
</div>
105-
<div className="space-y-2">
106-
{Object.entries(event.properties || {}).map(
107-
([key, value]) => (
108-
<div
109-
className="flex items-center gap-3 rounded border border-accent/20 bg-card/60 p-2"
110-
key={key}
111-
>
112-
<span className="min-w-0 truncate font-mono font-semibold text-accent-foreground text-xs">
113-
{key}
114-
</span>
115-
<span className="rounded bg-accent/20 px-2 py-1 font-medium text-accent-foreground text-xs">
116-
{formatPropertyValue(value)}
117-
</span>
118-
</div>
119-
)
120-
)}
121-
</div>
122-
</div>
123-
)}
124-
125-
{event.error_message && (
126-
<div className="mt-3 rounded-lg border-2 border-destructive/20 bg-destructive/10 p-3">
127-
<div className="text-destructive text-sm">
128-
<span className="font-semibold">{event.error_type}:</span>{' '}
129-
{event.error_message}
130-
</div>
131-
</div>
132-
)}
133-
</div>
134-
</div>
135-
</div>
136-
);
137-
})}
129+
{events.map((event, eventIndex) => (
130+
<EventItem
131+
event={event}
132+
eventIndex={eventIndex}
133+
key={event.event_id || eventIndex}
134+
/>
135+
))}
138136
</div>
139137
);
140138
}

0 commit comments

Comments
 (0)