Skip to content

Commit d3f0b47

Browse files
committed
feat(flow-view): surface DaVinci cached response and collectors in node detail card
The detail card previously looked for response/collectors on attributed network events, which never had that data. The real response payload lives in the DaVinci client's RTK Query cache keyed by node.cache?.key (= requestId). - davinci-client: add cache.getCache(requestId) that resolves RTK selectors with store state to return actual data (not just the selector function) - devtools-types: add responseBody field to SdkDataSchema so sdk:node-change events can carry the DaVinci raw response - devtools-bridge: extend Subscribable with optional cache.getCache, pass cached response as responseBody when building node SDK events - FlowView.elm: split detail card into node data section (response + collectors from the node event) and network section (HTTP requests attributed to the node); remove collectors lookup from network events where it never existed - panel.html: add .fv-node-label CSS class for the node response header
1 parent f8c3438 commit d3f0b47

5 files changed

Lines changed: 106 additions & 33 deletions

File tree

packages/davinci-client/src/lib/client.store.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,21 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
535535

536536
return flowItem || nextItem || startItem;
537537
},
538+
/**
539+
* Returns the raw cached response data for a given requestId (cache key).
540+
* Checks all three endpoints (flow, next, start) and returns the first with data.
541+
*/
542+
getCache: (requestId: string): unknown => {
543+
if (!requestId) return undefined;
544+
const state = store.getState();
545+
const flow = davinciApi.endpoints.flow.select(requestId)(state);
546+
if (flow?.data !== undefined) return flow.data;
547+
const next = davinciApi.endpoints.next.select(requestId)(state);
548+
if (next?.data !== undefined) return next.data;
549+
const start = davinciApi.endpoints.start.select(requestId)(state);
550+
if (start?.data !== undefined) return start.data;
551+
return undefined;
552+
},
538553
},
539554
};
540555
}

packages/devtools-bridge/src/lib/bridge.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { emitAuthEvent } from './emit.js';
66
interface Subscribable {
77
subscribe: (listener: () => void) => () => void;
88
getNode: () => unknown;
9+
cache?: {
10+
getCache: (requestId: string) => unknown;
11+
};
912
}
1013

1114
export interface BridgeHandle {
@@ -55,7 +58,11 @@ const decodeDaVinciNode = Schema.decodeUnknownOption(DaVinciNodeSchema);
5558
// Pure mapping — fully testable, no side effects
5659
// ---------------------------------------------------------------------------
5760

58-
export function nodeToSdkData(node: DaVinciNode, previousStatus: string | undefined): SdkData {
61+
export function nodeToSdkData(
62+
node: DaVinciNode,
63+
previousStatus: string | undefined,
64+
responseBody?: unknown,
65+
): SdkData {
5966
return {
6067
_tag: 'sdk',
6168
nodeStatus: node.status ?? 'unknown',
@@ -72,6 +79,7 @@ export function nodeToSdkData(node: DaVinciNode, previousStatus: string | undefi
7279
error: node.error ?? undefined,
7380
authorization: node.client?.authorization,
7481
session: node.server?.session,
82+
responseBody,
7583
};
7684
}
7785

@@ -201,7 +209,8 @@ export function attachDevToolsBridge(client: Subscribable, config?: object): Bri
201209
if (node.status === previousStatus) return Option.none();
202210
const priorStatus = previousStatus;
203211
previousStatus = node.status;
204-
return Option.some(nodeToSdkData(node, priorStatus));
212+
const cachedResponse = node.cache?.key ? client.cache?.getCache(node.cache.key) : undefined;
213+
return Option.some(nodeToSdkData(node, priorStatus, cachedResponse));
205214
}),
206215
Option.filter(() => '__PING_DEVTOOLS_EXTENSION__' in window),
207216
Option.map((data) => {

packages/devtools-extension/src/panel/panel.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,11 @@
724724
padding: 6px 10px;
725725
background: var(--raised);
726726
}
727+
.fv-node-label {
728+
font-size: 12px;
729+
font-weight: 600;
730+
color: var(--text);
731+
}
727732
.fv-net-status {
728733
font-size: 11px;
729734
font-family: var(--font-mono);

packages/devtools-extension/src/panel/src/FlowView.elm

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -270,19 +270,86 @@ viewDetail events selectedNodeId expandedSubRows =
270270

271271
Just nodeId ->
272272
let
273+
maybeNode =
274+
List.head (List.filter (\e -> e.id == nodeId) events)
275+
273276
netEvents =
274277
List.filter (\e -> e.causedBy == Just nodeId) events
275278
|> List.sortBy .timestamp
276279
in
277280
Html.div [ Html.Attributes.class "fv-detail" ]
278-
(if List.isEmpty netEvents then
279-
[ Html.div [ Html.Attributes.class "fv-no-data" ]
280-
[ Html.text "No network requests attributed to this node." ]
281+
(viewNodeData nodeId maybeNode expandedSubRows
282+
++ viewNetworkSection nodeId netEvents expandedSubRows
283+
)
284+
285+
286+
viewNodeData : String -> Maybe AuthEvent -> Set String -> List (Html Msg)
287+
viewNodeData nodeId maybeNode expandedSubRows =
288+
case maybeNode of
289+
Nothing ->
290+
[]
291+
292+
Just node ->
293+
let
294+
hasResponse =
295+
node.responseBody /= Nothing
296+
297+
collectorCount =
298+
Maybe.withDefault 0 (Maybe.map List.length node.collectors)
299+
300+
hasCollectors =
301+
collectorCount > 0
302+
303+
responseKey =
304+
nodeId ++ ":node-response"
305+
306+
collectorsKey =
307+
nodeId ++ ":node-collectors"
308+
in
309+
if not hasResponse && not hasCollectors then
310+
[]
311+
312+
else
313+
[ Html.div [ Html.Attributes.class "fv-net-group" ]
314+
[ Html.div [ Html.Attributes.class "fv-net-group-header" ]
315+
[ Html.span [ Html.Attributes.class "fv-node-label" ]
316+
[ Html.text (Maybe.withDefault "Node Response" node.nodeName) ]
317+
]
318+
, if hasResponse then
319+
viewSection responseKey "Response" expandedSubRows
320+
[ case node.responseBody of
321+
Just body -> JsonTree.view "Response" body
322+
Nothing -> Html.text ""
323+
]
324+
325+
else
326+
Html.text ""
327+
, if hasCollectors then
328+
viewSection collectorsKey ("Collectors (" ++ String.fromInt collectorCount ++ ")") expandedSubRows
329+
(case node.collectors of
330+
Nothing -> []
331+
Just cs ->
332+
List.indexedMap
333+
(\i c ->
334+
Html.div [ Html.Attributes.class "coll-card" ]
335+
[ JsonTree.view ("Collector " ++ String.fromInt (i + 1)) c ]
336+
)
337+
cs
338+
)
339+
340+
else
341+
Html.text ""
281342
]
343+
]
282344

283-
else
284-
List.map (\e -> viewNetGroup e expandedSubRows) netEvents
285-
)
345+
346+
viewNetworkSection : String -> List AuthEvent -> Set String -> List (Html Msg)
347+
viewNetworkSection _ netEvents expandedSubRows =
348+
if List.isEmpty netEvents then
349+
[]
350+
351+
else
352+
List.map (\e -> viewNetGroup e expandedSubRows) netEvents
286353

287354

288355
viewNetGroup : AuthEvent -> Set String -> Html Msg
@@ -304,24 +371,15 @@ viewNetGroup event expandedSubRows =
304371
hasResponse =
305372
event.responseBody /= Nothing
306373

307-
collectorCount =
308-
Maybe.withDefault 0 (Maybe.map List.length event.collectors)
309-
310-
hasCollectors =
311-
collectorCount > 0
312-
313374
hasRequest =
314375
event.requestBody /= Nothing
315376

316377
hasAny =
317-
hasResponse || hasCollectors || hasRequest
378+
hasResponse || hasRequest
318379

319380
responseKey =
320381
event.id ++ ":response"
321382

322-
collectorsKey =
323-
event.id ++ ":collectors"
324-
325383
requestKey =
326384
event.id ++ ":request"
327385
in
@@ -346,21 +404,6 @@ viewNetGroup event expandedSubRows =
346404
Nothing -> Html.text ""
347405
]
348406

349-
else
350-
Html.text ""
351-
, if hasCollectors then
352-
viewSection collectorsKey ("Collectors (" ++ String.fromInt collectorCount ++ ")") expandedSubRows
353-
(case event.collectors of
354-
Nothing -> []
355-
Just cs ->
356-
List.indexedMap
357-
(\i c ->
358-
Html.div [ Html.Attributes.class "coll-card" ]
359-
[ JsonTree.view ("Collector " ++ String.fromInt (i + 1)) c ]
360-
)
361-
cs
362-
)
363-
364407
else
365408
Html.text ""
366409
, if hasRequest then

packages/devtools-types/src/lib/auth-event.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const SdkDataSchema = Schema.Struct({
7777
error: Schema.optional(SdkErrorSchema),
7878
authorization: Schema.optional(SdkAuthorizationSchema),
7979
session: Schema.optional(Schema.String),
80+
responseBody: Schema.optional(Schema.Unknown),
8081
});
8182

8283
export const SdkConfigDataSchema = Schema.Struct({

0 commit comments

Comments
 (0)