Skip to content

Commit e35caeb

Browse files
committed
refactor events table
1 parent 6f3c80f commit e35caeb

File tree

2 files changed

+189
-163
lines changed

2 files changed

+189
-163
lines changed

internal/dev_server/ui/src/EventsPage.tsx

Lines changed: 3 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -2,146 +2,16 @@ import { useEffect, useState } from "react";
22
import { apiRoute } from "./util";
33
import { EventData } from "./types";
44
import { Icon } from "@launchpad-ui/icons";
5+
import EventsTable from "./EventsTable";
56

67
type Props = {
78
limit?: number;
89
};
910

10-
const clipboardLink = (linkText: string, value: string, showNotification: (message: string) => void) => {
11-
return (
12-
<a
13-
href="#"
14-
onClick={(e) => {
15-
e.preventDefault();
16-
navigator.clipboard.writeText(value).then(() => {
17-
showNotification("Copied to clipboard!");
18-
}).catch(() => {
19-
showNotification("Failed to copy to clipboard");
20-
});
21-
}}
22-
>
23-
{linkText}
24-
</a>
25-
);
26-
}
27-
28-
const summaryRows = (summaryEvent: EventData, showNotification: (message: string) => void) => {
29-
let rows = [];
30-
for (const [key, value] of Object.entries((summaryEvent.data as any).features)) {
31-
const rowId = summaryEvent.id + key;
32-
const counters = (value as any).counters || [];
33-
34-
for (const counter of counters) {
35-
rows.push(
36-
<tr key={rowId}>
37-
<td>{new Date(summaryEvent.timestamp).toLocaleTimeString()}</td>
38-
<td>summary</td>
39-
<td><Icon name="flag" size="small" /> {key}</td>
40-
<td>evaluated as {String(counter.value)}</td>
41-
<td>{clipboardLink('Copy to clipboard', JSON.stringify(summaryEvent.data), showNotification)}</td>
42-
</tr>
43-
);
44-
}
45-
}
46-
47-
return rows;
48-
}
49-
50-
const indexRows = (indexEvent: EventData, showNotification: (message: string) => void) => {
51-
let eventText;
52-
if (indexEvent.data.context) {
53-
eventText = (indexEvent.data.context?.kind || 'unknown') + ' context';
54-
} else if (indexEvent.data.user) {
55-
eventText = (indexEvent.data.user.key || 'unknown') + ' user';
56-
}
57-
else {
58-
eventText = 'unknown context';
59-
}
60-
61-
return [
62-
<tr key={indexEvent.id}>
63-
<td>{new Date(indexEvent.timestamp).toLocaleTimeString()}</td>
64-
<td>index</td>
65-
<td><Icon name="metric-funnel" size="small" /> {JSON.stringify(indexEvent.data).length} bytes</td>
66-
<td>{eventText}</td>
67-
<td>{clipboardLink('Copy to clipboard', JSON.stringify(indexEvent.data), showNotification)}</td>
68-
</tr>
69-
]
70-
}
71-
72-
const featureRows = (featureEvent: EventData, showNotification: (message: string) => void) => {
73-
const data = featureEvent.data as any; // Type assertion for feature event
74-
const eventText = `evaluated as ${String(data.value)}`;
75-
76-
return [
77-
<tr key={featureEvent.id} className="feature-row">
78-
<td>{new Date(featureEvent.timestamp).toLocaleTimeString()}</td>
79-
<td>feature</td>
80-
<td>{data.key || 'unknown'}</td>
81-
<td>{eventText}</td>
82-
<td>{clipboardLink('Copy to clipboard', JSON.stringify(featureEvent.data), showNotification)}</td>
83-
</tr>
84-
];
85-
}
86-
87-
const customRows = (event: EventData, showNotification: (message: string) => void) => {
88-
return [
89-
<tr key={event.id}>
90-
<td>{new Date(event.timestamp).toLocaleTimeString()}</td>
91-
<td>{event.data.kind}</td>
92-
<td><Icon name="chart-histogram" size="small" /> {event.data.key || 'unknown'}</td>
93-
<td>value is {(event.data as any).metricValue}</td>
94-
<td>{clipboardLink('Copy to clipboard', JSON.stringify(event.data), showNotification)}</td>
95-
</tr>,
96-
];
97-
}
98-
99-
100-
// Return array of <tr>s:
101-
// Time, Type, Key, Event, ViewAttributes
102-
const renderEvent = (event: EventData, showNotification: (message: string) => void) => {
103-
switch (event.data.kind) {
104-
case 'summary':
105-
return summaryRows(event, showNotification);
106-
case 'index':
107-
return indexRows(event, showNotification);
108-
case 'feature':
109-
return featureRows(event, showNotification);
110-
case 'custom':
111-
return customRows(event, showNotification);
112-
default:
113-
return [
114-
<tr key={event.id}>
115-
<td>{(() => {
116-
try {
117-
const date = new Date(event.timestamp);
118-
return isNaN(date.getTime()) ? event.timestamp : date.toLocaleTimeString();
119-
} catch {
120-
return event.timestamp;
121-
}
122-
})()}</td>
123-
<td>{event.data.kind}</td>
124-
<td></td>
125-
<td></td>
126-
<td>{clipboardLink('Copy to clipboard', JSON.stringify(event.data), showNotification)}</td>
127-
</tr>,
128-
];
129-
}
130-
};
131-
13211
const EventsPage = ({ limit = 1000 }: Props) => {
13312
const [events, setEvents] = useState<EventData[]>([]);
134-
const [notification, setNotification] = useState<string | null>(null);
135-
136-
const [isStreaming, setIsStreaming] = useState<boolean>(true);
13713
const [backlog, setBacklog] = useState<EventData[]>([]);
138-
139-
const showNotification = (message: string) => {
140-
setNotification(message);
141-
setTimeout(() => {
142-
setNotification(null);
143-
}, 1500);
144-
};
14+
const [isStreaming, setIsStreaming] = useState<boolean>(true);
14515

14616
useEffect(() => {
14717
const eventSource = new EventSource(apiRoute('/events/tee'));
@@ -188,37 +58,7 @@ const EventsPage = ({ limit = 1000 }: Props) => {
18858
}
18959
};
19060

191-
return (
192-
<div>
193-
<h3>Events Stream (limit: {limit})</h3>
194-
<button
195-
className={`streaming-toggle-button ${isStreaming ? 'streaming' : 'not-streaming'}`}
196-
onClick={() => toggleStreaming(!isStreaming)}
197-
>
198-
{isStreaming ? 'Streaming ON' : 'Streaming OFF'}
199-
</button>
200-
<table className="events-table">
201-
<thead>
202-
<tr>
203-
<th>Time</th>
204-
<th>Type</th>
205-
<th>Target</th>
206-
<th>Event</th>
207-
<th>Link</th>
208-
</tr>
209-
</thead>
210-
<tbody>
211-
{events.map(event => renderEvent(event, showNotification))}
212-
</tbody>
213-
</table>
214-
{events.length === 0 && <p>No events received yet...</p>}
215-
{notification && (
216-
<div className={`copy-notification ${notification ? 'show' : 'hide'}`}>
217-
{notification}
218-
</div>
219-
)}
220-
</div>
221-
);
61+
return <EventsTable events={events} limit={limit} onToggleStreaming={toggleStreaming} />;
22262
};
22363

22464
export default EventsPage;
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { EventData } from "./types";
2+
import { Icon } from "@launchpad-ui/icons";
3+
import { useState } from "react";
4+
5+
type Props = {
6+
events: EventData[];
7+
limit: number;
8+
onToggleStreaming?: (newStreamingState: boolean) => void;
9+
};
10+
11+
const clipboardLink = (linkText: string, value: string, showNotification: (message: string) => void) => {
12+
return (
13+
<a
14+
href="#"
15+
onClick={(e) => {
16+
e.preventDefault();
17+
navigator.clipboard.writeText(value).then(() => {
18+
showNotification("Copied to clipboard!");
19+
}).catch(() => {
20+
showNotification("Failed to copy to clipboard");
21+
});
22+
}}
23+
>
24+
{linkText}
25+
</a>
26+
);
27+
}
28+
29+
const summaryRows = (summaryEvent: EventData, showNotification: (message: string) => void) => {
30+
let rows = [];
31+
for (const [key, value] of Object.entries((summaryEvent.data as any).features)) {
32+
const rowId = summaryEvent.id + key;
33+
const counters = (value as any).counters || [];
34+
35+
for (const counter of counters) {
36+
rows.push(
37+
<tr key={rowId}>
38+
<td>{new Date(summaryEvent.timestamp).toLocaleTimeString()}</td>
39+
<td>summary</td>
40+
<td><Icon name="flag" size="small" /> {key}</td>
41+
<td>evaluated as {String(counter.value)}</td>
42+
<td>{clipboardLink('Copy to clipboard', JSON.stringify(summaryEvent.data), showNotification)}</td>
43+
</tr>
44+
);
45+
}
46+
}
47+
48+
return rows;
49+
}
50+
51+
const indexRows = (indexEvent: EventData, showNotification: (message: string) => void) => {
52+
let eventText;
53+
if (indexEvent.data.context) {
54+
eventText = (indexEvent.data.context?.kind || 'unknown') + ' context';
55+
} else if (indexEvent.data.user) {
56+
eventText = (indexEvent.data.user.key || 'unknown') + ' user';
57+
}
58+
else {
59+
eventText = 'unknown context';
60+
}
61+
62+
return [
63+
<tr key={indexEvent.id}>
64+
<td>{new Date(indexEvent.timestamp).toLocaleTimeString()}</td>
65+
<td>index</td>
66+
<td><Icon name="metric-funnel" size="small" /> {JSON.stringify(indexEvent.data).length} bytes</td>
67+
<td>{eventText}</td>
68+
<td>{clipboardLink('Copy to clipboard', JSON.stringify(indexEvent.data), showNotification)}</td>
69+
</tr>
70+
]
71+
}
72+
73+
const featureRows = (featureEvent: EventData, showNotification: (message: string) => void) => {
74+
const data = featureEvent.data as any; // Type assertion for feature event
75+
const eventText = `evaluated as ${String(data.value)}`;
76+
77+
return [
78+
<tr key={featureEvent.id} className="feature-row">
79+
<td>{new Date(featureEvent.timestamp).toLocaleTimeString()}</td>
80+
<td>feature</td>
81+
<td>{data.key || 'unknown'}</td>
82+
<td>{eventText}</td>
83+
<td>{clipboardLink('Copy to clipboard', JSON.stringify(featureEvent.data), showNotification)}</td>
84+
</tr>
85+
];
86+
}
87+
88+
const customRows = (event: EventData, showNotification: (message: string) => void) => {
89+
return [
90+
<tr key={event.id}>
91+
<td>{new Date(event.timestamp).toLocaleTimeString()}</td>
92+
<td>{event.data.kind}</td>
93+
<td><Icon name="chart-histogram" size="small" /> {event.data.key || 'unknown'}</td>
94+
<td>value is {(event.data as any).metricValue}</td>
95+
<td>{clipboardLink('Copy to clipboard', JSON.stringify(event.data), showNotification)}</td>
96+
</tr>,
97+
];
98+
}
99+
100+
101+
// Return array of <tr>s:
102+
// Time, Type, Key, Event, ViewAttributes
103+
const renderEvent = (event: EventData, showNotification: (message: string) => void) => {
104+
switch (event.data.kind) {
105+
case 'summary':
106+
return summaryRows(event, showNotification);
107+
case 'index':
108+
return indexRows(event, showNotification);
109+
case 'feature':
110+
return featureRows(event, showNotification);
111+
case 'custom':
112+
return customRows(event, showNotification);
113+
default:
114+
return [
115+
<tr key={event.id}>
116+
<td>{(() => {
117+
try {
118+
const date = new Date(event.timestamp);
119+
return isNaN(date.getTime()) ? event.timestamp : date.toLocaleTimeString();
120+
} catch {
121+
return event.timestamp;
122+
}
123+
})()}</td>
124+
<td>{event.data.kind}</td>
125+
<td></td>
126+
<td></td>
127+
<td>{clipboardLink('Copy to clipboard', JSON.stringify(event.data), showNotification)}</td>
128+
</tr>,
129+
];
130+
}
131+
};
132+
133+
const EventsTable = ({
134+
events,
135+
limit,
136+
onToggleStreaming
137+
}: Props) => {
138+
const [notification, setNotification] = useState<string | null>(null);
139+
const [isStreaming, setIsStreaming] = useState<boolean>(true);
140+
141+
const handleToggleStreaming = (newStreamingState: boolean) => {
142+
setIsStreaming(newStreamingState);
143+
onToggleStreaming?.(newStreamingState);
144+
};
145+
146+
const showNotification = (message: string) => {
147+
setNotification(message);
148+
setTimeout(() => {
149+
setNotification(null);
150+
}, 1500);
151+
};
152+
153+
return (
154+
<div>
155+
<h3>Events Stream (limit: {limit})</h3>
156+
<button
157+
className={`streaming-toggle-button ${isStreaming ? 'streaming' : 'not-streaming'}`}
158+
onClick={() => handleToggleStreaming(!isStreaming)}
159+
>
160+
{isStreaming ? 'Streaming ON' : 'Streaming OFF'}
161+
</button>
162+
<table className="events-table">
163+
<thead>
164+
<tr>
165+
<th>Time</th>
166+
<th>Type</th>
167+
<th>Target</th>
168+
<th>Event</th>
169+
<th>Link</th>
170+
</tr>
171+
</thead>
172+
<tbody>
173+
{events.map(event => renderEvent(event, showNotification))}
174+
</tbody>
175+
</table>
176+
{events.length === 0 && <p>No events received yet...</p>}
177+
{notification && (
178+
<div className={`copy-notification ${notification ? 'show' : 'hide'}`}>
179+
{notification}
180+
</div>
181+
)}
182+
</div>
183+
);
184+
};
185+
186+
export default EventsTable;

0 commit comments

Comments
 (0)