Skip to content

Commit a774fff

Browse files
committed
Updates webhook filtering to match device IDs
Switches from using bay ID to device ID for more accurate event filtering. Aligns the real-time data stream with device-centric architecture and ensures events only appear for the currently selected device.
1 parent 44984a3 commit a774fff

File tree

6 files changed

+165
-28
lines changed

6 files changed

+165
-28
lines changed

DEVICE_ID_FILTERING.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Device ID Filtering Implementation
2+
3+
## Summary
4+
Updated webhook event filtering to use Device ID from events instead of Bay ID. This allows filtering webhook events based on the device that generated them, matching the `deviceId` field from the GraphQL bay query.
5+
6+
## Changes Made
7+
8+
### 1. GraphQL Query Update
9+
**File: `src/graphql/queries.ts`**
10+
- Updated `BAYS_IN_LOCATION_QUERY` to include `deviceId` field for SimulatorBay type
11+
- Query now returns: `id`, `dbId`, `name`, and `deviceId`
12+
13+
### 2. Bay Interface Update
14+
**File: `src/App.tsx`**
15+
- Added optional `deviceId?: string` field to the `Bay` interface
16+
- This allows the bay object to carry the device ID throughout the application
17+
18+
### 3. Event Filtering Logic
19+
**File: `src/components/WebhookInspector.tsx`**
20+
21+
#### Replaced Function:
22+
- **Old:** `getBayIdFromEvent(e: EventItem)` - extracted Bay.Id from events
23+
- **New:** `getDeviceIdFromEvent(e: EventItem)` - extracts Device.Id from events
24+
25+
#### New Function Implementation:
26+
```typescript
27+
const getDeviceIdFromEvent = (e: EventItem) => {
28+
try {
29+
const raw = e.raw as any;
30+
const data = e.data as any;
31+
// Check for Device.Id in various locations
32+
if (raw && raw.data && raw.data.Device && raw.data.Device.Id) return raw.data.Device.Id;
33+
if (raw && raw.Device && raw.Device.Id) return raw.Device.Id;
34+
if (data && data.Device && data.Device.Id) return data.Device.Id;
35+
return null;
36+
} catch (err) {
37+
return null;
38+
}
39+
};
40+
```
41+
42+
#### Props Interface Update:
43+
- **Old:** `selectedBayDbId?: number | null`
44+
- **New:** `selectedDeviceId?: string | null`
45+
46+
#### Filtering Logic:
47+
- Updated SSE message filtering to match Device ID from events with selected bay's deviceId
48+
- Updated `filtered` useMemo to use Device ID comparison
49+
- Updated "Show All Events" button visibility condition
50+
51+
### 4. WebhookView Component
52+
**File: `src/components/WebhookView.tsx`**
53+
- Updated `WebhookViewProps` interface: `selectedDeviceId?: string | null`
54+
- Updated component to receive and pass `selectedDeviceId` to WebhookInspector
55+
- Changed prop from `selectedBayDbId` to `selectedDeviceId`
56+
57+
### 5. Routes Component
58+
**File: `src/app/Routes.tsx`**
59+
- Updated WebhookView instantiation:
60+
- **Old:** `<WebhookView selectedBayDbId={selectedBayObj?.dbId ?? null} />`
61+
- **New:** `<WebhookView selectedDeviceId={selectedBayObj?.deviceId ?? null} />`
62+
63+
## Event Data Structure
64+
65+
### Device ID Location in Events
66+
According to `server/src/events.ts`, the Device ID is found in the common event data:
67+
68+
```typescript
69+
export interface CommonEventData {
70+
Facility?: FacilityRef;
71+
Location?: LocationRef;
72+
Bay?: BayRef;
73+
Client?: ClientInfo;
74+
Device?: DeviceRef; // <-- Device.Id is here
75+
Radar?: RadarInfo;
76+
User?: UserRef;
77+
CustomerSession?: SessionRef;
78+
ActivitySession?: SessionRef;
79+
}
80+
81+
export interface DeviceRef {
82+
Id?: string; // <-- The Device ID
83+
}
84+
```
85+
86+
### Example Event Structure
87+
From `server/examples/events/TPS.SessionInfo.5.json`:
88+
```json
89+
{
90+
"data": {
91+
"Device": {
92+
"Id": "1d1d211b-981e-4ff4-907c-fc22f776358c"
93+
},
94+
"Bay": {
95+
"Id": "e9acfb8b-6ddd-45ac-9d5b-f23e51b1fe2d",
96+
"Name": "JFE Office"
97+
}
98+
}
99+
}
100+
```
101+
102+
## How It Works
103+
104+
1. **Bay Selection:** User selects a bay from the dropdown in the UI
105+
2. **Device ID Retrieved:** The selected bay object includes its `deviceId` from the GraphQL query
106+
3. **Event Filtering:** When webhook events arrive via SSE:
107+
- Extract `Device.Id` from the event's common data
108+
- Compare it with the selected bay's `deviceId`
109+
- Only show events where Device IDs match
110+
4. **Real-time Updates:** Events are filtered in real-time as they arrive via Server-Sent Events
111+
112+
## Benefits
113+
114+
1. **Accurate Filtering:** Matches events to bays based on the actual device that generated them
115+
2. **Direct Mapping:** Uses the device identifier that's consistently present in both:
116+
- Event data (Device.Id)
117+
- Bay configuration (deviceId from GraphQL)
118+
3. **Future-Proof:** Works with the device-centric architecture of the TrackMan system
119+
120+
## Testing
121+
122+
To verify the changes work correctly:
123+
124+
1. Select a bay from the dropdown
125+
2. Generate events from that bay's device
126+
3. Verify that only events from that device appear in the webhook inspector
127+
4. Switch to a different bay and verify filtering updates correctly
128+
5. Use "Show All Events" toggle to see events from all devices
129+
130+
## Notes
131+
132+
- The `selectedBayId` prop is still supported as a fallback for backward compatibility
133+
- Device IDs are GUIDs/UUIDs in the format: `1d1d211b-981e-4ff4-907c-fc22f776358c`
134+
- Bay IDs are also GUIDs but represent the bay itself, not the device
135+
- This change aligns with the fact that devices can be associated with different bays over time

src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface Bay {
2626
id: string;
2727
dbId: number;
2828
name: string;
29+
deviceId?: string;
2930
}
3031

3132
interface Location {

src/app/Routes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ const Routes: React.FC<RoutesProps> = ({
129129
if (activeTab === 'webhook') {
130130
return (
131131
<div className="documentation-flex">
132-
<WebhookView selectedBayDbId={selectedBayObj?.dbId ?? null} />
132+
<WebhookView selectedDeviceId={selectedBayObj?.deviceId ?? null} />
133133
</div>
134134
);
135135
}

src/components/WebhookInspector.tsx

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type EventItem = {
1313

1414
interface Props {
1515
userPath: string;
16-
selectedBayDbId?: number | null;
16+
selectedDeviceId?: string | null;
1717
selectedBayId?: string | null;
1818
clearSignal?: number;
1919
}
@@ -67,25 +67,21 @@ const getColorForId = (id: string | undefined, colorMap: Map<string, string>): s
6767
return colorMap.get(id) || null;
6868
};
6969

70-
const getBayIdFromEvent = (e: EventItem) => {
70+
const getDeviceIdFromEvent = (e: EventItem) => {
7171
try {
7272
const raw = e.raw as any;
7373
const data = e.data as any;
74-
if (raw && raw.common && raw.common.Bay && (raw.common.Bay.Id || raw.common.Bay.id)) return raw.common.Bay.Id ?? raw.common.Bay.id;
75-
if (raw && raw.common && raw.common.BayId) return raw.common.BayId;
76-
if (raw && raw.data && raw.data.Bay && (raw.data.Bay.Id || raw.data.Bay.id)) return raw.data.Bay.Id ?? raw.data.Bay.id;
77-
if (raw && raw.data && (raw.data.BayId || raw.data.bayId)) return raw.data.BayId ?? raw.data.bayId;
78-
if (raw && raw.Bay && (raw.Bay.Id || raw.Bay.id)) return raw.Bay.Id ?? raw.Bay.id;
79-
if (data && data.Bay && (data.Bay.Id || data.Bay.id)) return data.Bay.Id ?? data.Bay.id;
80-
if (data && (data.BayId || data.bayId)) return data.BayId ?? data.bayId;
81-
if (data && data.common && data.common.Bay && (data.common.Bay.Id || data.common.Bay.id)) return data.common.Bay.Id ?? data.common.Bay.id;
74+
// Check for Device.Id in various locations
75+
if (raw && raw.data && raw.data.Device && raw.data.Device.Id) return raw.data.Device.Id;
76+
if (raw && raw.Device && raw.Device.Id) return raw.Device.Id;
77+
if (data && data.Device && data.Device.Id) return data.Device.Id;
8278
return null;
8379
} catch (err) {
8480
return null;
8581
}
8682
};
8783

88-
const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, selectedBayId = null, clearSignal }) => {
84+
const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null, selectedBayId = null, clearSignal }) => {
8985
const [allEvents, setAllEvents] = React.useState<EventItem[]>([]);
9086
const [connected, setConnected] = React.useState(false);
9187
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(null);
@@ -159,10 +155,12 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
159155
}
160156
return [newItem, ...prev];
161157
});
162-
// If the new item matches the current bay filter (or there is no filter) select it and focus the list
158+
// If the new item matches the current device/bay filter (or there is no filter) select it and focus the list
163159
try {
164-
const bayId = getBayIdFromEvent(newItem);
165-
const matches = (!selectedBayDbId && !selectedBayId) || (bayId && (String(bayId) === String(selectedBayId) || String(bayId) === String(selectedBayDbId)));
160+
const deviceId = getDeviceIdFromEvent(newItem);
161+
const matches = (!selectedDeviceId && !selectedBayId) ||
162+
(deviceId && String(deviceId) === String(selectedDeviceId)) ||
163+
(selectedBayId && String(deviceId) === String(selectedBayId));
166164
if (matches) {
167165
setSelectedIndex(0);
168166
// focus the list container so keyboard navigation continues from the newly added item
@@ -187,21 +185,21 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
187185
}, [userPath]);
188186

189187
const filtered = React.useMemo(() => {
190-
// If "Show All Events" is enabled, bypass Bay filtering
188+
// If "Show All Events" is enabled, bypass Device/Bay filtering
191189
if (showAllEvents) return allEvents;
192190

193-
// If no Bay is selected, show all events
194-
if (!selectedBayDbId && !selectedBayId) return allEvents;
191+
// If no Device/Bay is selected, show all events
192+
if (!selectedDeviceId && !selectedBayId) return allEvents;
195193

196-
// Filter by selected Bay
194+
// Filter by selected Device ID (from bay's deviceId field)
197195
return allEvents.filter(e => {
198-
const bayId = getBayIdFromEvent(e);
199-
if (!bayId) return false;
200-
if (selectedBayId && String(bayId) === String(selectedBayId)) return true;
201-
if (selectedBayDbId && String(bayId) === String(selectedBayDbId)) return true;
196+
const deviceId = getDeviceIdFromEvent(e);
197+
if (!deviceId) return false;
198+
if (selectedDeviceId && String(deviceId) === String(selectedDeviceId)) return true;
199+
if (selectedBayId && String(deviceId) === String(selectedBayId)) return true;
202200
return false;
203201
});
204-
}, [allEvents, selectedBayDbId, selectedBayId, showAllEvents]);
202+
}, [allEvents, selectedDeviceId, selectedBayId, showAllEvents]);
205203

206204
// ensure selected item is visible
207205
React.useEffect(() => {
@@ -281,7 +279,7 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
281279
<div className="webhook-events-header">
282280
<strong>Events</strong>
283281
<span className={`webhook-events-status ${connected ? 'live' : ''}`}>{connected ? 'live' : 'disconnected'}</span>
284-
{(selectedBayDbId || selectedBayId) && (
282+
{(selectedDeviceId || selectedBayId) && (
285283
<label style={{ marginLeft: 'auto', fontSize: '0.85em', display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>
286284
<input
287285
type="checkbox"

src/components/WebhookView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import './WebhookView.css';
66
import './WebhookView.css';
77

88
interface WebhookViewProps {
9-
selectedBayDbId?: number | null;
9+
selectedDeviceId?: string | null;
1010
}
1111

12-
export const WebhookView: React.FC<WebhookViewProps> = ({ selectedBayDbId = null }) => {
12+
export const WebhookView: React.FC<WebhookViewProps> = ({ selectedDeviceId = null }) => {
1313
// selectedBayId will be passed from App via prop injection in App.tsx
1414
const selectedBayIdProp = (window as any)?._selectedBayIdForWebhook || null;
1515
const [result] = useQuery({
@@ -115,7 +115,7 @@ export const WebhookView: React.FC<WebhookViewProps> = ({ selectedBayDbId = null
115115

116116
<div className="webhook-inspector-wrap">
117117
{url && (
118-
<WebhookInspector userPath={(localWebhook || webhookPath) as string} selectedBayDbId={selectedBayDbId} selectedBayId={selectedBayIdProp} clearSignal={clearSignal} />
118+
<WebhookInspector userPath={(localWebhook || webhookPath) as string} selectedDeviceId={selectedDeviceId} selectedBayId={selectedBayIdProp} clearSignal={clearSignal} />
119119
)}
120120
</div>
121121
</div>

src/graphql/queries.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export const BAYS_IN_LOCATION_QUERY = gql`
5656
id
5757
dbId
5858
name
59+
... on SimulatorBay {
60+
deviceId
61+
}
5962
}
6063
}
6164
}

0 commit comments

Comments
 (0)