|
3 | 3 | import { parseWorkflowName } from '@workflow/core/parse-name'; |
4 | 4 | import { |
5 | 5 | cancelRun, |
| 6 | + ErrorBoundary, |
6 | 7 | type Event, |
7 | 8 | recreateRun, |
8 | 9 | type Step, |
@@ -550,115 +551,130 @@ export function RunDetailView({ |
550 | 551 | </TabsList> |
551 | 552 |
|
552 | 553 | <TabsContent value="trace" className="mt-0 flex-1 min-h-0"> |
553 | | - <div className="h-full"> |
554 | | - <WorkflowTraceViewer |
555 | | - error={error} |
556 | | - steps={allSteps} |
557 | | - events={allEvents} |
558 | | - hooks={allHooks} |
559 | | - env={env} |
560 | | - run={run} |
561 | | - isLoading={loading} |
562 | | - onStreamClick={handleStreamClick} |
563 | | - /> |
564 | | - </div> |
| 554 | + <ErrorBoundary |
| 555 | + title="Trace Viewer Error" |
| 556 | + description="Failed to load trace viewer. Please try refreshing the page." |
| 557 | + > |
| 558 | + <div className="h-full"> |
| 559 | + <WorkflowTraceViewer |
| 560 | + error={error} |
| 561 | + steps={allSteps} |
| 562 | + events={allEvents} |
| 563 | + hooks={allHooks} |
| 564 | + env={env} |
| 565 | + run={run} |
| 566 | + isLoading={loading} |
| 567 | + onStreamClick={handleStreamClick} |
| 568 | + /> |
| 569 | + </div> |
| 570 | + </ErrorBoundary> |
565 | 571 | </TabsContent> |
566 | 572 |
|
567 | 573 | <TabsContent value="streams" className="mt-0 flex-1 min-h-0"> |
568 | | - <div className="h-full flex gap-4"> |
569 | | - {/* Stream list sidebar */} |
570 | | - <div |
571 | | - className="w-64 flex-shrink-0 border rounded-lg overflow-hidden" |
572 | | - style={{ |
573 | | - borderColor: 'var(--ds-gray-300)', |
574 | | - backgroundColor: 'var(--ds-background-100)', |
575 | | - }} |
576 | | - > |
| 574 | + <ErrorBoundary |
| 575 | + title="Streams Error" |
| 576 | + description="Failed to load streams. Please try refreshing the page." |
| 577 | + > |
| 578 | + <div className="h-full flex gap-4"> |
| 579 | + {/* Stream list sidebar */} |
577 | 580 | <div |
578 | | - className="px-3 py-2 border-b text-xs font-medium" |
| 581 | + className="w-64 flex-shrink-0 border rounded-lg overflow-hidden" |
579 | 582 | style={{ |
580 | 583 | borderColor: 'var(--ds-gray-300)', |
581 | | - color: 'var(--ds-gray-900)', |
| 584 | + backgroundColor: 'var(--ds-background-100)', |
582 | 585 | }} |
583 | 586 | > |
584 | | - Streams ({streams.length}) |
585 | | - </div> |
586 | | - <div className="overflow-auto max-h-[calc(100vh-400px)]"> |
587 | | - {streamsLoading ? ( |
588 | | - <div className="p-4 flex items-center justify-center"> |
589 | | - <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" /> |
590 | | - </div> |
591 | | - ) : streamsError ? ( |
592 | | - <div className="p-4 text-xs text-destructive"> |
593 | | - {streamsError.message} |
594 | | - </div> |
595 | | - ) : streams.length === 0 ? ( |
596 | | - <div |
597 | | - className="p-4 text-xs" |
598 | | - style={{ color: 'var(--ds-gray-600)' }} |
599 | | - > |
600 | | - No streams found for this run |
601 | | - </div> |
602 | | - ) : ( |
603 | | - streams.map((streamId) => ( |
604 | | - <button |
605 | | - key={streamId} |
606 | | - type="button" |
607 | | - onClick={() => setSelectedStreamId(streamId)} |
608 | | - className="w-full text-left px-3 py-2 text-xs font-mono truncate hover:bg-accent transition-colors" |
609 | | - style={{ |
610 | | - backgroundColor: |
611 | | - selectedStreamId === streamId |
612 | | - ? 'var(--ds-gray-200)' |
613 | | - : 'transparent', |
614 | | - color: 'var(--ds-gray-1000)', |
615 | | - }} |
616 | | - title={streamId} |
617 | | - > |
618 | | - {streamId} |
619 | | - </button> |
620 | | - )) |
621 | | - )} |
622 | | - </div> |
623 | | - </div> |
624 | | - |
625 | | - {/* Stream viewer */} |
626 | | - <div className="flex-1 min-w-0"> |
627 | | - {selectedStreamId ? ( |
628 | | - <StreamViewer env={env} streamId={selectedStreamId} /> |
629 | | - ) : ( |
630 | 587 | <div |
631 | | - className="h-full flex items-center justify-center rounded-lg border" |
| 588 | + className="px-3 py-2 border-b text-xs font-medium" |
632 | 589 | style={{ |
633 | 590 | borderColor: 'var(--ds-gray-300)', |
634 | | - backgroundColor: 'var(--ds-gray-100)', |
| 591 | + color: 'var(--ds-gray-900)', |
635 | 592 | }} |
636 | 593 | > |
| 594 | + Streams ({streams.length}) |
| 595 | + </div> |
| 596 | + <div className="overflow-auto max-h-[calc(100vh-400px)]"> |
| 597 | + {streamsLoading ? ( |
| 598 | + <div className="p-4 flex items-center justify-center"> |
| 599 | + <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" /> |
| 600 | + </div> |
| 601 | + ) : streamsError ? ( |
| 602 | + <div className="p-4 text-xs text-destructive"> |
| 603 | + {streamsError.message} |
| 604 | + </div> |
| 605 | + ) : streams.length === 0 ? ( |
| 606 | + <div |
| 607 | + className="p-4 text-xs" |
| 608 | + style={{ color: 'var(--ds-gray-600)' }} |
| 609 | + > |
| 610 | + No streams found for this run |
| 611 | + </div> |
| 612 | + ) : ( |
| 613 | + streams.map((streamId) => ( |
| 614 | + <button |
| 615 | + key={streamId} |
| 616 | + type="button" |
| 617 | + onClick={() => setSelectedStreamId(streamId)} |
| 618 | + className="w-full text-left px-3 py-2 text-xs font-mono truncate hover:bg-accent transition-colors" |
| 619 | + style={{ |
| 620 | + backgroundColor: |
| 621 | + selectedStreamId === streamId |
| 622 | + ? 'var(--ds-gray-200)' |
| 623 | + : 'transparent', |
| 624 | + color: 'var(--ds-gray-1000)', |
| 625 | + }} |
| 626 | + title={streamId} |
| 627 | + > |
| 628 | + {streamId} |
| 629 | + </button> |
| 630 | + )) |
| 631 | + )} |
| 632 | + </div> |
| 633 | + </div> |
| 634 | + |
| 635 | + {/* Stream viewer */} |
| 636 | + <div className="flex-1 min-w-0"> |
| 637 | + {selectedStreamId ? ( |
| 638 | + <StreamViewer env={env} streamId={selectedStreamId} /> |
| 639 | + ) : ( |
637 | 640 | <div |
638 | | - className="text-sm" |
639 | | - style={{ color: 'var(--ds-gray-600)' }} |
| 641 | + className="h-full flex items-center justify-center rounded-lg border" |
| 642 | + style={{ |
| 643 | + borderColor: 'var(--ds-gray-300)', |
| 644 | + backgroundColor: 'var(--ds-gray-100)', |
| 645 | + }} |
640 | 646 | > |
641 | | - {streams.length > 0 |
642 | | - ? 'Select a stream to view its data' |
643 | | - : 'No streams available'} |
| 647 | + <div |
| 648 | + className="text-sm" |
| 649 | + style={{ color: 'var(--ds-gray-600)' }} |
| 650 | + > |
| 651 | + {streams.length > 0 |
| 652 | + ? 'Select a stream to view its data' |
| 653 | + : 'No streams available'} |
| 654 | + </div> |
644 | 655 | </div> |
645 | | - </div> |
646 | | - )} |
| 656 | + )} |
| 657 | + </div> |
647 | 658 | </div> |
648 | | - </div> |
| 659 | + </ErrorBoundary> |
649 | 660 | </TabsContent> |
650 | 661 |
|
651 | 662 | {isLocalBackend && ( |
652 | 663 | <TabsContent value="graph" className="mt-0 flex-1 min-h-0"> |
653 | | - <div className="h-full min-h-[500px]"> |
654 | | - <GraphTabContent |
655 | | - config={config} |
656 | | - run={run} |
657 | | - allSteps={allSteps} |
658 | | - allEvents={allEvents} |
659 | | - env={env} |
660 | | - /> |
661 | | - </div> |
| 664 | + <ErrorBoundary |
| 665 | + title="Graph Viewer Error" |
| 666 | + description="Failed to load execution graph. Please try refreshing the page." |
| 667 | + > |
| 668 | + <div className="h-full min-h-[500px]"> |
| 669 | + <GraphTabContent |
| 670 | + config={config} |
| 671 | + run={run} |
| 672 | + allSteps={allSteps} |
| 673 | + allEvents={allEvents} |
| 674 | + env={env} |
| 675 | + /> |
| 676 | + </div> |
| 677 | + </ErrorBoundary> |
662 | 678 | </TabsContent> |
663 | 679 | )} |
664 | 680 | </Tabs> |
|
0 commit comments