@outputai/cli 0.4.1-next.d43aa3d.0 → 0.5.0
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/assets/docker/docker-compose-dev.yml +1 -4
- package/dist/generated/framework_version.json +1 -1
- package/dist/views/dev/chrome/footer.d.ts +5 -4
- package/dist/views/dev/chrome/footer.js +12 -2
- package/dist/views/dev/chrome/header.d.ts +2 -1
- package/dist/views/dev/chrome/header.js +18 -47
- package/dist/views/dev/chrome/header.spec.js +6 -46
- package/dist/views/dev/chrome/layout_heights.spec.d.ts +1 -0
- package/dist/views/dev/chrome/layout_heights.spec.js +19 -0
- package/dist/views/dev/chrome/search_bar.d.ts +1 -1
- package/dist/views/dev/chrome/search_bar.js +10 -11
- package/dist/views/dev/chrome/tab_bar.d.ts +8 -1
- package/dist/views/dev/chrome/tab_bar.js +16 -2
- package/dist/views/dev/chrome/toasts.d.ts +1 -0
- package/dist/views/dev/chrome/toasts.js +8 -4
- package/dist/views/dev/components/content_title.d.ts +6 -0
- package/dist/views/dev/components/content_title.js +7 -0
- package/dist/views/dev/components/docker_service_status.d.ts +12 -0
- package/dist/views/dev/components/docker_service_status.js +19 -0
- package/dist/views/dev/components/inline_snippet.d.ts +4 -0
- package/dist/views/dev/components/inline_snippet.js +3 -0
- package/dist/views/dev/components/master_detail_panel.d.ts +9 -8
- package/dist/views/dev/components/master_detail_panel.js +8 -5
- package/dist/views/dev/components/run_info_sidebar.d.ts +7 -0
- package/dist/views/dev/components/run_info_sidebar.js +19 -0
- package/dist/views/dev/components/workflow_status.d.ts +12 -0
- package/dist/views/dev/components/workflow_status.js +19 -0
- package/dist/views/dev/dev_app.js +107 -31
- package/dist/views/dev/hooks/use_run_detail.js +6 -9
- package/dist/views/dev/hooks/use_run_detail.spec.js +7 -0
- package/dist/views/dev/modals/expanded_json_modal.js +5 -6
- package/dist/views/dev/modals/modal_frame.d.ts +13 -0
- package/dist/views/dev/modals/modal_frame.js +13 -0
- package/dist/views/dev/modals/run_modal.js +23 -13
- package/dist/views/dev/{panels/run_detail_view.d.ts → modals/steps_modal.d.ts} +2 -1
- package/dist/views/dev/modals/steps_modal.js +102 -0
- package/dist/views/dev/panels/help_panel.d.ts +14 -0
- package/dist/views/dev/panels/help_panel.js +19 -21
- package/dist/views/dev/panels/runs_panel.d.ts +6 -2
- package/dist/views/dev/panels/runs_panel.js +82 -83
- package/dist/views/dev/panels/runs_panel.spec.js +1 -28
- package/dist/views/dev/panels/services_panel.d.ts +6 -0
- package/dist/views/dev/panels/services_panel.js +53 -62
- package/dist/views/dev/panels/workflows_panel.d.ts +6 -0
- package/dist/views/dev/panels/workflows_panel.js +21 -29
- package/dist/views/dev/panels/workflows_panel.spec.d.ts +1 -0
- package/dist/views/dev/panels/workflows_panel.spec.js +39 -0
- package/dist/views/dev/state/ui_state.d.ts +7 -3
- package/dist/views/dev/state/ui_state.js +23 -6
- package/dist/views/dev/utils/constants.d.ts +2 -2
- package/dist/views/dev/utils/constants.js +2 -2
- package/dist/views/dev/utils/json_editor.js +3 -3
- package/dist/views/dev/utils/json_render.d.ts +2 -0
- package/dist/views/dev/utils/json_render.js +48 -6
- package/dist/views/dev/utils/json_render.spec.js +9 -1
- package/dist/views/dev/utils/panel_helpers.d.ts +15 -0
- package/dist/views/dev/utils/panel_helpers.js +30 -0
- package/dist/views/dev/utils/panel_helpers.spec.js +46 -1
- package/package.json +4 -4
- package/dist/components/status_icon.d.ts +0 -11
- package/dist/components/status_icon.js +0 -25
- package/dist/views/dev/chrome/divider.d.ts +0 -8
- package/dist/views/dev/chrome/divider.js +0 -16
- package/dist/views/dev/panels/run_detail_view.js +0 -112
|
@@ -1,53 +1,51 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
2
|
import { Box, Text, useInput } from 'ink';
|
|
4
3
|
import { config } from '#config.js';
|
|
5
4
|
import { openUrl } from '#utils/open_url.js';
|
|
6
|
-
import { Footer } from '#views/dev/chrome/footer.js';
|
|
7
5
|
import { SelectionIndicator } from '#views/dev/chrome/selection_indicator.js';
|
|
8
6
|
import { useUiState } from '#views/dev/state/ui_state.js';
|
|
7
|
+
import { useListSelection } from '#views/dev/utils/panel_helpers.js';
|
|
8
|
+
import { InlineSnippet } from '../components/inline_snippet.js';
|
|
9
9
|
const DOCS_URL = 'https://docs.output.ai';
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
10
|
+
export const Section = ({ children, title, direction = 'v' }) => (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: title }), _jsx(Box, { flexDirection: direction === 'v' ? 'column' : 'row', gap: 1, flexWrap: 'wrap', children: children })] }));
|
|
11
|
+
export const SubSection = ({ children, title }) => (_jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "single", borderColor: "blackBright", paddingLeft: 1, paddingRight: 1, children: [_jsx(Text, { italic: true, dimColor: true, children: title }), _jsx(Box, { flexDirection: "column", children: children })] }));
|
|
12
|
+
const KV = ({ label, value }) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 26, children: _jsx(Text, { children: label }) }), _jsx(Text, { bold: true, children: value })] }));
|
|
13
|
+
const RunFromCli = () => (_jsxs(Section, { title: "Running a workflow", children: [_jsxs(SubSection, { title: "From the CLI", children: [_jsx(InlineSnippet, { content: "npx output workflow run blog_evaluator paulgraham_hwh" }), _jsx(InlineSnippet, { content: 'npx output workflow run simple --input {"values":[1,2,3]}' }), _jsx(InlineSnippet, { content: "npx output workflow run simple --input scenario.json" })] }), _jsxs(SubSection, { title: "From the TUI", children: [_jsxs(Text, { children: ["Open Workflows tab, hover a workflow, press ", _jsx(Text, { bold: true, children: "r" }), "."] }), _jsx(Text, { children: "Create a custom input from the TUI using the editor with live JSON validation or select an existing scenario." })] })] }));
|
|
14
|
+
const ServiceUrls = () => (_jsx(Section, { title: "Service URLs", children: _jsxs(SubSection, { title: "Where the services are available", children: [_jsx(KV, { label: "Temporal gRPC", value: "localhost:7233" }), _jsx(KV, { label: "Temporal UI", value: "http://localhost:8080" }), _jsx(KV, { label: "API server", value: "localhost:3001" }), _jsx(KV, { label: "Redis", value: "localhost:6379" })] }) }));
|
|
15
|
+
const UpdatingMigrating = () => (_jsxs(Section, { title: "Updating / Migrating", children: [_jsxs(SubSection, { title: "Update", children: [_jsx(Text, { wrap: "wrap", children: "Update the CLI to the latest published version:" }), _jsx(InlineSnippet, { content: "output update" })] }), _jsxs(SubSection, { title: "Migrate", children: [_jsx(Text, { wrap: "wrap", children: "Migrate a workflow project to the SDK version this CLI ships with:" }), _jsx(InlineSnippet, { content: "output migrate" }), _jsx(Text, { wrap: "wrap", children: "The migration walks `package.json` and project files, updates `@outputai/*` deps, and applies any code-mod steps the SDK ships with the new version." })] })] }));
|
|
16
|
+
const ClaudePlugins = () => (_jsx(Section, { title: "Claude Plugins", children: _jsxs(SubSection, { title: "Reinstall", children: [_jsx(Text, { wrap: "wrap", children: "Command `output init` already installs the Claude Code plugins (skills, commands, agents) into your project during scaffolding." }), _jsx(Text, { wrap: "wrap", children: "But if it is necessary to reinstall it, use the update command:" }), _jsx(InlineSnippet, { content: "output update --agents" }), _jsx(Text, { children: "This pulls the latest plugin bundle that ships with the installed CLI version." })] }) }));
|
|
16
17
|
const Troubleshooting = () => {
|
|
17
18
|
const logsCommand = `docker compose -p ${config.dockerServiceName} logs -f <service>`;
|
|
18
|
-
return (_jsxs(
|
|
19
|
+
return (_jsxs(Section, { title: "Troubleshooting", children: [_jsxs(SubSection, { title: "Worker won't start", children: [_jsx(Text, { children: "If the worker won't start, rebuild the local image:" }), _jsx(InlineSnippet, { content: "output fix" })] }), _jsxs(SubSection, { title: "I need to see more logs", children: [_jsx(Text, { children: "Tail a service log from the shell:" }), _jsx(InlineSnippet, { content: logsCommand })] }), _jsxs(SubSection, { title: "Force pull-images", children: [_jsx(Text, { children: "If the images get stale and a fresh start is necessary, force pull with:" }), _jsx(InlineSnippet, { content: "output dev --image-pull-policy always" })] })] }));
|
|
19
20
|
};
|
|
20
21
|
const SECTIONS = [
|
|
21
22
|
{ id: 'cli', title: 'Run from CLI', body: RunFromCli },
|
|
22
|
-
{ id: 'hotkeys', title: 'Hotkeys', body: Hotkeys },
|
|
23
23
|
{ id: 'urls', title: 'Service URLs', body: ServiceUrls },
|
|
24
24
|
{ id: 'updating', title: 'Updating / Migrating', body: UpdatingMigrating },
|
|
25
25
|
{ id: 'claude-plugins', title: 'Claude Plugins', body: ClaudePlugins },
|
|
26
26
|
{ id: 'troubleshooting', title: 'Troubleshooting', body: Troubleshooting }
|
|
27
27
|
];
|
|
28
|
-
const
|
|
28
|
+
export const HELP_HINTS = [
|
|
29
29
|
{ key: '↑/↓', label: 'navigate' },
|
|
30
|
-
{ key: 'd', label: 'docs' }
|
|
31
|
-
{ key: 'tab', label: 'next tab' },
|
|
32
|
-
{ key: 'ctrl+c', label: 'quit' }
|
|
30
|
+
{ key: 'd', label: 'docs' }
|
|
33
31
|
];
|
|
32
|
+
export const HELP_SECTION_COUNT = SECTIONS.length;
|
|
34
33
|
export const HelpPanel = () => {
|
|
35
34
|
const ui = useUiState();
|
|
36
|
-
const
|
|
37
|
-
const isActive = ui.tab === 'help' && !ui.search.open && !ui.runModal.open;
|
|
35
|
+
const { selectedIndex: index, selectPrevious, selectNext } = useListSelection(SECTIONS.length);
|
|
38
36
|
useInput((input, key) => {
|
|
39
37
|
if (key.upArrow) {
|
|
40
|
-
|
|
38
|
+
selectPrevious();
|
|
41
39
|
return;
|
|
42
40
|
}
|
|
43
41
|
if (key.downArrow) {
|
|
44
|
-
|
|
42
|
+
selectNext();
|
|
45
43
|
return;
|
|
46
44
|
}
|
|
47
45
|
if (input === 'd') {
|
|
48
46
|
openUrl(DOCS_URL);
|
|
49
47
|
}
|
|
50
|
-
}, { isActive });
|
|
51
|
-
const
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "
|
|
48
|
+
}, { isActive: ui.tab === 'help' && !ui.search.open });
|
|
49
|
+
const ActiveSection = SECTIONS[index].body;
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingRight: 2, children: SECTIONS.map((section, i) => (_jsxs(Box, { children: [_jsx(SelectionIndicator, { selected: i === index }), _jsxs(Text, { bold: i === index, dimColor: i !== index, children: [' ', section.title] })] }, section.id))) }), _jsx(ActiveSection, {})] }));
|
|
53
51
|
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { WorkflowRun } from '#services/workflow_runs.js';
|
|
3
|
-
import type { TraceData } from '#types/trace.js';
|
|
4
3
|
export declare const buildVisibleRuns: (runs: WorkflowRun[], query: string) => WorkflowRun[];
|
|
5
|
-
export declare const
|
|
4
|
+
export declare const RUNS_HINTS: {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}[];
|
|
8
|
+
export declare const RUNS_EMPTY_HINTS: never[];
|
|
6
9
|
export declare const RunsPanel: React.FC<{
|
|
7
10
|
runs: WorkflowRun[];
|
|
11
|
+
height: number;
|
|
8
12
|
}>;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import {
|
|
5
|
-
import { elapsedMs, formatDurationCompact
|
|
4
|
+
import { WorkflowStatusIcon, workflowStatusColor } from '#views/dev/components/workflow_status.js';
|
|
5
|
+
import { elapsedMs, formatDurationCompact } from '#utils/date_formatter.js';
|
|
6
6
|
import { openUrl } from '#utils/open_url.js';
|
|
7
|
-
import {
|
|
7
|
+
import { TabBar, getHeight as getTabBarHeight } from '#views/dev/chrome/tab_bar.js';
|
|
8
|
+
import { ContentTitle, getHeight as getContentTitleHeight } from '#views/dev/components/content_title.js';
|
|
8
9
|
import { LoadingSpinner } from '#views/dev/chrome/loading_spinner.js';
|
|
9
10
|
import { SelectionIndicator } from '#views/dev/chrome/selection_indicator.js';
|
|
10
11
|
import { useUiState } from '#views/dev/state/ui_state.js';
|
|
11
|
-
import { RunDetailView } from '#views/dev/panels/run_detail_view.js';
|
|
12
12
|
import { useRunDetail } from '#views/dev/hooks/use_run_detail.js';
|
|
13
13
|
import { JsonView } from '#views/dev/utils/json_render.js';
|
|
14
|
+
import { RunInfoSidebar } from '#views/dev/components/run_info_sidebar.js';
|
|
14
15
|
import { MasterDetailPanel } from '#views/dev/components/master_detail_panel.js';
|
|
15
|
-
import {
|
|
16
|
-
import { CATALOG_WORKFLOW_NAME, RUNS_VISIBLE_ROWS
|
|
16
|
+
import { capitalize, cycleValue, formatContentTitle, formatStartedShort, hasJsonValue, truncate, useListSelection } from '#views/dev/utils/panel_helpers.js';
|
|
17
|
+
import { CATALOG_WORKFLOW_NAME, RUNS_VISIBLE_ROWS } from '#views/dev/utils/constants.js';
|
|
17
18
|
const TEMPORAL_UI_BASE = 'http://localhost:8080';
|
|
18
19
|
const STATUS_ORDER = {
|
|
19
20
|
running: 0,
|
|
@@ -47,19 +48,6 @@ export const buildVisibleRuns = (runs, query) => {
|
|
|
47
48
|
const filtered = query ? visible.filter(r => matchesFilter(r, query)) : visible;
|
|
48
49
|
return sortRuns(filtered);
|
|
49
50
|
};
|
|
50
|
-
export const extractRunInput = (trace) => {
|
|
51
|
-
if (!trace?.children) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const firstChild = trace.children[0];
|
|
55
|
-
if (!firstChild) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
if (firstChild.input !== undefined) {
|
|
59
|
-
return firstChild.input;
|
|
60
|
-
}
|
|
61
|
-
return firstChild.details?.input ?? null;
|
|
62
|
-
};
|
|
63
51
|
const COL = {
|
|
64
52
|
indicator: 3,
|
|
65
53
|
icon: 3,
|
|
@@ -69,59 +57,86 @@ const COL = {
|
|
|
69
57
|
duration: 9,
|
|
70
58
|
started: 14
|
|
71
59
|
};
|
|
72
|
-
const
|
|
60
|
+
const RUN_INFO_TABS = [
|
|
61
|
+
{ id: 'status', label: 'Status' },
|
|
62
|
+
{ id: 'input', label: 'Input' },
|
|
63
|
+
{ id: 'output', label: 'Output' },
|
|
64
|
+
{ id: 'attributes', label: 'Attributes' },
|
|
65
|
+
{ id: 'aggregations', label: 'Aggregations' }
|
|
66
|
+
];
|
|
67
|
+
const RUN_INFO_TAB_ORDER = ['status', 'input', 'output', 'attributes', 'aggregations'];
|
|
68
|
+
const HeaderRow = () => (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(Text, { children: "\u00A0" }) }), _jsx(Box, { width: COL.icon, children: _jsx(Text, { children: "\u00A0" }) }), _jsx(Box, { width: COL.status, children: _jsx(Text, { dimColor: true, bold: true, children: "STATUS" }) }), _jsx(Box, { width: COL.type, children: _jsx(Text, { dimColor: true, bold: true, children: "TYPE" }) }), _jsx(Box, { width: COL.id, children: _jsx(Text, { dimColor: true, bold: true, children: "ID" }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, bold: true, children: "DURATION" }) }), _jsx(Box, { width: COL.started, marginLeft: 2, children: _jsx(Text, { dimColor: true, bold: true, children: "STARTED" }) })] }));
|
|
73
69
|
const RunRow = ({ run, selected }) => {
|
|
74
70
|
const status = run.status ?? 'running';
|
|
75
|
-
const color =
|
|
71
|
+
const color = workflowStatusColor(status);
|
|
76
72
|
const duration = run.startedAt ? formatDurationCompact(elapsedMs(run.startedAt, run.completedAt)) : '-';
|
|
77
|
-
return (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(SelectionIndicator, { selected: selected }) }), _jsx(Box, { width: COL.icon, children: _jsx(
|
|
73
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(SelectionIndicator, { selected: selected }) }), _jsx(Box, { width: COL.icon, children: _jsx(WorkflowStatusIcon, { status: status }) }), _jsx(Box, { width: COL.status, children: _jsx(Text, { color: color, children: status }) }), _jsx(Box, { width: COL.type, children: _jsx(Text, { bold: selected, children: truncate(run.workflowType ?? '-', COL.type - 1) }) }), _jsx(Box, { width: COL.id, children: _jsx(Text, { dimColor: !selected, children: truncate(run.workflowId ?? '-', COL.id - 1) }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: !selected, children: duration }) }), _jsx(Box, { width: COL.started, marginLeft: 2, children: _jsx(Text, { dimColor: !selected, children: formatStartedShort(run.startedAt) }) })] }));
|
|
74
|
+
};
|
|
75
|
+
const statusPaneValue = (run, pane) => ({
|
|
76
|
+
status: pane.status,
|
|
77
|
+
runId: run.runId,
|
|
78
|
+
workflowId: run.workflowId,
|
|
79
|
+
workflowType: run.workflowType,
|
|
80
|
+
startedAt: run.startedAt,
|
|
81
|
+
completedAt: run.completedAt
|
|
82
|
+
});
|
|
83
|
+
const runPaneValue = (run, pane, activePane) => {
|
|
84
|
+
if (activePane === 'status') {
|
|
85
|
+
return statusPaneValue(run, pane);
|
|
86
|
+
}
|
|
87
|
+
if (activePane === 'input') {
|
|
88
|
+
return pane.input;
|
|
89
|
+
}
|
|
90
|
+
if (activePane === 'output') {
|
|
91
|
+
return pane.error ?? pane.output;
|
|
92
|
+
}
|
|
93
|
+
if (activePane === 'attributes') {
|
|
94
|
+
return pane.attributes;
|
|
95
|
+
}
|
|
96
|
+
return pane.aggregations;
|
|
78
97
|
};
|
|
79
|
-
const
|
|
80
|
-
const InlineKV = ({ label, value }) => (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [label, ": "] }), _jsx(Text, { children: value })] }));
|
|
81
|
-
const DetailPane = ({ run, pane }) => {
|
|
98
|
+
const DetailPane = ({ run, pane, rows }) => {
|
|
82
99
|
const ui = useUiState();
|
|
83
100
|
if (!run || !pane) {
|
|
84
101
|
return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Select a run to see details." }) }));
|
|
85
102
|
}
|
|
86
|
-
const {
|
|
87
|
-
const
|
|
88
|
-
const
|
|
103
|
+
const { loading } = pane;
|
|
104
|
+
const activePane = ui.runListPaneTab;
|
|
105
|
+
const tabContentRows = Math.max(1, rows - getContentTitleHeight() - getTabBarHeight());
|
|
106
|
+
const tabs = hasJsonValue(pane.error) ?
|
|
107
|
+
RUN_INFO_TABS.map(tab => tab.id === 'output' ? { ...tab, label: 'Error' } : tab) :
|
|
108
|
+
RUN_INFO_TABS;
|
|
89
109
|
const renderPane = () => {
|
|
90
|
-
if (activePane === '
|
|
91
|
-
|
|
92
|
-
return _jsx(LoadingSpinner, {});
|
|
93
|
-
}
|
|
94
|
-
return _jsx(JsonView, { value: runInput, maxLines: RUNS_PREVIEW_LINES });
|
|
110
|
+
if (activePane === 'status') {
|
|
111
|
+
return _jsx(RunInfoSidebar, { run: run, resultStatus: pane.status, maxRows: tabContentRows });
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
if (runOutput === undefined || runOutput === null) {
|
|
113
|
+
const value = runPaneValue(run, pane, activePane);
|
|
114
|
+
if (value === undefined || value === null) {
|
|
100
115
|
if (loading) {
|
|
101
116
|
return _jsx(LoadingSpinner, {});
|
|
102
117
|
}
|
|
103
|
-
return _jsx(Text, { dimColor: true, children: "
|
|
118
|
+
return _jsx(Text, { dimColor: true, children: "\u2014" });
|
|
119
|
+
}
|
|
120
|
+
if (activePane === 'output' && hasJsonValue(pane.error)) {
|
|
121
|
+
const lines = String(pane.error).split('\n').slice(0, tabContentRows);
|
|
122
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: "red", wrap: "truncate-end", children: line }, i))) }));
|
|
104
123
|
}
|
|
105
|
-
return _jsx(JsonView, { value:
|
|
124
|
+
return _jsx(JsonView, { value: value, maxLines: tabContentRows, truncateLine: true });
|
|
106
125
|
};
|
|
107
|
-
|
|
108
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: heading }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(StatusIcon, { status: status }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: statusColor(status), children: status.toUpperCase() }), _jsx(Text, { dimColor: true, children: " " }), _jsx(InlineKV, { label: "DURATION", value: duration }), _jsx(Text, { dimColor: true, children: " " }), _jsx(InlineKV, { label: "STARTED", value: formatDate(run.startedAt) }), _jsx(Text, { dimColor: true, children: " " }), _jsx(InlineKV, { label: "COMPLETED", value: run.completedAt ? formatDate(run.completedAt) : '—' })] }), _jsx(Box, { marginTop: 1, children: _jsx(PaneTabs, { active: activePane }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: renderPane() })] }));
|
|
126
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(ContentTitle, { title: formatContentTitle([`Workflow "${run.workflowType}"`, 'Result']) }), _jsx(TabBar, { active: activePane, items: tabs }), renderPane()] }));
|
|
109
127
|
};
|
|
110
|
-
const
|
|
128
|
+
export const RUNS_HINTS = [
|
|
111
129
|
{ key: '↑/↓', label: 'navigate' },
|
|
112
130
|
{ key: 'enter', label: 'open' },
|
|
113
131
|
{ key: '←/→', label: 'switch pane' },
|
|
114
132
|
{ key: 'e', label: 'expand' },
|
|
115
|
-
{ key: 'o', label: 'temporal' }
|
|
116
|
-
{ key: '/', label: 'filter' },
|
|
117
|
-
{ key: 'tab', label: 'next tab' }
|
|
133
|
+
{ key: 'o', label: 'temporal' }
|
|
118
134
|
];
|
|
119
|
-
export const
|
|
135
|
+
export const RUNS_EMPTY_HINTS = [];
|
|
136
|
+
export const RunsPanel = ({ runs, height }) => {
|
|
120
137
|
const ui = useUiState();
|
|
121
138
|
const filteredRuns = useMemo(() => buildVisibleRuns(runs, ui.search.query), [runs, ui.search.query]);
|
|
122
|
-
|
|
123
|
-
// run after the expanded-JSON modal unmounts and remounts the panel.
|
|
124
|
-
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
139
|
+
const initialIndex = () => {
|
|
125
140
|
const previousRunId = ui.selection.runId;
|
|
126
141
|
if (!previousRunId) {
|
|
127
142
|
return 0;
|
|
@@ -129,23 +144,19 @@ export const RunsPanel = ({ runs }) => {
|
|
|
129
144
|
const initial = buildVisibleRuns(runs, ui.search.query);
|
|
130
145
|
const i = initial.findIndex(r => r.runId === previousRunId);
|
|
131
146
|
return i >= 0 ? i : 0;
|
|
132
|
-
}
|
|
133
|
-
const
|
|
134
|
-
const clampedIndex = Math.min(selectedIndex, Math.max(0, filteredRuns.length - 1));
|
|
147
|
+
};
|
|
148
|
+
const { selectedIndex: clampedIndex, selectPrevious, selectNext } = useListSelection(filteredRuns.length, initialIndex);
|
|
135
149
|
const selectedRun = filteredRuns[clampedIndex];
|
|
136
|
-
const { result,
|
|
150
|
+
const { result, loading } = useRunDetail(selectedRun?.workflowId, selectedRun?.runId, selectedRun?.status);
|
|
137
151
|
const pane = selectedRun ? {
|
|
138
|
-
input:
|
|
152
|
+
input: result?.input,
|
|
139
153
|
output: result?.output,
|
|
140
154
|
error: result?.error,
|
|
155
|
+
attributes: result?.attributes,
|
|
156
|
+
aggregations: result?.aggregations,
|
|
141
157
|
status: result?.status ?? selectedRun.status ?? 'unknown',
|
|
142
158
|
loading
|
|
143
159
|
} : null;
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (clampedIndex !== selectedIndex) {
|
|
146
|
-
setSelectedIndex(clampedIndex);
|
|
147
|
-
}
|
|
148
|
-
}, [clampedIndex, selectedIndex]);
|
|
149
160
|
const setSelection = ui.setSelection;
|
|
150
161
|
useEffect(() => {
|
|
151
162
|
setSelection({
|
|
@@ -156,11 +167,11 @@ export const RunsPanel = ({ runs }) => {
|
|
|
156
167
|
}, [selectedRun?.runId, selectedRun?.workflowId, selectedRun?.workflowType, setSelection]);
|
|
157
168
|
useInput((input, key) => {
|
|
158
169
|
if (key.upArrow) {
|
|
159
|
-
|
|
170
|
+
selectPrevious();
|
|
160
171
|
return;
|
|
161
172
|
}
|
|
162
173
|
if (key.downArrow) {
|
|
163
|
-
|
|
174
|
+
selectNext();
|
|
164
175
|
return;
|
|
165
176
|
}
|
|
166
177
|
if (input === 'o' && selectedRun?.workflowId) {
|
|
@@ -172,33 +183,21 @@ export const RunsPanel = ({ runs }) => {
|
|
|
172
183
|
return;
|
|
173
184
|
}
|
|
174
185
|
if (key.leftArrow || key.rightArrow) {
|
|
175
|
-
ui.
|
|
186
|
+
ui.setRunListPaneTab(cycleValue(RUN_INFO_TAB_ORDER, ui.runListPaneTab, key.rightArrow ? 1 : -1));
|
|
176
187
|
return;
|
|
177
188
|
}
|
|
178
189
|
if (input === 'e' && pane) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
ui.openExpandedJson(content, label);
|
|
190
|
+
const activePane = ui.runListPaneTab;
|
|
191
|
+
const content = selectedRun ? runPaneValue(selectedRun, pane, activePane) : null;
|
|
192
|
+
const title = formatContentTitle(['Recent Runs', `Workflow "${selectedRun?.workflowType ?? ''}"`, capitalize(activePane)]);
|
|
193
|
+
ui.openExpandedJson(content, title);
|
|
184
194
|
}
|
|
185
|
-
}, { isActive });
|
|
186
|
-
const detailRun = ui.runsView === 'detail' ?
|
|
187
|
-
(runs.find(r => r.runId === ui.selection.runId && r.workflowId === ui.selection.workflowId) ?? selectedRun) :
|
|
188
|
-
undefined;
|
|
189
|
-
useEffect(() => {
|
|
190
|
-
if (ui.runsView === 'detail' && !detailRun) {
|
|
191
|
-
ui.setRunsView('list');
|
|
192
|
-
}
|
|
193
|
-
}, [ui, detailRun]);
|
|
194
|
-
if (ui.runsView === 'detail' && detailRun) {
|
|
195
|
-
return _jsx(RunDetailView, { run: detailRun });
|
|
196
|
-
}
|
|
195
|
+
}, { isActive: ui.tab === 'runs' && ui.runsView === 'list' && !ui.search.open });
|
|
197
196
|
if (runs.length === 0) {
|
|
198
|
-
return (
|
|
197
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { dimColor: true, children: "No runs yet. Trigger one from the Workflows tab or with `output workflow run \u2026`." }) }));
|
|
199
198
|
}
|
|
200
199
|
if (filteredRuns.length === 0) {
|
|
201
|
-
return (
|
|
200
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { dimColor: true, children: ["No runs match `", ui.search.query, "`. Press ", _jsx(Text, { bold: true, children: "esc" }), " to clear the filter."] }) }));
|
|
202
201
|
}
|
|
203
|
-
return (_jsx(MasterDetailPanel, { items: filteredRuns, selectedIndex: clampedIndex, visibleRows: RUNS_VISIBLE_ROWS, renderHeader: () => _jsx(HeaderRow, {}), renderRow: (run, selected) => _jsx(RunRow, { run: run, selected: selected }), rowKey: (run, i) => `${run.workflowId}-${run.runId ?? run.startedAt}-${i}`, detail: _jsx(DetailPane, { run: selectedRun, pane: pane
|
|
202
|
+
return (_jsx(MasterDetailPanel, { items: filteredRuns, selectedIndex: clampedIndex, height: height, visibleRows: RUNS_VISIBLE_ROWS, renderHeader: () => _jsx(HeaderRow, {}), renderRow: (run, selected) => _jsx(RunRow, { run: run, selected: selected }), rowKey: (run, i) => `${run.workflowId}-${run.runId ?? run.startedAt}-${i}`, detail: ({ detailRows }) => _jsx(DetailPane, { run: selectedRun, pane: pane, rows: detailRows }) }));
|
|
204
203
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildVisibleRuns
|
|
2
|
+
import { buildVisibleRuns } from './runs_panel.js';
|
|
3
3
|
const run = (overrides) => ({
|
|
4
4
|
workflowId: 'wf',
|
|
5
5
|
workflowType: 'demo',
|
|
@@ -53,30 +53,3 @@ describe('buildVisibleRuns', () => {
|
|
|
53
53
|
expect(buildVisibleRuns(runs, 'no-match')).toHaveLength(0);
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
-
describe('extractRunInput', () => {
|
|
57
|
-
it('returns null when the trace has no children', () => {
|
|
58
|
-
expect(extractRunInput(null)).toBeNull();
|
|
59
|
-
expect(extractRunInput({ root: { workflowName: 'x', workflowId: 'y', startTime: 0 }, children: [] })).toBeNull();
|
|
60
|
-
});
|
|
61
|
-
it('reads from the first child input field directly', () => {
|
|
62
|
-
const trace = {
|
|
63
|
-
root: { workflowName: 'x', workflowId: 'y', startTime: 0 },
|
|
64
|
-
children: [{ input: { foo: 1 } }]
|
|
65
|
-
};
|
|
66
|
-
expect(extractRunInput(trace)).toEqual({ foo: 1 });
|
|
67
|
-
});
|
|
68
|
-
it('falls back to details.input when the top-level input is missing', () => {
|
|
69
|
-
const trace = {
|
|
70
|
-
root: { workflowName: 'x', workflowId: 'y', startTime: 0 },
|
|
71
|
-
children: [{ details: { input: { bar: 2 } } }]
|
|
72
|
-
};
|
|
73
|
-
expect(extractRunInput(trace)).toEqual({ bar: 2 });
|
|
74
|
-
});
|
|
75
|
-
it('returns null when neither input source is set', () => {
|
|
76
|
-
const trace = {
|
|
77
|
-
root: { workflowName: 'x', workflowId: 'y', startTime: 0 },
|
|
78
|
-
children: [{ name: 'first-step' }]
|
|
79
|
-
};
|
|
80
|
-
expect(extractRunInput(trace)).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -7,7 +7,13 @@ import type { Phase } from '#views/dev/dev_app.js';
|
|
|
7
7
|
* the comparator three lines.
|
|
8
8
|
*/
|
|
9
9
|
export declare const compareService: (a: ServiceStatus, b: ServiceStatus) => number;
|
|
10
|
+
export declare const SERVICES_HINTS: {
|
|
11
|
+
key: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}[];
|
|
14
|
+
export declare const SERVICES_BOOT_HINTS: never[];
|
|
10
15
|
export declare const ServicesPanel: React.FC<{
|
|
16
|
+
height: number;
|
|
11
17
|
phase: Phase;
|
|
12
18
|
services: ServiceStatus[];
|
|
13
19
|
dockerComposePath: string;
|