Skip to content

Commit 848ed4c

Browse files
committed
Adds session and activity visual indicators
Helps users quickly group related events by coloring customer and activity sessions. Improves debugging and pattern recognition through clearer visual boundaries.
1 parent ac0046b commit 848ed4c

File tree

3 files changed

+360
-8
lines changed

3 files changed

+360
-8
lines changed

SESSION_INDICATORS_FEATURE.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Session/Activity Visual Indicators
2+
3+
**Feature:** Colored dots to visually identify CustomerSession and ActivitySession groupings in webhook events
4+
5+
## Overview
6+
7+
Events in the webhook inspector now display two colored dots that help identify which events belong to the same session and activity. This makes it easy to visually group related events without reading session IDs.
8+
9+
## Visual Design
10+
11+
Each event shows up to two colored dots:
12+
13+
```
14+
┌─────────────────────────────────────────┐
15+
│ TPS.SessionInfo │
16+
│ Oct 2, 2025, 3:45:12 PM │
17+
│ ● ● ← Session indicators │
18+
│ ↑ ↑ │
19+
│ │ └─ Activity Session (green) │
20+
│ └─── Customer Session (blue) │
21+
└─────────────────────────────────────────┘
22+
```
23+
24+
### Dot Meanings
25+
26+
- **First dot (left):** CustomerSession
27+
- All events with the same CustomerSession.Id have the same color
28+
- A customer session can contain 0 or more activities
29+
30+
- **Second dot (right):** ActivitySession
31+
- All events with the same ActivitySession.Id have the same color
32+
- An activity is a subset of a customer session
33+
34+
### Color Assignment
35+
36+
Colors are automatically assigned from a palette of 10 distinct colors:
37+
1. Blue (#3b82f6)
38+
2. Green (#10b981)
39+
3. Amber (#f59e0b)
40+
4. Red (#ef4444)
41+
5. Violet (#8b5cf6)
42+
6. Pink (#ec4899)
43+
7. Cyan (#06b6d4)
44+
8. Orange (#f97316)
45+
9. Lime (#84cc16)
46+
10. Indigo (#6366f1)
47+
48+
Colors are assigned in order as new session IDs are encountered. If more than 10 sessions exist, colors cycle through the palette.
49+
50+
## Implementation
51+
52+
### Session ID Extraction
53+
54+
**File:** `src/components/WebhookInspector.tsx` (lines 5-32)
55+
56+
```typescript
57+
const getSessionIds = (e: EventItem): { customerSessionId?: string; activitySessionId?: string } => {
58+
const raw = e.raw as any;
59+
const data = raw?.data || raw;
60+
61+
// Extract CustomerSession.Id from various possible locations
62+
const customerSessionId =
63+
data?.CustomerSession?.Id ||
64+
data?.common?.CustomerSession?.Id ||
65+
raw?.common?.CustomerSession?.Id;
66+
67+
// Extract ActivitySession.Id from various possible locations
68+
const activitySessionId =
69+
data?.ActivitySession?.Id ||
70+
data?.common?.ActivitySession?.Id ||
71+
raw?.common?.ActivitySession?.Id;
72+
73+
return { customerSessionId, activitySessionId };
74+
};
75+
```
76+
77+
### Color Assignment Logic
78+
79+
**File:** `src/components/WebhookInspector.tsx` (lines 34-44)
80+
81+
```typescript
82+
const getColorForId = (id: string | undefined, colorMap: Map<string, string>): string | null => {
83+
if (!id) return null;
84+
85+
if (!colorMap.has(id)) {
86+
// Assign a color based on the current size of the map
87+
const colorIndex = colorMap.size % SESSION_COLORS.length;
88+
colorMap.set(id, SESSION_COLORS[colorIndex]);
89+
}
90+
91+
return colorMap.get(id) || null;
92+
};
93+
```
94+
95+
### State Management
96+
97+
Color maps are stored in React refs to persist across renders:
98+
99+
```typescript
100+
const customerSessionColors = React.useRef(new Map<string, string>()).current;
101+
const activitySessionColors = React.useRef(new Map<string, string>()).current;
102+
```
103+
104+
### Rendering
105+
106+
**File:** `src/components/WebhookInspector.tsx` (lines 291-320)
107+
108+
Each event extracts session IDs, gets colors, and renders dots:
109+
110+
```tsx
111+
{filtered.map((e, idx) => {
112+
const { customerSessionId, activitySessionId } = getSessionIds(e);
113+
const customerColor = getColorForId(customerSessionId, customerSessionColors);
114+
const activityColor = getColorForId(activitySessionId, activitySessionColors);
115+
116+
return (
117+
<li className="webhook-event-item">
118+
<div className="event-type">{e.eventType}</div>
119+
<div className="event-meta">{new Date(e.timestamp).toLocaleString()}</div>
120+
<div className="event-session-indicators">
121+
{customerColor && <div className="session-dot" style={{ backgroundColor: customerColor }} />}
122+
{activityColor && <div className="session-dot" style={{ backgroundColor: activityColor }} />}
123+
</div>
124+
</li>
125+
);
126+
})}
127+
```
128+
129+
### CSS Styling
130+
131+
**File:** `src/components/WebhookInspector.css`
132+
133+
```css
134+
.event-session-indicators{
135+
display:flex;
136+
gap:6px;
137+
margin-top:6px;
138+
align-items:center
139+
}
140+
141+
.session-dot{
142+
width:10px;
143+
height:10px;
144+
border-radius:50%;
145+
border:1px solid rgba(0,0,0,0.1)
146+
}
147+
```
148+
149+
## Usage Examples
150+
151+
### Single Session, Multiple Activities
152+
153+
```
154+
Event 1: ● ● (blue, green) - Session A, Activity 1
155+
Event 2: ● ● (blue, green) - Session A, Activity 1
156+
Event 3: ● ● (blue, amber) - Session A, Activity 2
157+
Event 4: ● ● (blue, amber) - Session A, Activity 2
158+
```
159+
160+
All events share the same CustomerSession (blue dot), but activities 1 and 2 have different colors.
161+
162+
### Multiple Sessions
163+
164+
```
165+
Event 1: ● ● (blue, green) - Session A, Activity 1
166+
Event 2: ● ● (blue, green) - Session A, Activity 1
167+
Event 3: ● ● (amber, red) - Session B, Activity 3
168+
Event 4: ● ● (amber, red) - Session B, Activity 3
169+
```
170+
171+
Different sessions have completely different color pairs.
172+
173+
### Events Without Sessions
174+
175+
If an event doesn't have CustomerSession or ActivitySession data, no dots are shown for those missing values:
176+
177+
```
178+
Event 1: ● (blue only) - Has CustomerSession, no ActivitySession
179+
Event 2: (no dots) - No session data
180+
Event 3: ● ● (blue, green) - Both sessions present
181+
```
182+
183+
## Tooltip Information
184+
185+
Hovering over a dot shows the actual session ID:
186+
187+
- **CustomerSession dot:** "Customer Session: a3b5c7d9-..."
188+
- **ActivitySession dot:** "Activity Session: f1e2d3c4-..."
189+
190+
This helps when you need to reference the actual ID values.
191+
192+
## Benefits
193+
194+
**Visual grouping** - Quickly identify related events without reading IDs
195+
**Session boundaries** - Easily spot when a new session starts
196+
**Activity transitions** - See when activities change within a session
197+
**Pattern recognition** - Identify event sequences by color patterns
198+
**Debugging** - Trace event flow through sessions and activities
199+
200+
## Migration from Bay Display
201+
202+
**Previous:** Each event showed "Bay: X" text
203+
**Now:** Events show session/activity colored dots
204+
205+
The bay information is still available in the event data when you select an event for preview. The session indicators provide more useful grouping information at a glance.
206+
207+
## Event Data Structure
208+
209+
Session IDs are extracted from the `common` metadata in webhook events:
210+
211+
```json
212+
{
213+
"eventType": "TPS.SessionInfo",
214+
"data": {
215+
"common": {
216+
"CustomerSession": {
217+
"Id": "a3b5c7d9-e1f2-4a5b-8c9d-0e1f2a3b4c5d"
218+
},
219+
"ActivitySession": {
220+
"Id": "f1e2d3c4-b5a6-4c7d-8e9f-0a1b2c3d4e5f"
221+
}
222+
}
223+
}
224+
}
225+
```
226+
227+
Or from the root level:
228+
229+
```json
230+
{
231+
"eventType": "TPS.Live.OnStrokeCompletedEvent",
232+
"common": {
233+
"CustomerSession": { "Id": "..." },
234+
"ActivitySession": { "Id": "..." }
235+
}
236+
}
237+
```
238+
239+
## Testing
240+
241+
1. **Send multiple events in the same session**
242+
- All should have the same CustomerSession dot color
243+
244+
2. **Start a new activity within the same session**
245+
- CustomerSession dot color stays the same
246+
- ActivitySession dot color changes
247+
248+
3. **Start a new customer session**
249+
- Both dot colors change
250+
251+
4. **Mix events with and without session data**
252+
- Events without session data show no dots
253+
- Events with partial session data show only relevant dots
254+
255+
## Future Enhancements
256+
257+
Potential improvements (not implemented):
258+
259+
1. **Legend** - Show all active sessions with their colors and IDs
260+
2. **Filtering** - Click a dot to filter events by that session
261+
3. **Custom colors** - Allow users to assign specific colors to known sessions
262+
4. **Session stats** - Show event count per session
263+
5. **Timeline view** - Visualize sessions on a timeline
264+
265+
## Related Files
266+
267+
- `src/components/WebhookInspector.tsx` - Main component logic
268+
- `src/components/WebhookInspector.css` - Styling for dots
269+
- `server/src/events.ts` - Event type definitions including CommonEventData
270+
- `server/src/webhook.ts` - Server-side event extraction
271+
272+
## References
273+
274+
- Session/Activity metadata is defined in `server/src/events.ts` (lines 322-333)
275+
- CustomerSession and ActivitySession are part of CommonEventData interface

src/components/WebhookInspector.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
.webhook-event-item.selected{background:#f7fbff}
1010
.event-type{font-size:13px;color:#333}
1111
.event-meta{font-size:12px;color:#777}
12-
.event-bay{font-size:11px;color:#666;margin-top:6px}
12+
.event-session-indicators{display:flex;gap:6px;margin-top:6px;align-items:center}
13+
.session-dot{width:10px;height:10px;border-radius:50%;border:1px solid rgba(0,0,0,0.1)}
1314
.no-events{padding:12px;color:#666}
1415
.webhook-inspector-preview{flex:1;padding:12px;overflow:auto;min-width:0}
1516
.preview-title{margin-top:0}

src/components/WebhookInspector.tsx

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,55 @@ interface Props {
1818
clearSignal?: number;
1919
}
2020

21+
// Color palette for session/activity indicators
22+
const SESSION_COLORS = [
23+
'#3b82f6', // blue
24+
'#10b981', // green
25+
'#f59e0b', // amber
26+
'#ef4444', // red
27+
'#8b5cf6', // violet
28+
'#ec4899', // pink
29+
'#06b6d4', // cyan
30+
'#f97316', // orange
31+
'#84cc16', // lime
32+
'#6366f1', // indigo
33+
];
34+
35+
const getSessionIds = (e: EventItem): { customerSessionId?: string; activitySessionId?: string } => {
36+
try {
37+
const raw = e.raw as any;
38+
const data = raw?.data || raw;
39+
40+
// Extract CustomerSession.Id
41+
const customerSessionId =
42+
data?.CustomerSession?.Id ||
43+
data?.common?.CustomerSession?.Id ||
44+
raw?.common?.CustomerSession?.Id;
45+
46+
// Extract ActivitySession.Id
47+
const activitySessionId =
48+
data?.ActivitySession?.Id ||
49+
data?.common?.ActivitySession?.Id ||
50+
raw?.common?.ActivitySession?.Id;
51+
52+
return { customerSessionId, activitySessionId };
53+
} catch (err) {
54+
return {};
55+
}
56+
};
57+
58+
const getColorForId = (id: string | undefined, colorMap: Map<string, string>): string | null => {
59+
if (!id) return null;
60+
61+
if (!colorMap.has(id)) {
62+
// Assign a color based on the current size of the map
63+
const colorIndex = colorMap.size % SESSION_COLORS.length;
64+
colorMap.set(id, SESSION_COLORS[colorIndex]);
65+
}
66+
67+
return colorMap.get(id) || null;
68+
};
69+
2170
const getBayIdFromEvent = (e: EventItem) => {
2271
try {
2372
const raw = e.raw as any;
@@ -43,6 +92,10 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
4392
const [showAllEvents, setShowAllEvents] = React.useState(false);
4493
const listRef = React.useRef<HTMLUListElement | null>(null);
4594
const listContainerRef = React.useRef<HTMLDivElement | null>(null);
95+
96+
// Color maps for session IDs
97+
const customerSessionColors = React.useRef(new Map<string, string>()).current;
98+
const activitySessionColors = React.useRef(new Map<string, string>()).current;
4699

47100
// Fetch initial events
48101
React.useEffect(() => {
@@ -236,13 +289,36 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
236289
{filtered.length === 0 ? (
237290
<li className="no-events">No events yet.</li>
238291
) : (
239-
filtered.map((e, idx) => (
240-
<li key={e.id || idx} className={`webhook-event-item ${selectedIndex === idx ? 'selected' : ''}`} onClick={() => select(idx)}>
241-
<div className="event-type">{e.eventType}</div>
242-
<div className="event-meta">{new Date(e.timestamp).toLocaleString()}</div>
243-
<div className="event-bay">{getBayIdFromEvent(e) ? `Bay: ${getBayIdFromEvent(e)}` : ''}</div>
244-
</li>
245-
))
292+
filtered.map((e, idx) => {
293+
const { customerSessionId, activitySessionId } = getSessionIds(e);
294+
const customerColor = getColorForId(customerSessionId, customerSessionColors);
295+
const activityColor = getColorForId(activitySessionId, activitySessionColors);
296+
297+
return (
298+
<li key={e.id || idx} className={`webhook-event-item ${selectedIndex === idx ? 'selected' : ''}`} onClick={() => select(idx)}>
299+
<div className="event-type">{e.eventType}</div>
300+
<div className="event-meta">{new Date(e.timestamp).toLocaleString()}</div>
301+
<div className="event-session-indicators">
302+
{customerColor && (
303+
<div
304+
className="session-dot"
305+
// eslint-disable-next-line react/forbid-dom-props
306+
style={{ backgroundColor: customerColor }}
307+
title={`Customer Session: ${customerSessionId}`}
308+
/>
309+
)}
310+
{activityColor && (
311+
<div
312+
className="session-dot"
313+
// eslint-disable-next-line react/forbid-dom-props
314+
style={{ backgroundColor: activityColor }}
315+
title={`Activity Session: ${activitySessionId}`}
316+
/>
317+
)}
318+
</div>
319+
</li>
320+
);
321+
})
246322
)}
247323
</ul>
248324
</div>

0 commit comments

Comments
 (0)