@flanksource/clicky-ui 0.3.49 → 0.3.50
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/connections/ConnectionsPage.cjs +219 -0
- package/dist/connections/ConnectionsPage.cjs.map +1 -0
- package/dist/connections/ConnectionsPage.d.ts +23 -0
- package/dist/connections/ConnectionsPage.d.ts.map +1 -0
- package/dist/connections/ConnectionsPage.js +219 -0
- package/dist/connections/ConnectionsPage.js.map +1 -0
- package/dist/connections/connectionActionStatus.cjs +43 -0
- package/dist/connections/connectionActionStatus.cjs.map +1 -0
- package/dist/connections/connectionActionStatus.d.ts +5 -0
- package/dist/connections/connectionActionStatus.d.ts.map +1 -0
- package/dist/connections/connectionActionStatus.js +43 -0
- package/dist/connections/connectionActionStatus.js.map +1 -0
- package/dist/connections/connectionActionTypes.cjs +24 -0
- package/dist/connections/connectionActionTypes.cjs.map +1 -0
- package/dist/connections/connectionActionTypes.d.ts +36 -0
- package/dist/connections/connectionActionTypes.d.ts.map +1 -0
- package/dist/connections/connectionActionTypes.js +24 -0
- package/dist/connections/connectionActionTypes.js.map +1 -0
- package/dist/connections/connectionActions.cjs +11 -0
- package/dist/connections/connectionActions.cjs.map +1 -0
- package/dist/connections/connectionActions.d.ts +15 -0
- package/dist/connections/connectionActions.d.ts.map +1 -0
- package/dist/connections/connectionActions.js +11 -0
- package/dist/connections/connectionActions.js.map +1 -0
- package/dist/connections/connectionDashboard.cjs +185 -0
- package/dist/connections/connectionDashboard.cjs.map +1 -0
- package/dist/connections/connectionDashboard.d.ts +25 -0
- package/dist/connections/connectionDashboard.d.ts.map +1 -0
- package/dist/connections/connectionDashboard.js +185 -0
- package/dist/connections/connectionDashboard.js.map +1 -0
- package/dist/connections/connectionDashboardModel.cjs +43 -0
- package/dist/connections/connectionDashboardModel.cjs.map +1 -0
- package/dist/connections/connectionDashboardModel.d.ts +75 -0
- package/dist/connections/connectionDashboardModel.d.ts.map +1 -0
- package/dist/connections/connectionDashboardModel.js +43 -0
- package/dist/connections/connectionDashboardModel.js.map +1 -0
- package/dist/connections/connectionDashboardRenderer.cjs +21 -0
- package/dist/connections/connectionDashboardRenderer.cjs.map +1 -0
- package/dist/connections/connectionDashboardRenderer.d.ts +15 -0
- package/dist/connections/connectionDashboardRenderer.d.ts.map +1 -0
- package/dist/connections/connectionDashboardRenderer.js +21 -0
- package/dist/connections/connectionDashboardRenderer.js.map +1 -0
- package/dist/connections/connectionDashboardRow.cjs +169 -0
- package/dist/connections/connectionDashboardRow.cjs.map +1 -0
- package/dist/connections/connectionDashboardRow.d.ts +12 -0
- package/dist/connections/connectionDashboardRow.d.ts.map +1 -0
- package/dist/connections/connectionDashboardRow.js +169 -0
- package/dist/connections/connectionDashboardRow.js.map +1 -0
- package/dist/connections/connectionErrorView.cjs +8 -0
- package/dist/connections/connectionErrorView.cjs.map +1 -0
- package/dist/connections/connectionErrorView.d.ts +4 -0
- package/dist/connections/connectionErrorView.d.ts.map +1 -0
- package/dist/connections/connectionErrorView.js +8 -0
- package/dist/connections/connectionErrorView.js.map +1 -0
- package/dist/connections/connectionHealth.cjs +82 -0
- package/dist/connections/connectionHealth.cjs.map +1 -0
- package/dist/connections/connectionHealth.d.ts +33 -0
- package/dist/connections/connectionHealth.d.ts.map +1 -0
- package/dist/connections/connectionHealth.js +82 -0
- package/dist/connections/connectionHealth.js.map +1 -0
- package/dist/connections/connectionLogging.cjs +29 -0
- package/dist/connections/connectionLogging.cjs.map +1 -0
- package/dist/connections/connectionLogging.d.ts +11 -0
- package/dist/connections/connectionLogging.d.ts.map +1 -0
- package/dist/connections/connectionLogging.js +29 -0
- package/dist/connections/connectionLogging.js.map +1 -0
- package/dist/connections/connectionLoggingField.cjs +37 -0
- package/dist/connections/connectionLoggingField.cjs.map +1 -0
- package/dist/connections/connectionLoggingField.d.ts +12 -0
- package/dist/connections/connectionLoggingField.d.ts.map +1 -0
- package/dist/connections/connectionLoggingField.js +37 -0
- package/dist/connections/connectionLoggingField.js.map +1 -0
- package/dist/connections/connectionLoggingModel.cjs +16 -0
- package/dist/connections/connectionLoggingModel.cjs.map +1 -0
- package/dist/connections/connectionLoggingModel.d.ts +6 -0
- package/dist/connections/connectionLoggingModel.d.ts.map +1 -0
- package/dist/connections/connectionLoggingModel.js +16 -0
- package/dist/connections/connectionLoggingModel.js.map +1 -0
- package/dist/connections/connectionResolveView.cjs +24 -0
- package/dist/connections/connectionResolveView.cjs.map +1 -0
- package/dist/connections/connectionResolveView.d.ts +5 -0
- package/dist/connections/connectionResolveView.d.ts.map +1 -0
- package/dist/connections/connectionResolveView.js +24 -0
- package/dist/connections/connectionResolveView.js.map +1 -0
- package/dist/connections/connectionTestButton.cjs +58 -0
- package/dist/connections/connectionTestButton.cjs.map +1 -0
- package/dist/connections/connectionTestButton.d.ts +6 -0
- package/dist/connections/connectionTestButton.d.ts.map +1 -0
- package/dist/connections/connectionTestButton.js +58 -0
- package/dist/connections/connectionTestButton.js.map +1 -0
- package/dist/connections/connectionTestView.cjs +12 -0
- package/dist/connections/connectionTestView.cjs.map +1 -0
- package/dist/connections/connectionTestView.d.ts +5 -0
- package/dist/connections/connectionTestView.d.ts.map +1 -0
- package/dist/connections/connectionTestView.js +12 -0
- package/dist/connections/connectionTestView.js.map +1 -0
- package/dist/connections/connectionsPageModel.cjs +33 -0
- package/dist/connections/connectionsPageModel.cjs.map +1 -0
- package/dist/connections/connectionsPageModel.d.ts +24 -0
- package/dist/connections/connectionsPageModel.d.ts.map +1 -0
- package/dist/connections/connectionsPageModel.js +33 -0
- package/dist/connections/connectionsPageModel.js.map +1 -0
- package/dist/connections/iconProvider.cjs +20 -0
- package/dist/connections/iconProvider.cjs.map +1 -0
- package/dist/connections/iconProvider.d.ts +7 -0
- package/dist/connections/iconProvider.d.ts.map +1 -0
- package/dist/connections/iconProvider.js +20 -0
- package/dist/connections/iconProvider.js.map +1 -0
- package/dist/connections.cjs +45 -0
- package/dist/connections.cjs.map +1 -0
- package/dist/connections.d.ts +34 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +45 -0
- package/dist/connections.js.map +1 -0
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +52 -295
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js +47 -290
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
- package/dist/data/diagnostics/MetricTile.cjs +44 -0
- package/dist/data/diagnostics/MetricTile.cjs.map +1 -0
- package/dist/data/diagnostics/MetricTile.d.ts +13 -0
- package/dist/data/diagnostics/MetricTile.d.ts.map +1 -0
- package/dist/data/diagnostics/MetricTile.js +44 -0
- package/dist/data/diagnostics/MetricTile.js.map +1 -0
- package/dist/data/diagnostics/StackDumpView.cjs +227 -0
- package/dist/data/diagnostics/StackDumpView.cjs.map +1 -0
- package/dist/data/diagnostics/StackDumpView.d.ts +53 -0
- package/dist/data/diagnostics/StackDumpView.d.ts.map +1 -0
- package/dist/data/diagnostics/StackDumpView.js +227 -0
- package/dist/data/diagnostics/StackDumpView.js.map +1 -0
- package/dist/data/diagnostics/fixtures.d.ts +1 -0
- package/dist/data/diagnostics/fixtures.d.ts.map +1 -1
- package/dist/data/diagnostics/metric-tone.cjs +18 -0
- package/dist/data/diagnostics/metric-tone.cjs.map +1 -0
- package/dist/data/diagnostics/metric-tone.d.ts +6 -0
- package/dist/data/diagnostics/metric-tone.d.ts.map +1 -0
- package/dist/data/diagnostics/metric-tone.js +18 -0
- package/dist/data/diagnostics/metric-tone.js.map +1 -0
- package/dist/data/diagnostics/stacktrace.cjs +44 -0
- package/dist/data/diagnostics/stacktrace.cjs.map +1 -1
- package/dist/data/diagnostics/stacktrace.d.ts +7 -0
- package/dist/data/diagnostics/stacktrace.d.ts.map +1 -1
- package/dist/data/diagnostics/stacktrace.js +44 -0
- package/dist/data/diagnostics/stacktrace.js.map +1 -1
- package/dist/data/diagnostics/types.d.ts +2 -8
- package/dist/data/diagnostics/types.d.ts.map +1 -1
- package/dist/data/lint/LintDetail.cjs +339 -0
- package/dist/data/lint/LintDetail.cjs.map +1 -0
- package/dist/data/lint/LintDetail.d.ts +17 -0
- package/dist/data/lint/LintDetail.d.ts.map +1 -0
- package/dist/data/lint/LintDetail.js +339 -0
- package/dist/data/lint/LintDetail.js.map +1 -0
- package/dist/data/lint/LintFilterBar.cjs +103 -0
- package/dist/data/lint/LintFilterBar.cjs.map +1 -0
- package/dist/data/lint/LintFilterBar.d.ts +16 -0
- package/dist/data/lint/LintFilterBar.d.ts.map +1 -0
- package/dist/data/lint/LintFilterBar.js +103 -0
- package/dist/data/lint/LintFilterBar.js.map +1 -0
- package/dist/data/lint/LintResults.cjs +144 -0
- package/dist/data/lint/LintResults.cjs.map +1 -0
- package/dist/data/lint/LintResults.d.ts +39 -0
- package/dist/data/lint/LintResults.d.ts.map +1 -0
- package/dist/data/lint/LintResults.fixtures.d.ts +10 -0
- package/dist/data/lint/LintResults.fixtures.d.ts.map +1 -0
- package/dist/data/lint/LintResults.js +144 -0
- package/dist/data/lint/LintResults.js.map +1 -0
- package/dist/data/lint/LintTreeNode.cjs +66 -0
- package/dist/data/lint/LintTreeNode.cjs.map +1 -0
- package/dist/data/lint/LintTreeNode.d.ts +7 -0
- package/dist/data/lint/LintTreeNode.d.ts.map +1 -0
- package/dist/data/lint/LintTreeNode.js +66 -0
- package/dist/data/lint/LintTreeNode.js.map +1 -0
- package/dist/data/lint/actions.cjs +195 -0
- package/dist/data/lint/actions.cjs.map +1 -0
- package/dist/data/lint/actions.d.ts +29 -0
- package/dist/data/lint/actions.d.ts.map +1 -0
- package/dist/data/lint/actions.js +195 -0
- package/dist/data/lint/actions.js.map +1 -0
- package/dist/data/lint/grouping-options.cjs +14 -0
- package/dist/data/lint/grouping-options.cjs.map +1 -0
- package/dist/data/lint/grouping-options.d.ts +9 -0
- package/dist/data/lint/grouping-options.d.ts.map +1 -0
- package/dist/data/lint/grouping-options.js +14 -0
- package/dist/data/lint/grouping-options.js.map +1 -0
- package/dist/data/lint/grouping.cjs +450 -0
- package/dist/data/lint/grouping.cjs.map +1 -0
- package/dist/data/lint/grouping.d.ts +10 -0
- package/dist/data/lint/grouping.d.ts.map +1 -0
- package/dist/data/lint/grouping.js +450 -0
- package/dist/data/lint/grouping.js.map +1 -0
- package/dist/data/lint/index.d.ts +10 -0
- package/dist/data/lint/index.d.ts.map +1 -0
- package/dist/data/lint/paths.cjs +35 -0
- package/dist/data/lint/paths.cjs.map +1 -0
- package/dist/data/lint/paths.d.ts +13 -0
- package/dist/data/lint/paths.d.ts.map +1 -0
- package/dist/data/lint/paths.js +35 -0
- package/dist/data/lint/paths.js.map +1 -0
- package/dist/data/lint/stats.cjs +76 -0
- package/dist/data/lint/stats.cjs.map +1 -0
- package/dist/data/lint/stats.d.ts +19 -0
- package/dist/data/lint/stats.d.ts.map +1 -0
- package/dist/data/lint/stats.js +76 -0
- package/dist/data/lint/stats.js.map +1 -0
- package/dist/data/lint/types.cjs +16 -0
- package/dist/data/lint/types.cjs.map +1 -0
- package/dist/data/lint/types.d.ts +93 -0
- package/dist/data/lint/types.d.ts.map +1 -0
- package/dist/data/lint/types.js +16 -0
- package/dist/data/lint/types.js.map +1 -0
- package/dist/data/test-runner/TestAttemptDetail.cjs +113 -0
- package/dist/data/test-runner/TestAttemptDetail.cjs.map +1 -0
- package/dist/data/test-runner/TestAttemptDetail.d.ts +19 -0
- package/dist/data/test-runner/TestAttemptDetail.d.ts.map +1 -0
- package/dist/data/test-runner/TestAttemptDetail.js +113 -0
- package/dist/data/test-runner/TestAttemptDetail.js.map +1 -0
- package/dist/data/test-runner/TestAttempts.cjs +36 -0
- package/dist/data/test-runner/TestAttempts.cjs.map +1 -0
- package/dist/data/test-runner/TestAttempts.d.ts +5 -0
- package/dist/data/test-runner/TestAttempts.d.ts.map +1 -0
- package/dist/data/test-runner/TestAttempts.js +36 -0
- package/dist/data/test-runner/TestAttempts.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +13 -3
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +14 -4
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.cjs +9 -29
- package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.d.ts +6 -6
- package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.js +9 -29
- package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
- package/dist/data/test-runner/TestRunSummary.cjs +33 -0
- package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
- package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunSummary.js +34 -1
- package/dist/data/test-runner/TestRunSummary.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +3 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +8 -1
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +3 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/test-runner/TestTreeNode.cjs +3 -1
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
- package/dist/data/test-runner/TestTreeNode.js +3 -1
- package/dist/data/test-runner/TestTreeNode.js.map +1 -1
- package/dist/data/test-runner/adapter.cjs +9 -1
- package/dist/data/test-runner/adapter.cjs.map +1 -1
- package/dist/data/test-runner/adapter.d.ts +5 -0
- package/dist/data/test-runner/adapter.d.ts.map +1 -1
- package/dist/data/test-runner/adapter.js +9 -1
- package/dist/data/test-runner/adapter.js.map +1 -1
- package/dist/data/test-runner/attempts.cjs +62 -0
- package/dist/data/test-runner/attempts.cjs.map +1 -0
- package/dist/data/test-runner/attempts.d.ts +13 -0
- package/dist/data/test-runner/attempts.d.ts.map +1 -0
- package/dist/data/test-runner/attempts.js +62 -0
- package/dist/data/test-runner/attempts.js.map +1 -0
- package/dist/data/test-runner/context.cjs.map +1 -1
- package/dist/data/test-runner/context.d.ts +6 -0
- package/dist/data/test-runner/context.d.ts.map +1 -1
- package/dist/data/test-runner/context.js.map +1 -1
- package/dist/data/test-runner/index.d.ts +5 -1
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/marks.cjs +2 -0
- package/dist/data/test-runner/marks.cjs.map +1 -1
- package/dist/data/test-runner/marks.d.ts.map +1 -1
- package/dist/data/test-runner/marks.js +2 -0
- package/dist/data/test-runner/marks.js.map +1 -1
- package/dist/data/test-runner/process-metrics.cjs +15 -0
- package/dist/data/test-runner/process-metrics.cjs.map +1 -0
- package/dist/data/test-runner/process-metrics.d.ts +22 -0
- package/dist/data/test-runner/process-metrics.d.ts.map +1 -0
- package/dist/data/test-runner/process-metrics.js +15 -0
- package/dist/data/test-runner/process-metrics.js.map +1 -0
- package/dist/data/test-runner/status.cjs +11 -3
- package/dist/data/test-runner/status.cjs.map +1 -1
- package/dist/data/test-runner/status.d.ts +3 -1
- package/dist/data/test-runner/status.d.ts.map +1 -1
- package/dist/data/test-runner/status.js +11 -3
- package/dist/data/test-runner/status.js.map +1 -1
- package/dist/data/test-runner/types.d.ts +14 -0
- package/dist/data/test-runner/types.d.ts.map +1 -1
- package/dist/data/traces/GoProfileView.cjs +176 -0
- package/dist/data/traces/GoProfileView.cjs.map +1 -0
- package/dist/data/traces/GoProfileView.d.ts +30 -0
- package/dist/data/traces/GoProfileView.d.ts.map +1 -0
- package/dist/data/traces/GoProfileView.js +176 -0
- package/dist/data/traces/GoProfileView.js.map +1 -0
- package/dist/data/traces/ProcessFacetTable.cjs +79 -0
- package/dist/data/traces/ProcessFacetTable.cjs.map +1 -0
- package/dist/data/traces/ProcessFacetTable.d.ts +21 -0
- package/dist/data/traces/ProcessFacetTable.d.ts.map +1 -0
- package/dist/data/traces/ProcessFacetTable.js +79 -0
- package/dist/data/traces/ProcessFacetTable.js.map +1 -0
- package/dist/data/traces/ProcessRecordViews.cjs +41 -0
- package/dist/data/traces/ProcessRecordViews.cjs.map +1 -0
- package/dist/data/traces/ProcessRecordViews.d.ts +16 -0
- package/dist/data/traces/ProcessRecordViews.d.ts.map +1 -0
- package/dist/data/traces/ProcessRecordViews.js +41 -0
- package/dist/data/traces/ProcessRecordViews.js.map +1 -0
- package/dist/data/traces/ProcessTraceDetail.cjs +316 -0
- package/dist/data/traces/ProcessTraceDetail.cjs.map +1 -0
- package/dist/data/traces/ProcessTraceDetail.d.ts +39 -0
- package/dist/data/traces/ProcessTraceDetail.d.ts.map +1 -0
- package/dist/data/traces/ProcessTraceDetail.js +316 -0
- package/dist/data/traces/ProcessTraceDetail.js.map +1 -0
- package/dist/data/traces/ProcessTraceTree.cjs +76 -34
- package/dist/data/traces/ProcessTraceTree.cjs.map +1 -1
- package/dist/data/traces/ProcessTraceTree.d.ts +21 -4
- package/dist/data/traces/ProcessTraceTree.d.ts.map +1 -1
- package/dist/data/traces/ProcessTraceTree.js +72 -30
- package/dist/data/traces/ProcessTraceTree.js.map +1 -1
- package/dist/data/traces/Sparkline.cjs +27 -5
- package/dist/data/traces/Sparkline.cjs.map +1 -1
- package/dist/data/traces/Sparkline.d.ts +17 -1
- package/dist/data/traces/Sparkline.d.ts.map +1 -1
- package/dist/data/traces/Sparkline.js +28 -6
- package/dist/data/traces/Sparkline.js.map +1 -1
- package/dist/data/traces/TraceView.cjs +9 -0
- package/dist/data/traces/TraceView.cjs.map +1 -1
- package/dist/data/traces/TraceView.d.ts +4 -2
- package/dist/data/traces/TraceView.d.ts.map +1 -1
- package/dist/data/traces/TraceView.js +9 -0
- package/dist/data/traces/TraceView.js.map +1 -1
- package/dist/data/traces/TraceWindowList.cjs +117 -0
- package/dist/data/traces/TraceWindowList.cjs.map +1 -0
- package/dist/data/traces/TraceWindowList.d.ts +48 -0
- package/dist/data/traces/TraceWindowList.d.ts.map +1 -0
- package/dist/data/traces/TraceWindowList.js +117 -0
- package/dist/data/traces/TraceWindowList.js.map +1 -0
- package/dist/data/traces/fixtures.d.ts +25 -1
- package/dist/data/traces/fixtures.d.ts.map +1 -1
- package/dist/data/traces/index.d.ts +10 -3
- package/dist/data/traces/index.d.ts.map +1 -1
- package/dist/data/traces/process-facets.cjs +48 -0
- package/dist/data/traces/process-facets.cjs.map +1 -0
- package/dist/data/traces/process-facets.d.ts +38 -0
- package/dist/data/traces/process-facets.d.ts.map +1 -0
- package/dist/data/traces/process-facets.js +48 -0
- package/dist/data/traces/process-facets.js.map +1 -0
- package/dist/data/traces/process-tree.cjs +36 -4
- package/dist/data/traces/process-tree.cjs.map +1 -1
- package/dist/data/traces/process-tree.d.ts +20 -1
- package/dist/data/traces/process-tree.d.ts.map +1 -1
- package/dist/data/traces/process-tree.js +36 -4
- package/dist/data/traces/process-tree.js.map +1 -1
- package/dist/data/traces/profile-aggregate.cjs +78 -0
- package/dist/data/traces/profile-aggregate.cjs.map +1 -0
- package/dist/data/traces/profile-aggregate.d.ts +41 -0
- package/dist/data/traces/profile-aggregate.d.ts.map +1 -0
- package/dist/data/traces/profile-aggregate.js +78 -0
- package/dist/data/traces/profile-aggregate.js.map +1 -0
- package/dist/data/traces/stack-checkpoints.cjs +22 -0
- package/dist/data/traces/stack-checkpoints.cjs.map +1 -0
- package/dist/data/traces/stack-checkpoints.d.ts +18 -0
- package/dist/data/traces/stack-checkpoints.d.ts.map +1 -0
- package/dist/data/traces/stack-checkpoints.js +22 -0
- package/dist/data/traces/stack-checkpoints.js.map +1 -0
- package/dist/data/traces/types.d.ts +120 -5
- package/dist/data/traces/types.d.ts.map +1 -1
- package/dist/data/verification/adapters.cjs +1 -0
- package/dist/data/verification/adapters.cjs.map +1 -1
- package/dist/data/verification/adapters.d.ts.map +1 -1
- package/dist/data/verification/adapters.js +1 -0
- package/dist/data/verification/adapters.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +85 -7
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +4 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +81 -3
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +87 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +81 -3
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { formatBytes } from "../../lib/format.js";
|
|
4
|
+
import { MetricTile } from "../diagnostics/MetricTile.js";
|
|
5
|
+
import { cpuTone } from "../diagnostics/metric-tone.js";
|
|
6
|
+
import { StackDumpView } from "../diagnostics/StackDumpView.js";
|
|
7
|
+
import { isAttemptLive } from "./attempts.js";
|
|
8
|
+
import { ProcessNotFoundError } from "./process-metrics.js";
|
|
9
|
+
import { formatTestDuration } from "./status.js";
|
|
10
|
+
const POLL_MS = 2e3;
|
|
11
|
+
function useLiveProcessMetrics(pid, enabled, fetchMetrics) {
|
|
12
|
+
const [state, setState] = useState({});
|
|
13
|
+
const [polling, setPolling] = useState(false);
|
|
14
|
+
const fetchRef = useRef(fetchMetrics);
|
|
15
|
+
fetchRef.current = fetchMetrics;
|
|
16
|
+
const active = enabled && pid !== void 0 && pid > 0 && fetchMetrics !== void 0;
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!active || pid === void 0) return;
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
let timer;
|
|
21
|
+
setState({});
|
|
22
|
+
setPolling(true);
|
|
23
|
+
const read = (target) => {
|
|
24
|
+
const current = fetchRef.current;
|
|
25
|
+
if (!current) throw new Error("fetchProcessMetrics was removed while an attempt was being polled");
|
|
26
|
+
return current(target);
|
|
27
|
+
};
|
|
28
|
+
const poll = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const metrics = await read(pid);
|
|
31
|
+
if (cancelled) return;
|
|
32
|
+
setState({ metrics });
|
|
33
|
+
} catch (reason) {
|
|
34
|
+
if (cancelled) return;
|
|
35
|
+
if (reason instanceof ProcessNotFoundError) {
|
|
36
|
+
setState((prev) => ({ ...prev, note: "process exited" }));
|
|
37
|
+
setPolling(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setState((prev) => ({ ...prev, note: reason instanceof Error ? reason.message : String(reason) }));
|
|
41
|
+
}
|
|
42
|
+
timer = setTimeout(poll, POLL_MS);
|
|
43
|
+
};
|
|
44
|
+
void poll();
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
setPolling(false);
|
|
49
|
+
};
|
|
50
|
+
}, [active, pid]);
|
|
51
|
+
return { state, polling };
|
|
52
|
+
}
|
|
53
|
+
function TestAttemptDetail({ attempt, fetchProcessMetrics }) {
|
|
54
|
+
const live = isAttemptLive(attempt);
|
|
55
|
+
const { state, polling } = useLiveProcessMetrics(attempt.pid, live, fetchProcessMetrics);
|
|
56
|
+
const recorded = {
|
|
57
|
+
...attempt.pid !== void 0 ? { pid: attempt.pid } : {},
|
|
58
|
+
...attempt.cpu_percent !== void 0 ? { cpu_percent: attempt.cpu_percent } : {},
|
|
59
|
+
...attempt.rss !== void 0 ? { rss: attempt.rss } : {}
|
|
60
|
+
};
|
|
61
|
+
const shown = live && state.metrics ? state.metrics : recorded;
|
|
62
|
+
const pid = shown.pid ?? attempt.pid;
|
|
63
|
+
const hasAny = (pid ?? 0) > 0 || shown.cpu_percent !== void 0 || shown.rss !== void 0;
|
|
64
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-attempt-sequence": attempt.sequence, children: [
|
|
65
|
+
/* @__PURE__ */ jsxs("section", { children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-2", children: [
|
|
67
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Process metrics" }),
|
|
68
|
+
polling && /* @__PURE__ */ jsx(
|
|
69
|
+
"span",
|
|
70
|
+
{
|
|
71
|
+
className: "size-1.5 animate-pulse rounded-full bg-blue-500",
|
|
72
|
+
role: "status",
|
|
73
|
+
"aria-label": "Reading live metrics"
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
attempt.shared_process && /* @__PURE__ */ jsx(
|
|
77
|
+
"span",
|
|
78
|
+
{
|
|
79
|
+
className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
|
|
80
|
+
title: "The pid, CPU and RSS belong to the go test batch this package ran in, not to the package alone",
|
|
81
|
+
"data-shared-process": "",
|
|
82
|
+
children: "shared by batch"
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] }),
|
|
86
|
+
!hasAny ? /* @__PURE__ */ jsx("div", { className: "text-xs italic text-muted-foreground", children: "No metrics captured for this attempt." }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-density-1 md:grid-cols-4", children: [
|
|
87
|
+
pid !== void 0 && pid > 0 && /* @__PURE__ */ jsx(MetricTile, { label: "PID", value: String(pid) }),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
MetricTile,
|
|
90
|
+
{
|
|
91
|
+
label: "CPU",
|
|
92
|
+
value: shown.cpu_percent !== void 0 ? `${shown.cpu_percent.toFixed(1)}%` : "—",
|
|
93
|
+
...shown.cpu_percent !== void 0 ? { barPct: Math.min(100, shown.cpu_percent), tone: cpuTone(shown.cpu_percent) } : {}
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsx(MetricTile, { label: "RSS", value: shown.rss !== void 0 && shown.rss > 0 ? formatBytes(shown.rss) : "—" }),
|
|
97
|
+
shown.open_files !== void 0 && /* @__PURE__ */ jsx(MetricTile, { label: "FDs", value: String(shown.open_files) }),
|
|
98
|
+
attempt.duration !== void 0 && attempt.duration > 0 && /* @__PURE__ */ jsx(MetricTile, { label: "Duration", value: formatTestDuration(attempt.duration) }),
|
|
99
|
+
attempt.exit_code !== void 0 && /* @__PURE__ */ jsx(MetricTile, { label: "Exit", value: String(attempt.exit_code), tone: attempt.exit_code === 0 ? "success" : "danger" }),
|
|
100
|
+
attempt.goroutine_count !== void 0 && /* @__PURE__ */ jsx(MetricTile, { label: "Goroutines", value: String(attempt.goroutine_count) })
|
|
101
|
+
] }),
|
|
102
|
+
state.note && live && /* @__PURE__ */ jsx("div", { className: "mt-1 text-[11px] italic text-muted-foreground", children: state.note })
|
|
103
|
+
] }),
|
|
104
|
+
attempt.stack_trace && /* @__PURE__ */ jsxs("section", { children: [
|
|
105
|
+
/* @__PURE__ */ jsx("div", { className: "mb-1 text-[10px] uppercase tracking-wide text-muted-foreground", children: "Stack trace" }),
|
|
106
|
+
/* @__PURE__ */ jsx("div", { className: "flex max-h-[32rem] min-h-0 flex-col", children: /* @__PURE__ */ jsx(StackDumpView, { text: attempt.stack_trace }) })
|
|
107
|
+
] })
|
|
108
|
+
] });
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
TestAttemptDetail
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=TestAttemptDetail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestAttemptDetail.js","sources":["../../../src/data/test-runner/TestAttemptDetail.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { formatBytes } from \"../../lib/format\";\nimport { MetricTile } from \"../diagnostics/MetricTile\";\nimport { cpuTone } from \"../diagnostics/metric-tone\";\nimport { StackDumpView } from \"../diagnostics/StackDumpView\";\nimport { isAttemptLive } from \"./attempts\";\nimport {\n ProcessNotFoundError,\n type FetchProcessMetrics,\n type ProcessMetrics,\n} from \"./process-metrics\";\nimport { formatTestDuration } from \"./status\";\nimport type { TestAttempt } from \"./types\";\n\n/** How often a live attempt's process is read. */\nconst POLL_MS = 2000;\n\nexport type TestAttemptDetailProps = {\n attempt: TestAttempt;\n /**\n * Reads the attempt's process while it is live, every two seconds. Polling\n * stops when the attempt finishes or the fetch rejects with\n * `ProcessNotFoundError`; any other rejection is shown and polling goes on.\n */\n fetchProcessMetrics?: FetchProcessMetrics;\n};\n\ntype LiveState = { metrics?: ProcessMetrics; note?: string };\n\n/** Polls `fetchMetrics` for `pid` while `enabled`; the fetch may change identity between renders without restarting the poll. */\nfunction useLiveProcessMetrics(\n pid: number | undefined,\n enabled: boolean,\n fetchMetrics: FetchProcessMetrics | undefined,\n): { state: LiveState; polling: boolean } {\n const [state, setState] = useState<LiveState>({});\n const [polling, setPolling] = useState(false);\n const fetchRef = useRef(fetchMetrics);\n fetchRef.current = fetchMetrics;\n const active = enabled && pid !== undefined && pid > 0 && fetchMetrics !== undefined;\n\n useEffect(() => {\n if (!active || pid === undefined) return;\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n setState({});\n setPolling(true);\n const read = (target: number) => {\n const current = fetchRef.current;\n if (!current) throw new Error(\"fetchProcessMetrics was removed while an attempt was being polled\");\n return current(target);\n };\n const poll = async () => {\n try {\n const metrics = await read(pid);\n if (cancelled) return;\n setState({ metrics });\n } catch (reason) {\n if (cancelled) return;\n if (reason instanceof ProcessNotFoundError) {\n setState((prev) => ({ ...prev, note: \"process exited\" }));\n setPolling(false);\n return;\n }\n setState((prev) => ({ ...prev, note: reason instanceof Error ? reason.message : String(reason) }));\n }\n timer = setTimeout(poll, POLL_MS);\n };\n void poll();\n return () => {\n cancelled = true;\n clearTimeout(timer);\n setPolling(false);\n };\n }, [active, pid]);\n\n return { state, polling };\n}\n\n/**\n * What one attempt of a test measured: its process metrics (pid, CPU, RSS, open\n * files, duration, exit code, goroutines), read live while the attempt runs,\n * and its timeout stack trace. A pid shared by every package of a `go test`\n * batch is labelled as such, since the numbers are the batch's.\n */\nexport function TestAttemptDetail({ attempt, fetchProcessMetrics }: TestAttemptDetailProps) {\n const live = isAttemptLive(attempt);\n const { state, polling } = useLiveProcessMetrics(attempt.pid, live, fetchProcessMetrics);\n const recorded: ProcessMetrics = {\n ...(attempt.pid !== undefined ? { pid: attempt.pid } : {}),\n ...(attempt.cpu_percent !== undefined ? { cpu_percent: attempt.cpu_percent } : {}),\n ...(attempt.rss !== undefined ? { rss: attempt.rss } : {}),\n };\n const shown = live && state.metrics ? state.metrics : recorded;\n const pid = shown.pid ?? attempt.pid;\n const hasAny = (pid ?? 0) > 0 || shown.cpu_percent !== undefined || shown.rss !== undefined;\n\n return (\n <div className=\"space-y-3\" data-attempt-sequence={attempt.sequence}>\n <section>\n <div className=\"mb-1 flex items-center gap-2\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Process metrics</div>\n {polling && (\n <span\n className=\"size-1.5 animate-pulse rounded-full bg-blue-500\"\n role=\"status\"\n aria-label=\"Reading live metrics\"\n />\n )}\n {attempt.shared_process && (\n <span\n className=\"rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\"\n title=\"The pid, CPU and RSS belong to the go test batch this package ran in, not to the package alone\"\n data-shared-process=\"\"\n >\n shared by batch\n </span>\n )}\n </div>\n {!hasAny ? (\n <div className=\"text-xs italic text-muted-foreground\">No metrics captured for this attempt.</div>\n ) : (\n <div className=\"grid grid-cols-2 gap-density-1 md:grid-cols-4\">\n {pid !== undefined && pid > 0 && <MetricTile label=\"PID\" value={String(pid)} />}\n <MetricTile\n label=\"CPU\"\n value={shown.cpu_percent !== undefined ? `${shown.cpu_percent.toFixed(1)}%` : \"—\"}\n {...(shown.cpu_percent !== undefined\n ? { barPct: Math.min(100, shown.cpu_percent), tone: cpuTone(shown.cpu_percent) }\n : {})}\n />\n <MetricTile label=\"RSS\" value={shown.rss !== undefined && shown.rss > 0 ? formatBytes(shown.rss) : \"—\"} />\n {shown.open_files !== undefined && <MetricTile label=\"FDs\" value={String(shown.open_files)} />}\n {attempt.duration !== undefined && attempt.duration > 0 && (\n <MetricTile label=\"Duration\" value={formatTestDuration(attempt.duration)} />\n )}\n {attempt.exit_code !== undefined && (\n <MetricTile label=\"Exit\" value={String(attempt.exit_code)} tone={attempt.exit_code === 0 ? \"success\" : \"danger\"} />\n )}\n {attempt.goroutine_count !== undefined && (\n <MetricTile label=\"Goroutines\" value={String(attempt.goroutine_count)} />\n )}\n </div>\n )}\n {state.note && live && <div className=\"mt-1 text-[11px] italic text-muted-foreground\">{state.note}</div>}\n </section>\n {attempt.stack_trace && (\n <section>\n <div className=\"mb-1 text-[10px] uppercase tracking-wide text-muted-foreground\">Stack trace</div>\n <div className=\"flex max-h-[32rem] min-h-0 flex-col\">\n <StackDumpView text={attempt.stack_trace} />\n </div>\n </section>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const utils = require("../../lib/utils.cjs");
|
|
5
|
+
const status = require("./status.cjs");
|
|
6
|
+
function TestAttempts({ attempts }) {
|
|
7
|
+
if (attempts.length === 0) return null;
|
|
8
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
9
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: [
|
|
10
|
+
"Attempts (",
|
|
11
|
+
attempts.length,
|
|
12
|
+
")"
|
|
13
|
+
] }),
|
|
14
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-border rounded-md border border-border text-xs", children: attempts.map((attempt) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-2 px-2 py-1", children: [
|
|
15
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
16
|
+
"span",
|
|
17
|
+
{
|
|
18
|
+
className: utils.cn(
|
|
19
|
+
"font-medium",
|
|
20
|
+
attempt.failed || attempt.timed_out ? "text-red-600 dark:text-red-400" : attempt.passed ? "text-green-600 dark:text-green-400" : "text-muted-foreground"
|
|
21
|
+
),
|
|
22
|
+
children: [
|
|
23
|
+
"#",
|
|
24
|
+
attempt.sequence,
|
|
25
|
+
" ",
|
|
26
|
+
attempt.run_kind ?? ""
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: attempt.message }),
|
|
31
|
+
attempt.duration ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: status.formatTestDuration(attempt.duration) }) : null
|
|
32
|
+
] }, attempt.sequence)) })
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
35
|
+
exports.TestAttempts = TestAttempts;
|
|
36
|
+
//# sourceMappingURL=TestAttempts.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestAttempts.cjs","sources":["../../../src/data/test-runner/TestAttempts.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { formatTestDuration } from \"./status\";\nimport type { TestAttempt } from \"./types\";\n\nexport function TestAttempts({ attempts }: { attempts: TestAttempt[] }) {\n if (attempts.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n Attempts ({attempts.length})\n </div>\n <ul className=\"divide-y divide-border rounded-md border border-border text-xs\">\n {attempts.map((attempt) => (\n <li key={attempt.sequence} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <span\n className={cn(\n \"font-medium\",\n attempt.failed || attempt.timed_out\n ? \"text-red-600 dark:text-red-400\"\n : attempt.passed\n ? \"text-green-600 dark:text-green-400\"\n : \"text-muted-foreground\",\n )}\n >\n #{attempt.sequence} {attempt.run_kind ?? \"\"}\n </span>\n <span className=\"truncate text-muted-foreground\">{attempt.message}</span>\n {attempt.duration ? (\n <span className=\"shrink-0 text-muted-foreground\">\n {formatTestDuration(attempt.duration)}\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"names":["jsxs","jsx","cn","formatTestDuration"],"mappings":";;;;;AAIO,SAAS,aAAa,EAAE,YAAyC;AACtE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SACEA,2BAAAA,KAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,IAAAA,2BAAAA,KAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA;AAAA,MAAA;AAAA,MAC9D,SAAS;AAAA,MAAO;AAAA,IAAA,GAC7B;AAAA,IACAC,2BAAAA,IAAC,MAAA,EAAG,WAAU,kEACX,UAAA,SAAS,IAAI,CAAC,YACbD,2BAAAA,KAAC,MAAA,EAA0B,WAAU,qDACnC,UAAA;AAAA,MAAAA,2BAAAA;AAAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWE,MAAAA;AAAAA,YACT;AAAA,YACA,QAAQ,UAAU,QAAQ,YACtB,mCACA,QAAQ,SACN,uCACA;AAAA,UAAA;AAAA,UAET,UAAA;AAAA,YAAA;AAAA,YACG,QAAQ;AAAA,YAAS;AAAA,YAAE,QAAQ,YAAY;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAE3CD,2BAAAA,IAAC,QAAA,EAAK,WAAU,kCAAkC,kBAAQ,SAAQ;AAAA,MACjE,QAAQ,WACPA,+BAAC,QAAA,EAAK,WAAU,kCACb,UAAAE,OAAAA,mBAAmB,QAAQ,QAAQ,EAAA,CACtC,IACE;AAAA,IAAA,KAlBG,QAAQ,QAmBjB,CACD,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestAttempts.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestAttempts.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,sCAiCrE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { formatTestDuration } from "./status.js";
|
|
4
|
+
function TestAttempts({ attempts }) {
|
|
5
|
+
if (attempts.length === 0) return null;
|
|
6
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
7
|
+
/* @__PURE__ */ jsxs("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: [
|
|
8
|
+
"Attempts (",
|
|
9
|
+
attempts.length,
|
|
10
|
+
")"
|
|
11
|
+
] }),
|
|
12
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border rounded-md border border-border text-xs", children: attempts.map((attempt) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 px-2 py-1", children: [
|
|
13
|
+
/* @__PURE__ */ jsxs(
|
|
14
|
+
"span",
|
|
15
|
+
{
|
|
16
|
+
className: cn(
|
|
17
|
+
"font-medium",
|
|
18
|
+
attempt.failed || attempt.timed_out ? "text-red-600 dark:text-red-400" : attempt.passed ? "text-green-600 dark:text-green-400" : "text-muted-foreground"
|
|
19
|
+
),
|
|
20
|
+
children: [
|
|
21
|
+
"#",
|
|
22
|
+
attempt.sequence,
|
|
23
|
+
" ",
|
|
24
|
+
attempt.run_kind ?? ""
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
),
|
|
28
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: attempt.message }),
|
|
29
|
+
attempt.duration ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatTestDuration(attempt.duration) }) : null
|
|
30
|
+
] }, attempt.sequence)) })
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
TestAttempts
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=TestAttempts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestAttempts.js","sources":["../../../src/data/test-runner/TestAttempts.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { formatTestDuration } from \"./status\";\nimport type { TestAttempt } from \"./types\";\n\nexport function TestAttempts({ attempts }: { attempts: TestAttempt[] }) {\n if (attempts.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n Attempts ({attempts.length})\n </div>\n <ul className=\"divide-y divide-border rounded-md border border-border text-xs\">\n {attempts.map((attempt) => (\n <li key={attempt.sequence} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <span\n className={cn(\n \"font-medium\",\n attempt.failed || attempt.timed_out\n ? \"text-red-600 dark:text-red-400\"\n : attempt.passed\n ? \"text-green-600 dark:text-green-400\"\n : \"text-muted-foreground\",\n )}\n >\n #{attempt.sequence} {attempt.run_kind ?? \"\"}\n </span>\n <span className=\"truncate text-muted-foreground\">{attempt.message}</span>\n {attempt.duration ? (\n <span className=\"shrink-0 text-muted-foreground\">\n {formatTestDuration(attempt.duration)}\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"names":[],"mappings":";;;AAIO,SAAS,aAAa,EAAE,YAAyC;AACtE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SACE,qBAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA;AAAA,MAAA;AAAA,MAC9D,SAAS;AAAA,MAAO;AAAA,IAAA,GAC7B;AAAA,IACA,oBAAC,MAAA,EAAG,WAAU,kEACX,UAAA,SAAS,IAAI,CAAC,YACb,qBAAC,MAAA,EAA0B,WAAU,qDACnC,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,QAAQ,UAAU,QAAQ,YACtB,mCACA,QAAQ,SACN,uCACA;AAAA,UAAA;AAAA,UAET,UAAA;AAAA,YAAA;AAAA,YACG,QAAQ;AAAA,YAAS;AAAA,YAAE,QAAQ,YAAY;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAE3C,oBAAC,QAAA,EAAK,WAAU,kCAAkC,kBAAQ,SAAQ;AAAA,MACjE,QAAQ,WACP,oBAAC,QAAA,EAAK,WAAU,kCACb,UAAA,mBAAmB,QAAQ,QAAQ,EAAA,CACtC,IACE;AAAA,IAAA,KAlBG,QAAQ,QAmBjB,CACD,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -16,6 +16,7 @@ const status = require("./status.cjs");
|
|
|
16
16
|
const marks = require("./marks.cjs");
|
|
17
17
|
const MarkIcons = require("./MarkIcons.cjs");
|
|
18
18
|
const TestFailureDetail = require("./TestFailureDetail.cjs");
|
|
19
|
+
const TestAttemptDetail = require("./TestAttemptDetail.cjs");
|
|
19
20
|
const UiClass = require("../../icons/components/UiClass.cjs");
|
|
20
21
|
const UiClock = require("../../icons/components/UiClock.cjs");
|
|
21
22
|
const UiRefresh = require("../../icons/components/UiRefresh.cjs");
|
|
@@ -25,14 +26,23 @@ const UiWarningCircle = require("../../icons/components/UiWarningCircle.cjs");
|
|
|
25
26
|
const UiCheck = require("../../icons/components/UiCheck.cjs");
|
|
26
27
|
const UiCopy = require("../../icons/components/UiCopy.cjs");
|
|
27
28
|
function DefaultDetailBody({ node }) {
|
|
28
|
-
var _a;
|
|
29
|
-
const
|
|
29
|
+
var _a, _b;
|
|
30
|
+
const runner = context.useTestRunner();
|
|
31
|
+
const attempt = (_a = node.attempts) == null ? void 0 : _a.at(-1);
|
|
32
|
+
const hasChildren = (((_b = node.children) == null ? void 0 : _b.length) ?? 0) > 0;
|
|
30
33
|
const counts = hasChildren ? status.sum(node) : null;
|
|
31
34
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 p-density-4", children: [
|
|
32
35
|
node.message && !node.failure_detail && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap break-words text-sm", children: node.message }),
|
|
33
36
|
node.failure_detail && /* @__PURE__ */ jsxRuntime.jsx(TestFailureDetail.TestFailureDetail, { failure: node.failure_detail }),
|
|
34
37
|
/* @__PURE__ */ jsxRuntime.jsx(TestFailureDetail.TestOutput, { node }),
|
|
35
|
-
|
|
38
|
+
attempt && /* @__PURE__ */ jsxRuntime.jsx(
|
|
39
|
+
TestAttemptDetail.TestAttemptDetail,
|
|
40
|
+
{
|
|
41
|
+
attempt,
|
|
42
|
+
...runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {}
|
|
43
|
+
},
|
|
44
|
+
`${attempt.run_kind ?? ""}:${attempt.sequence}`
|
|
45
|
+
),
|
|
36
46
|
counts && counts.total > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-3 text-xs text-muted-foreground", children: [
|
|
37
47
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
38
48
|
counts.total,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.cjs","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestAttempts, TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {node.attempts && node.attempts.length > 0 && <TestAttempts attempts={node.attempts} />}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />;\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"names":["sum","jsxs","jsx","TestFailureDetail","TestOutput","TestAttempts","JsonView","copy","UiCopy","UiCheck","UiWarningCircle","useTestRunner","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","_a","Select","cn","TabButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.cjs","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />;\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"names":["useTestRunner","sum","jsxs","jsx","TestFailureDetail","TestOutput","TestAttemptDetail","JsonView","copy","UiCopy","UiCheck","UiWarningCircle","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","_a","Select","cn","TabButton"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"AAiLA;;;;GAIG;AACH,wBAAgB,eAAe,gCAoF9B"}
|
|
@@ -13,7 +13,8 @@ import { buildNodeJson, buildNodeBrief } from "./copy.js";
|
|
|
13
13
|
import { formatTestDuration, sum, outcomeFor } from "./status.js";
|
|
14
14
|
import { toolKeyFor } from "./marks.js";
|
|
15
15
|
import { OutcomeIcon, ToolIcon } from "./MarkIcons.js";
|
|
16
|
-
import { TestFailureDetail, TestOutput
|
|
16
|
+
import { TestFailureDetail, TestOutput } from "./TestFailureDetail.js";
|
|
17
|
+
import { TestAttemptDetail } from "./TestAttemptDetail.js";
|
|
17
18
|
import { UiClass } from "../../icons/components/UiClass.js";
|
|
18
19
|
import { UiClock } from "../../icons/components/UiClock.js";
|
|
19
20
|
import { UiRefresh } from "../../icons/components/UiRefresh.js";
|
|
@@ -23,14 +24,23 @@ import { UiWarningCircle } from "../../icons/components/UiWarningCircle.js";
|
|
|
23
24
|
import { UiCheck } from "../../icons/components/UiCheck.js";
|
|
24
25
|
import { UiCopy } from "../../icons/components/UiCopy.js";
|
|
25
26
|
function DefaultDetailBody({ node }) {
|
|
26
|
-
var _a;
|
|
27
|
-
const
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const runner = useTestRunner();
|
|
29
|
+
const attempt = (_a = node.attempts) == null ? void 0 : _a.at(-1);
|
|
30
|
+
const hasChildren = (((_b = node.children) == null ? void 0 : _b.length) ?? 0) > 0;
|
|
28
31
|
const counts = hasChildren ? sum(node) : null;
|
|
29
32
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-density-4", children: [
|
|
30
33
|
node.message && !node.failure_detail && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-sm", children: node.message }),
|
|
31
34
|
node.failure_detail && /* @__PURE__ */ jsx(TestFailureDetail, { failure: node.failure_detail }),
|
|
32
35
|
/* @__PURE__ */ jsx(TestOutput, { node }),
|
|
33
|
-
|
|
36
|
+
attempt && /* @__PURE__ */ jsx(
|
|
37
|
+
TestAttemptDetail,
|
|
38
|
+
{
|
|
39
|
+
attempt,
|
|
40
|
+
...runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {}
|
|
41
|
+
},
|
|
42
|
+
`${attempt.run_kind ?? ""}:${attempt.sequence}`
|
|
43
|
+
),
|
|
34
44
|
counts && counts.total > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3 text-xs text-muted-foreground", children: [
|
|
35
45
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
36
46
|
counts.total,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.js","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestAttempts, TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {node.attempts && node.attempts.length > 0 && <TestAttempts attempts={node.attempts} />}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />;\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.js","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />;\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
4
5
|
const LogViewer = require("../LogViewer.cjs");
|
|
5
|
-
const
|
|
6
|
+
const stacktrace = require("../diagnostics/stacktrace.cjs");
|
|
7
|
+
const StackDumpView = require("../diagnostics/StackDumpView.cjs");
|
|
6
8
|
const Expectation = require("./Expectation.cjs");
|
|
7
|
-
|
|
9
|
+
function FailureStack({ stack }) {
|
|
10
|
+
const parsed = react.useMemo(() => stacktrace.parseStackDump(stack), [stack]);
|
|
11
|
+
if (parsed.format !== "go") return /* @__PURE__ */ jsxRuntime.jsx(LogViewer.LogViewer, { logs: stack, collapsedLines: 6 });
|
|
12
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-[32rem] min-h-0 flex-col", "data-failure-stack": "go", children: /* @__PURE__ */ jsxRuntime.jsx(StackDumpView.StackDumpView, { text: stack, singleItem: parsed.goroutines.length === 1 }) });
|
|
13
|
+
}
|
|
8
14
|
function TestFailureDetail({ failure }) {
|
|
9
15
|
const hasExpectation = !!(failure.matcher || failure.expected || failure.actual || failure.location);
|
|
10
16
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
@@ -18,7 +24,7 @@ function TestFailureDetail({ failure }) {
|
|
|
18
24
|
location: failure.location
|
|
19
25
|
}
|
|
20
26
|
),
|
|
21
|
-
failure.stack && /* @__PURE__ */ jsxRuntime.jsx(
|
|
27
|
+
failure.stack && /* @__PURE__ */ jsxRuntime.jsx(FailureStack, { stack: failure.stack })
|
|
22
28
|
] });
|
|
23
29
|
}
|
|
24
30
|
const OUTPUT_MAX_HEIGHT = "25cqh";
|
|
@@ -43,32 +49,6 @@ function TestOutput({ node }) {
|
|
|
43
49
|
] })
|
|
44
50
|
] });
|
|
45
51
|
}
|
|
46
|
-
function attemptTone(a) {
|
|
47
|
-
if (a.failed || a.timed_out) return "text-red-600 dark:text-red-400";
|
|
48
|
-
if (a.passed) return "text-green-600 dark:text-green-400";
|
|
49
|
-
return "text-muted-foreground";
|
|
50
|
-
}
|
|
51
|
-
function TestAttempts({ attempts }) {
|
|
52
|
-
if (attempts.length === 0) return null;
|
|
53
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
54
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: [
|
|
55
|
-
"Attempts (",
|
|
56
|
-
attempts.length,
|
|
57
|
-
")"
|
|
58
|
-
] }),
|
|
59
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-border rounded-md border border-border text-xs", children: attempts.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-2 px-2 py-1", children: [
|
|
60
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: utils.cn("font-medium", attemptTone(a)), children: [
|
|
61
|
-
"#",
|
|
62
|
-
a.sequence,
|
|
63
|
-
" ",
|
|
64
|
-
a.run_kind ?? ""
|
|
65
|
-
] }),
|
|
66
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: a.message }),
|
|
67
|
-
a.duration ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: status.formatTestDuration(a.duration) }) : null
|
|
68
|
-
] }, a.sequence)) })
|
|
69
|
-
] });
|
|
70
|
-
}
|
|
71
|
-
exports.TestAttempts = TestAttempts;
|
|
72
52
|
exports.TestFailureDetail = TestFailureDetail;
|
|
73
53
|
exports.TestOutput = TestOutput;
|
|
74
54
|
//# sourceMappingURL=TestFailureDetail.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestFailureDetail.cjs","sources":["../../../src/data/test-runner/TestFailureDetail.tsx"],"sourcesContent":["import { LogViewer } from \"../LogViewer\";\nimport {
|
|
1
|
+
{"version":3,"file":"TestFailureDetail.cjs","sources":["../../../src/data/test-runner/TestFailureDetail.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { LogViewer } from \"../LogViewer\";\nimport { parseStackDump } from \"../diagnostics/stacktrace\";\nimport { StackDumpView } from \"../diagnostics/StackDumpView\";\nimport { Expectation } from \"./Expectation\";\nimport type { FailureDetail, Test } from \"./types\";\n\n/** A failure stack: a Go panic dump as goroutine cards, anything else as a log. */\nfunction FailureStack({ stack }: { stack: string }) {\n const parsed = useMemo(() => parseStackDump(stack), [stack]);\n if (parsed.format !== \"go\") return <LogViewer logs={stack} collapsedLines={6} />;\n return (\n <div className=\"flex max-h-[32rem] min-h-0 flex-col\" data-failure-stack=\"go\">\n <StackDumpView text={stack} singleItem={parsed.goroutines.length === 1} />\n </div>\n );\n}\n\n/**\n * Renders a structured failure: its summary, the expectation, and the stack.\n * A stack that is a Go goroutine dump (a panic) is shown as searchable\n * goroutine cards; any other stack stays a log.\n */\nexport function TestFailureDetail({ failure }: { failure: FailureDetail }) {\n const hasExpectation = !!(\n failure.matcher ||\n failure.expected ||\n failure.actual ||\n failure.location\n );\n return (\n <div className=\"space-y-2\">\n {failure.summary && (\n <p className=\"text-sm font-medium text-red-700 dark:text-red-300\">{failure.summary}</p>\n )}\n {hasExpectation && (\n <Expectation\n check={failure.matcher}\n expected={failure.expected || undefined}\n actual={failure.actual || undefined}\n location={failure.location}\n />\n )}\n {failure.stack && <FailureStack stack={failure.stack} />}\n </div>\n );\n}\n\n// A quarter of the detail pane, which TestDetailPanel makes a size container.\nconst OUTPUT_MAX_HEIGHT = \"25cqh\";\n\n/** stdout / stderr as terminal blocks, each scrolling within a quarter of the detail pane. */\nexport function TestOutput({ node }: { node: Test }) {\n if (!node.stdout && !node.stderr) return null;\n return (\n <div className=\"space-y-3\">\n {node.stdout && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stdout</div>\n <LogViewer logs={node.stdout} variant=\"terminal\" maxHeight={OUTPUT_MAX_HEIGHT} />\n </div>\n )}\n {node.stderr && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stderr</div>\n <LogViewer\n logs={node.stderr}\n variant=\"terminal\"\n maxHeight={OUTPUT_MAX_HEIGHT}\n borderClass=\"border-red-500/70\"\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":["useMemo","parseStackDump","LogViewer","jsx","StackDumpView","jsxs","Expectation"],"mappings":";;;;;;;;AAQA,SAAS,aAAa,EAAE,SAA4B;AAClD,QAAM,SAASA,MAAAA,QAAQ,MAAMC,WAAAA,eAAe,KAAK,GAAG,CAAC,KAAK,CAAC;AAC3D,MAAI,OAAO,WAAW,KAAM,uCAAQC,qBAAA,EAAU,MAAM,OAAO,gBAAgB,EAAA,CAAG;AAC9E,SACEC,2BAAAA,IAAC,OAAA,EAAI,WAAU,uCAAsC,sBAAmB,MACtE,UAAAA,2BAAAA,IAACC,cAAAA,eAAA,EAAc,MAAM,OAAO,YAAY,OAAO,WAAW,WAAW,GAAG,GAC1E;AAEJ;AAOO,SAAS,kBAAkB,EAAE,WAAuC;AACzE,QAAM,iBAAiB,CAAC,EACtB,QAAQ,WACR,QAAQ,YACR,QAAQ,UACR,QAAQ;AAEV,SACEC,2BAAAA,KAAC,OAAA,EAAI,WAAU,aACZ,UAAA;AAAA,IAAA,QAAQ,WACPF,2BAAAA,IAAC,KAAA,EAAE,WAAU,sDAAsD,kBAAQ,SAAQ;AAAA,IAEpF,kBACCA,2BAAAA;AAAAA,MAACG,YAAAA;AAAAA,MAAA;AAAA,QACC,OAAO,QAAQ;AAAA,QACf,UAAU,QAAQ,YAAY;AAAA,QAC9B,QAAQ,QAAQ,UAAU;AAAA,QAC1B,UAAU,QAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,IAGrB,QAAQ,SAASH,+BAAC,cAAA,EAAa,OAAO,QAAQ,MAAA,CAAO;AAAA,EAAA,GACxD;AAEJ;AAGA,MAAM,oBAAoB;AAGnB,SAAS,WAAW,EAAE,QAAwB;AACnD,MAAI,CAAC,KAAK,UAAU,CAAC,KAAK,OAAQ,QAAO;AACzC,SACEE,2BAAAA,KAAC,OAAA,EAAI,WAAU,aACZ,UAAA;AAAA,IAAA,KAAK,UACJA,gCAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,MAAAF,2BAAAA,IAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA,UAAM;AAAA,MACjFA,+BAACD,UAAAA,aAAU,MAAM,KAAK,QAAQ,SAAQ,YAAW,WAAW,kBAAA,CAAmB;AAAA,IAAA,GACjF;AAAA,IAED,KAAK,UACJG,gCAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,MAAAF,2BAAAA,IAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA,UAAM;AAAA,MACjFA,2BAAAA;AAAAA,QAACD,UAAAA;AAAAA,QAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,SAAQ;AAAA,UACR,WAAW;AAAA,UACX,aAAY;AAAA,QAAA;AAAA,MAAA;AAAA,IACd,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;;;"}
|