@workflow/web-shared 5.0.0-beta.25 → 5.0.0-beta.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts +34 -0
- package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/detail-panel-width.js +67 -0
- package/dist/components/new-trace-viewer/components/detail-panel.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/detail-panel.js +122 -0
- package/dist/components/new-trace-viewer/components/draggable-border.d.ts +31 -0
- package/dist/components/new-trace-viewer/components/draggable-border.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/draggable-border.js +140 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +2 -2
- package/dist/components/new-trace-viewer/components/pane-constants.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/pane-constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/pane-constants.js +16 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +1 -4
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/split-pane.js +18 -16
- package/dist/components/new-trace-viewer/components/timeline.d.ts +2 -2
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +5 -2
- package/dist/components/new-trace-viewer/components/use-element-width.d.ts +8 -0
- package/dist/components/new-trace-viewer/components/use-element-width.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/use-element-width.js +25 -0
- package/dist/components/new-trace-viewer/context.d.ts +2 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/context.js +3 -2
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.js +10 -90
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +38 -35
- package/dist/components/sidebar/attributes-block.d.ts +18 -7
- package/dist/components/sidebar/attributes-block.d.ts.map +1 -1
- package/dist/components/sidebar/attributes-block.js +76 -18
- package/dist/components/sidebar/events-list.d.ts +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +9 -10
- package/dist/components/trace-viewer/components/map.d.ts +2 -2
- package/dist/components/trace-viewer/components/map.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/node.d.ts +7 -7
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
- package/dist/components/ui/collapsible.d.ts +46 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +66 -0
- package/dist/components/ui/data-inspector.js +2 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/hydration.d.ts +12 -2
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +41 -18
- package/package.json +6 -6
- package/src/components/new-trace-viewer/components/detail-panel-width.test.ts +123 -0
- package/src/components/new-trace-viewer/components/detail-panel-width.ts +76 -0
- package/src/components/new-trace-viewer/components/detail-panel.tsx +272 -0
- package/src/components/new-trace-viewer/components/draggable-border.tsx +216 -0
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +1 -1
- package/src/components/new-trace-viewer/components/pane-constants.ts +19 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +44 -55
- package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +4 -1
- package/src/components/new-trace-viewer/components/use-element-width.ts +29 -0
- package/src/components/new-trace-viewer/context.tsx +4 -1
- package/src/components/new-trace-viewer/trace-viewer.tsx +18 -199
- package/src/components/sidebar/attribute-panel.tsx +106 -105
- package/src/components/sidebar/attributes-block.tsx +170 -50
- package/src/components/sidebar/events-list.tsx +107 -92
- package/src/components/ui/collapsible.tsx +219 -0
- package/src/index.ts +8 -7
- package/src/lib/hydration.ts +48 -25
- package/dist/components/sidebar/detail-card.d.ts +0 -13
- package/dist/components/sidebar/detail-card.d.ts.map +0 -1
- package/dist/components/sidebar/detail-card.js +0 -36
- package/src/components/sidebar/detail-card.tsx +0 -143
|
@@ -8,8 +8,12 @@ import type { KeyboardEvent, ReactNode } from 'react';
|
|
|
8
8
|
import { useCallback, useContext, useMemo, useState } from 'react';
|
|
9
9
|
import { isEncryptedMarker, isExpiredMarker } from '../../lib/hydration';
|
|
10
10
|
import { extractConversation, isDoStreamStep } from '../../lib/utils';
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
Collapsible,
|
|
13
|
+
CollapsibleContent,
|
|
14
|
+
CollapsibleRoot,
|
|
15
|
+
CollapsibleTrigger,
|
|
16
|
+
} from '../ui/collapsible';
|
|
13
17
|
import { ContextCardProvider } from '../ui/context-card';
|
|
14
18
|
import {
|
|
15
19
|
DecryptClickContext,
|
|
@@ -20,10 +24,13 @@ import { ErrorCard } from '../ui/error-card';
|
|
|
20
24
|
import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
|
|
21
25
|
import { Skeleton } from '../ui/skeleton';
|
|
22
26
|
import { TimestampTooltip } from '../ui/timestamp-tooltip';
|
|
23
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
DetailMonoKeyValueRow,
|
|
29
|
+
RunAttributesCard,
|
|
30
|
+
RunMetadataCard,
|
|
31
|
+
} from './attributes-block';
|
|
24
32
|
import { ConversationView } from './conversation-view';
|
|
25
33
|
import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
|
|
26
|
-
import { DetailCard } from './detail-card';
|
|
27
34
|
|
|
28
35
|
/**
|
|
29
36
|
* Tab button for conversation/JSON toggle
|
|
@@ -150,7 +157,7 @@ function ConversationWithTabs({
|
|
|
150
157
|
);
|
|
151
158
|
|
|
152
159
|
return (
|
|
153
|
-
<
|
|
160
|
+
<Collapsible label="Input">
|
|
154
161
|
<TabbedContainer
|
|
155
162
|
tabs={conversationTabs}
|
|
156
163
|
activeTab={activeTab}
|
|
@@ -171,7 +178,7 @@ function ConversationWithTabs({
|
|
|
171
178
|
</div>
|
|
172
179
|
)}
|
|
173
180
|
</TabbedContainer>
|
|
174
|
-
</
|
|
181
|
+
</Collapsible>
|
|
175
182
|
);
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -413,7 +420,7 @@ const attributeToDisplayFn: Record<
|
|
|
413
420
|
environment: (_value: unknown) => null,
|
|
414
421
|
executionContext: (_value: unknown) => null,
|
|
415
422
|
// Attributes — string-string metadata attached to the run.
|
|
416
|
-
// Rendered as key-value rows in its own collapsible
|
|
423
|
+
// Rendered as key-value rows in its own collapsible section;
|
|
417
424
|
// if empty/missing, hidden by the hasDisplayContent gate.
|
|
418
425
|
attributes: (value: unknown) => {
|
|
419
426
|
if (!hasDisplayContent(value)) return null;
|
|
@@ -431,16 +438,28 @@ const attributeToDisplayFn: Record<
|
|
|
431
438
|
// Resolved attributes, won't actually use this function
|
|
432
439
|
metadata: (value: unknown) => {
|
|
433
440
|
if (!hasDisplayContent(value)) return null;
|
|
434
|
-
if (isEncryptedMarker(value))
|
|
435
|
-
|
|
436
|
-
|
|
441
|
+
if (isEncryptedMarker(value)) {
|
|
442
|
+
return (
|
|
443
|
+
<Collapsible label="Metadata">
|
|
444
|
+
<EncryptedDataBlock />
|
|
445
|
+
</Collapsible>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (isExpiredMarker(value)) {
|
|
449
|
+
return (
|
|
450
|
+
<Collapsible label="Metadata">
|
|
451
|
+
<ExpiredFieldBlock />
|
|
452
|
+
</Collapsible>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return <RunMetadataCard metadata={value} />;
|
|
437
456
|
},
|
|
438
457
|
input: (value: unknown, context?: DisplayContext) => {
|
|
439
458
|
if (isEncryptedMarker(value)) {
|
|
440
459
|
return (
|
|
441
|
-
<
|
|
460
|
+
<Collapsible label="Input">
|
|
442
461
|
<EncryptedFieldBlock />
|
|
443
|
-
</
|
|
462
|
+
</Collapsible>
|
|
444
463
|
);
|
|
445
464
|
}
|
|
446
465
|
if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
|
|
@@ -463,14 +482,14 @@ const attributeToDisplayFn: Record<
|
|
|
463
482
|
<>
|
|
464
483
|
<ConversationWithTabs conversation={conversation} args={args} />
|
|
465
484
|
{hasClosureVars && (
|
|
466
|
-
<
|
|
485
|
+
<Collapsible label="Closure Variables">
|
|
467
486
|
{JsonBlock(closureVars)}
|
|
468
|
-
</
|
|
487
|
+
</Collapsible>
|
|
469
488
|
)}
|
|
470
489
|
{hasThisVal && (
|
|
471
|
-
<
|
|
490
|
+
<Collapsible label="This Value">
|
|
472
491
|
{JsonBlock(thisVal)}
|
|
473
|
-
</
|
|
492
|
+
</Collapsible>
|
|
474
493
|
)}
|
|
475
494
|
</>
|
|
476
495
|
);
|
|
@@ -479,12 +498,12 @@ const attributeToDisplayFn: Record<
|
|
|
479
498
|
|
|
480
499
|
// Don't render an empty "Input (0 arguments)" card when no input exists.
|
|
481
500
|
if (!hasArgs && !hasClosureVars && !hasThisVal) {
|
|
482
|
-
return <
|
|
501
|
+
return <Collapsible label="Input (no data)" disabled />;
|
|
483
502
|
}
|
|
484
503
|
|
|
485
504
|
return (
|
|
486
505
|
<>
|
|
487
|
-
<
|
|
506
|
+
<Collapsible label="Input">
|
|
488
507
|
{Array.isArray(args)
|
|
489
508
|
? args.map((v, i) => (
|
|
490
509
|
<div className="mt-2 first:mt-0" key={i}>
|
|
@@ -492,14 +511,14 @@ const attributeToDisplayFn: Record<
|
|
|
492
511
|
</div>
|
|
493
512
|
))
|
|
494
513
|
: JsonBlock(args)}
|
|
495
|
-
</
|
|
514
|
+
</Collapsible>
|
|
496
515
|
{hasClosureVars && (
|
|
497
|
-
<
|
|
516
|
+
<Collapsible label="Closure Variables">
|
|
498
517
|
{JsonBlock(closureVars)}
|
|
499
|
-
</
|
|
518
|
+
</Collapsible>
|
|
500
519
|
)}
|
|
501
520
|
{hasThisVal && (
|
|
502
|
-
<
|
|
521
|
+
<Collapsible label="Context">{JsonBlock(thisVal)}</Collapsible>
|
|
503
522
|
)}
|
|
504
523
|
</>
|
|
505
524
|
);
|
|
@@ -507,10 +526,10 @@ const attributeToDisplayFn: Record<
|
|
|
507
526
|
|
|
508
527
|
// Fallback: treat as plain array or object
|
|
509
528
|
if (!hasDisplayContent(value)) {
|
|
510
|
-
return <
|
|
529
|
+
return <Collapsible label="Input (no data)" disabled />;
|
|
511
530
|
}
|
|
512
531
|
return (
|
|
513
|
-
<
|
|
532
|
+
<Collapsible label="Input">
|
|
514
533
|
{Array.isArray(value)
|
|
515
534
|
? value.map((v, i) => (
|
|
516
535
|
<div className="mt-2 first:mt-0" key={i}>
|
|
@@ -518,27 +537,27 @@ const attributeToDisplayFn: Record<
|
|
|
518
537
|
</div>
|
|
519
538
|
))
|
|
520
539
|
: JsonBlock(value)}
|
|
521
|
-
</
|
|
540
|
+
</Collapsible>
|
|
522
541
|
);
|
|
523
542
|
},
|
|
524
543
|
output: (value: unknown) => {
|
|
525
544
|
if (isEncryptedMarker(value)) {
|
|
526
545
|
return (
|
|
527
|
-
<
|
|
546
|
+
<Collapsible label="Output">
|
|
528
547
|
<EncryptedFieldBlock />
|
|
529
|
-
</
|
|
548
|
+
</Collapsible>
|
|
530
549
|
);
|
|
531
550
|
}
|
|
532
551
|
if (!hasDisplayContent(value)) return null;
|
|
533
552
|
if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
|
|
534
|
-
return <
|
|
553
|
+
return <Collapsible label="Output">{JsonBlock(value)}</Collapsible>;
|
|
535
554
|
},
|
|
536
555
|
error: (value: unknown) => {
|
|
537
556
|
if (isEncryptedMarker(value)) {
|
|
538
557
|
return (
|
|
539
|
-
<
|
|
558
|
+
<Collapsible label="Error" defaultOpen>
|
|
540
559
|
<EncryptedFieldBlock />
|
|
541
|
-
</
|
|
560
|
+
</Collapsible>
|
|
542
561
|
);
|
|
543
562
|
}
|
|
544
563
|
if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
|
|
@@ -548,32 +567,32 @@ const attributeToDisplayFn: Record<
|
|
|
548
567
|
// `{ message, stack }`. Render both with the dedicated error block.
|
|
549
568
|
if (isStructuredError(value)) {
|
|
550
569
|
return (
|
|
551
|
-
<
|
|
570
|
+
<Collapsible label="Error" defaultOpen>
|
|
552
571
|
<ErrorStackBlock value={value} />
|
|
553
|
-
</
|
|
572
|
+
</Collapsible>
|
|
554
573
|
);
|
|
555
574
|
}
|
|
556
575
|
|
|
557
576
|
return (
|
|
558
|
-
<
|
|
577
|
+
<Collapsible label="Error" defaultOpen>
|
|
559
578
|
{JsonBlock(value)}
|
|
560
|
-
</
|
|
579
|
+
</Collapsible>
|
|
561
580
|
);
|
|
562
581
|
},
|
|
563
582
|
eventData: (value: unknown) => {
|
|
564
583
|
if (isEncryptedMarker(value)) {
|
|
565
584
|
return (
|
|
566
|
-
<
|
|
585
|
+
<Collapsible label="Event Data" defaultOpen>
|
|
567
586
|
<EncryptedFieldBlock />
|
|
568
|
-
</
|
|
587
|
+
</Collapsible>
|
|
569
588
|
);
|
|
570
589
|
}
|
|
571
590
|
if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
|
|
572
591
|
if (!hasDisplayContent(value)) return null;
|
|
573
592
|
return (
|
|
574
|
-
<
|
|
593
|
+
<Collapsible label="Event Data" defaultOpen>
|
|
575
594
|
{JsonBlock(value)}
|
|
576
|
-
</
|
|
595
|
+
</Collapsible>
|
|
577
596
|
);
|
|
578
597
|
},
|
|
579
598
|
errorCode: (value: unknown) => {
|
|
@@ -591,12 +610,13 @@ const resolvableAttributes = [
|
|
|
591
610
|
'eventData',
|
|
592
611
|
];
|
|
593
612
|
|
|
594
|
-
// Attributes whose displayFn renders its own section header via
|
|
613
|
+
// Attributes whose displayFn renders its own section header via Collapsible,
|
|
595
614
|
// so the outer AttributeBlock should not duplicate the label.
|
|
596
615
|
const selfHeaderedAttributes = new Set([
|
|
597
616
|
'input',
|
|
598
617
|
'output',
|
|
599
618
|
'error',
|
|
619
|
+
'metadata',
|
|
600
620
|
'attributes',
|
|
601
621
|
'eventData',
|
|
602
622
|
]);
|
|
@@ -649,12 +669,12 @@ export const AttributeBlock = ({
|
|
|
649
669
|
: 'Input';
|
|
650
670
|
if (decryptCtx?.hasEncryptedData) {
|
|
651
671
|
return (
|
|
652
|
-
<
|
|
672
|
+
<Collapsible label={label} defaultOpen={attribute === 'eventData'}>
|
|
653
673
|
<EncryptedFieldBlock />
|
|
654
|
-
</
|
|
674
|
+
</Collapsible>
|
|
655
675
|
);
|
|
656
676
|
}
|
|
657
|
-
return <
|
|
677
|
+
return <Collapsible label={label} />;
|
|
658
678
|
}
|
|
659
679
|
|
|
660
680
|
const displayFn =
|
|
@@ -835,61 +855,44 @@ export const AttributePanel = ({
|
|
|
835
855
|
<StreamClickContext.Provider value={onStreamClick}>
|
|
836
856
|
<DecryptClickContext.Provider value={decryptValue}>
|
|
837
857
|
{visibleBasicAttributes.length > 0 && (
|
|
838
|
-
<
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
858
|
+
<CollapsibleRoot defaultOpen>
|
|
859
|
+
<CollapsibleTrigger>Metadata</CollapsibleTrigger>
|
|
860
|
+
<CollapsibleContent className="mt-0 mb-2">
|
|
861
|
+
<div className="flex flex-col">
|
|
862
|
+
{orderedBasicAttributes.map((attribute) => {
|
|
863
|
+
const displayValue = attributeToDisplayFn[
|
|
864
|
+
attribute as keyof typeof attributeToDisplayFn
|
|
865
|
+
]?.(displayData[attribute as keyof typeof displayData]);
|
|
866
|
+
const isCopyableBasicAttribute =
|
|
867
|
+
copyableBasicAttributes.has(
|
|
868
|
+
attribute as AttributeKey
|
|
869
|
+
) && typeof displayValue === 'string';
|
|
870
|
+
const label = getAttributeDisplayName(attribute);
|
|
871
|
+
|
|
872
|
+
return (
|
|
873
|
+
<DetailMonoKeyValueRow
|
|
874
|
+
key={attribute}
|
|
875
|
+
label={label}
|
|
876
|
+
value={displayValue}
|
|
877
|
+
copyText={
|
|
878
|
+
isCopyableBasicAttribute ? displayValue : undefined
|
|
879
|
+
}
|
|
880
|
+
/>
|
|
881
|
+
);
|
|
882
|
+
})}
|
|
883
|
+
{isLoading &&
|
|
884
|
+
resource === 'sleep' &&
|
|
885
|
+
!displayData.resumeAt && (
|
|
886
|
+
<div className="flex items-center justify-between gap-3 py-0.5">
|
|
887
|
+
<span className="text-label-13 text-gray-900">
|
|
888
|
+
Resume
|
|
889
|
+
</span>
|
|
890
|
+
<Skeleton className="h-4 w-[55%]" />
|
|
870
891
|
</div>
|
|
871
|
-
) : (
|
|
872
|
-
<span className="text-right text-label-13 font-mono">
|
|
873
|
-
{displayValue}
|
|
874
|
-
</span>
|
|
875
892
|
)}
|
|
876
|
-
</div>
|
|
877
|
-
);
|
|
878
|
-
})}
|
|
879
|
-
{isLoading && resource === 'sleep' && !displayData.resumeAt && (
|
|
880
|
-
<div className="py-1">
|
|
881
|
-
<div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
|
|
882
|
-
<span
|
|
883
|
-
className="text-[14px] first-letter:uppercase"
|
|
884
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
885
|
-
>
|
|
886
|
-
resumeAt
|
|
887
|
-
</span>
|
|
888
|
-
<Skeleton className="h-4 w-[55%]" />
|
|
889
|
-
</div>
|
|
890
893
|
</div>
|
|
891
|
-
|
|
892
|
-
</
|
|
894
|
+
</CollapsibleContent>
|
|
895
|
+
</CollapsibleRoot>
|
|
893
896
|
)}
|
|
894
897
|
{error ? (
|
|
895
898
|
<ErrorCard
|
|
@@ -900,17 +903,15 @@ export const AttributePanel = ({
|
|
|
900
903
|
) : hasExpired ? (
|
|
901
904
|
<ExpiredDataMessage />
|
|
902
905
|
) : resolvedAttributes.length > 0 ? (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
))}
|
|
913
|
-
</>
|
|
906
|
+
resolvedAttributes.map((attribute) => (
|
|
907
|
+
<AttributeBlock
|
|
908
|
+
isLoading={isLoading}
|
|
909
|
+
key={attribute}
|
|
910
|
+
attribute={attribute}
|
|
911
|
+
value={displayData[attribute as keyof typeof displayData]}
|
|
912
|
+
context={displayContext}
|
|
913
|
+
/>
|
|
914
|
+
))
|
|
914
915
|
) : null}
|
|
915
916
|
</DecryptClickContext.Provider>
|
|
916
917
|
</StreamClickContext.Provider>
|
|
@@ -4,60 +4,94 @@ import {
|
|
|
4
4
|
type AttributeChange,
|
|
5
5
|
RESERVED_ATTRIBUTE_KEY_PREFIX,
|
|
6
6
|
} from '@workflow/world';
|
|
7
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import { cn } from '../../lib/cn';
|
|
10
|
+
import { CopyButton } from '../new-trace-viewer/components/copy-button';
|
|
11
|
+
import { MiddleTruncate } from '../new-trace-viewer/components/middle-truncate/middle-truncate';
|
|
12
|
+
import {
|
|
13
|
+
CollapsibleContent,
|
|
14
|
+
CollapsibleRoot,
|
|
15
|
+
CollapsibleTrigger,
|
|
16
|
+
} from '../ui/collapsible';
|
|
7
17
|
import { CopyableDataBlock } from './copyable-data-block';
|
|
8
|
-
import { DetailCard } from './detail-card';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Check whether an attribute key is in the reserved (`$`-prefixed)
|
|
12
|
-
* namespace used by framework/library code.
|
|
13
|
-
*/
|
|
14
|
-
export const isReservedAttributeKey = (key: string): boolean =>
|
|
15
|
-
key.startsWith(RESERVED_ATTRIBUTE_KEY_PREFIX);
|
|
16
18
|
|
|
17
|
-
function
|
|
18
|
-
return (
|
|
19
|
-
<span
|
|
20
|
-
className="shrink-0 rounded border px-1 py-px text-[10px] font-medium leading-3"
|
|
21
|
-
style={{
|
|
22
|
-
borderColor: 'var(--ds-gray-alpha-400)',
|
|
23
|
-
backgroundColor: 'var(--ds-gray-100)',
|
|
24
|
-
color: 'var(--ds-gray-700)',
|
|
25
|
-
}}
|
|
26
|
-
title={`Keys prefixed with "${RESERVED_ATTRIBUTE_KEY_PREFIX}" are reserved for framework and library code`}
|
|
27
|
-
>
|
|
28
|
-
Reserved
|
|
29
|
-
</span>
|
|
30
|
-
);
|
|
19
|
+
function isReservedAttributeKey(key: string): boolean {
|
|
20
|
+
return key.startsWith(RESERVED_ATTRIBUTE_KEY_PREFIX);
|
|
31
21
|
}
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
const rowValueVariants = cva(
|
|
24
|
+
'max-w-[60%] truncate text-right text-copy-13 text-gray-1000',
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
variant: {
|
|
28
|
+
default: '',
|
|
29
|
+
mono: 'font-mono',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: 'default',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const rowCopyValueVariants = cva(
|
|
39
|
+
'flex min-w-0 max-w-[60%] items-center justify-end gap-1 text-copy-13 text-gray-1000',
|
|
40
|
+
{
|
|
41
|
+
variants: {
|
|
42
|
+
variant: {
|
|
43
|
+
default: '',
|
|
44
|
+
mono: 'font-mono',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
variant: 'default',
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
type DetailKeyValueRowProps = {
|
|
54
|
+
label: string;
|
|
55
|
+
value?: ReactNode;
|
|
56
|
+
copyText?: string;
|
|
57
|
+
removed?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function DetailKeyValueRowBase({
|
|
61
|
+
label,
|
|
35
62
|
value,
|
|
63
|
+
copyText,
|
|
36
64
|
removed = false,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
value
|
|
40
|
-
|
|
41
|
-
}) {
|
|
42
|
-
const reserved = isReservedAttributeKey(attributeKey);
|
|
65
|
+
variant,
|
|
66
|
+
}: DetailKeyValueRowProps & VariantProps<typeof rowValueVariants>) {
|
|
67
|
+
const stringValue = typeof value === 'string' ? value : undefined;
|
|
68
|
+
|
|
43
69
|
return (
|
|
44
|
-
<div className="
|
|
45
|
-
<span
|
|
46
|
-
|
|
47
|
-
style={reserved ? { color: 'var(--ds-gray-700)' } : undefined}
|
|
48
|
-
>
|
|
49
|
-
<span className="truncate">{attributeKey}</span>
|
|
50
|
-
{reserved && <ReservedBadge />}
|
|
70
|
+
<div className="px-1.5 hover:bg-gray-100 flex justify-between gap-3 -mx-1.5 py-0.5 rounded items-center">
|
|
71
|
+
<span className="flex min-w-0 items-center gap-1.5 truncate text-label-13 text-gray-900">
|
|
72
|
+
{label}
|
|
51
73
|
</span>
|
|
52
74
|
{removed ? (
|
|
53
75
|
<span className="shrink-0 text-copy-13 italic text-gray-700">
|
|
54
76
|
removed
|
|
55
77
|
</span>
|
|
78
|
+
) : copyText ? (
|
|
79
|
+
<div className={cn(rowCopyValueVariants({ variant }))} title={copyText}>
|
|
80
|
+
<MiddleTruncate
|
|
81
|
+
value={copyText}
|
|
82
|
+
className={cn(
|
|
83
|
+
rowValueVariants({ variant, className: 'text-right' })
|
|
84
|
+
)}
|
|
85
|
+
style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}
|
|
86
|
+
/>
|
|
87
|
+
<CopyButton
|
|
88
|
+
copyText={copyText}
|
|
89
|
+
ariaLabel={`Copy ${label}`}
|
|
90
|
+
className="shrink-0 -mr-1"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
56
93
|
) : (
|
|
57
|
-
<span
|
|
58
|
-
className="max-w-[60%] truncate text-copy-13 text-gray-1000"
|
|
59
|
-
title={value}
|
|
60
|
-
>
|
|
94
|
+
<span className={cn(rowValueVariants({ variant }))} title={stringValue}>
|
|
61
95
|
{value}
|
|
62
96
|
</span>
|
|
63
97
|
)}
|
|
@@ -65,6 +99,28 @@ function AttributeRow({
|
|
|
65
99
|
);
|
|
66
100
|
}
|
|
67
101
|
|
|
102
|
+
export function DetailKeyValueRow(props: DetailKeyValueRowProps) {
|
|
103
|
+
return <DetailKeyValueRowBase {...props} />;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function DetailMonoKeyValueRow(props: DetailKeyValueRowProps) {
|
|
107
|
+
return <DetailKeyValueRowBase {...props} variant="mono" />;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function AttributeRow({
|
|
111
|
+
attributeKey,
|
|
112
|
+
value,
|
|
113
|
+
removed = false,
|
|
114
|
+
}: {
|
|
115
|
+
attributeKey: string;
|
|
116
|
+
value?: string;
|
|
117
|
+
removed?: boolean;
|
|
118
|
+
}) {
|
|
119
|
+
return (
|
|
120
|
+
<DetailKeyValueRow label={attributeKey} value={value} removed={removed} />
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
/**
|
|
69
125
|
* Sort attribute keys for display: user keys first (alphabetical), then
|
|
70
126
|
* reserved `$`-prefixed keys (alphabetical), so framework metadata doesn't
|
|
@@ -81,8 +137,7 @@ export function sortAttributeKeys(keys: string[]): string[] {
|
|
|
81
137
|
|
|
82
138
|
/**
|
|
83
139
|
* Collapsible section showing a run's materialized attributes as key-value
|
|
84
|
-
* rows. Reserved (`$`-prefixed) keys are
|
|
85
|
-
* badge and sorted after user keys.
|
|
140
|
+
* rows. Reserved (`$`-prefixed) keys are sorted after user keys.
|
|
86
141
|
*/
|
|
87
142
|
export function RunAttributesCard({
|
|
88
143
|
attributes,
|
|
@@ -93,13 +148,78 @@ export function RunAttributesCard({
|
|
|
93
148
|
if (keys.length === 0) return null;
|
|
94
149
|
|
|
95
150
|
return (
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
151
|
+
<CollapsibleRoot defaultOpen>
|
|
152
|
+
<CollapsibleTrigger>Attributes</CollapsibleTrigger>
|
|
153
|
+
<CollapsibleContent className="mb-4">
|
|
154
|
+
<div className="flex flex-col">
|
|
155
|
+
{keys.map((key) => (
|
|
156
|
+
<AttributeRow
|
|
157
|
+
attributeKey={key}
|
|
158
|
+
key={key}
|
|
159
|
+
value={attributes[key]}
|
|
160
|
+
/>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
</CollapsibleContent>
|
|
164
|
+
</CollapsibleRoot>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatMetadataValue(value: unknown): string {
|
|
169
|
+
if (value == null) {
|
|
170
|
+
return String(value);
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === 'string') {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
176
|
+
return String(value);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
return JSON.stringify(value);
|
|
180
|
+
} catch {
|
|
181
|
+
return String(value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isMetadataRecord(value: unknown): value is Record<string, unknown> {
|
|
186
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Collapsible section showing hook (or other resource) metadata as key-value
|
|
191
|
+
* rows, matching the Attributes section styling.
|
|
192
|
+
*/
|
|
193
|
+
export function RunMetadataCard({ metadata }: { metadata: unknown }) {
|
|
194
|
+
if (!isMetadataRecord(metadata)) {
|
|
195
|
+
return (
|
|
196
|
+
<CollapsibleRoot defaultOpen>
|
|
197
|
+
<CollapsibleTrigger>Metadata</CollapsibleTrigger>
|
|
198
|
+
<CollapsibleContent className="mb-4">
|
|
199
|
+
<CopyableDataBlock data={metadata} />
|
|
200
|
+
</CollapsibleContent>
|
|
201
|
+
</CollapsibleRoot>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const keys = Object.keys(metadata).sort((a, b) => a.localeCompare(b));
|
|
206
|
+
if (keys.length === 0) return null;
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<CollapsibleRoot defaultOpen>
|
|
210
|
+
<CollapsibleTrigger>Metadata</CollapsibleTrigger>
|
|
211
|
+
<CollapsibleContent className="mb-4">
|
|
212
|
+
<div className="flex flex-col">
|
|
213
|
+
{keys.map((key) => (
|
|
214
|
+
<AttributeRow
|
|
215
|
+
attributeKey={key}
|
|
216
|
+
key={key}
|
|
217
|
+
value={formatMetadataValue(metadata[key])}
|
|
218
|
+
/>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
</CollapsibleContent>
|
|
222
|
+
</CollapsibleRoot>
|
|
103
223
|
);
|
|
104
224
|
}
|
|
105
225
|
|