github-issue-tower-defence-management 2.11.0 → 2.12.1
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/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/console/consoleTabNames.js +1 -0
- package/bin/adapter/entry-points/console/consoleTabNames.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-D6QOQvoN.js +88 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +14 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleTabNames.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +41 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +52 -2
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +5 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +9 -2
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +37 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +13 -2
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +50 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +98 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +66 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +69 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +101 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +75 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOkAndAwaitingWorkspaceActions.test.tsx +12 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOkAndAwaitingWorkspaceActions.tsx +1 -9
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +71 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +2 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +26 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +6 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +12 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +11 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +58 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +25 -9
- package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +14 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-D6QOQvoN.js +88 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +84 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +39 -0
- package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +13 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BSa2JTna.js +0 -88
- package/src/adapter/entry-points/console/ui-dist/assets/index-BSa2JTna.js +0 -88
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { buildConsoleListRows } from '../../logic/grouping';
|
|
3
|
+
import {
|
|
4
|
+
consoleAgentOptionsFixture,
|
|
5
|
+
consoleListItemsFixture,
|
|
6
|
+
consoleStatusOptionsFixture,
|
|
7
|
+
consoleStoryColorsFixture,
|
|
8
|
+
} from '../../testing/fixtures';
|
|
9
|
+
import { ConsoleQueuedList } from './ConsoleQueuedList';
|
|
10
|
+
|
|
11
|
+
const queuedItems = consoleListItemsFixture.filter(
|
|
12
|
+
(item) =>
|
|
13
|
+
(item.status === 'Awaiting Workspace' || item.status === 'Preparation') &&
|
|
14
|
+
item.dependedIssueUrls.length === 0,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const meta: Meta<typeof ConsoleQueuedList> = {
|
|
18
|
+
title: 'Console/ConsoleQueuedList',
|
|
19
|
+
component: ConsoleQueuedList,
|
|
20
|
+
args: {
|
|
21
|
+
storyColors: consoleStoryColorsFixture,
|
|
22
|
+
statusOptions: consoleStatusOptionsFixture,
|
|
23
|
+
agentOptions: consoleAgentOptionsFixture,
|
|
24
|
+
activeItemId: null,
|
|
25
|
+
isLoading: false,
|
|
26
|
+
error: null,
|
|
27
|
+
onSelectItem: () => {},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof ConsoleQueuedList>;
|
|
34
|
+
|
|
35
|
+
export const WithQueuedItems: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
rows: buildConsoleListRows(queuedItems, {}, []),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const WithStoryGroups: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
rows: buildConsoleListRows(
|
|
44
|
+
queuedItems,
|
|
45
|
+
{},
|
|
46
|
+
Object.keys(consoleStoryColorsFixture),
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Loading: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
rows: [],
|
|
54
|
+
isLoading: true,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const Empty: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
rows: [],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const ErrorState: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
rows: [],
|
|
67
|
+
error: 'HTTP 404',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
2
|
+
import { buildConsoleListRows } from '../../logic/grouping';
|
|
3
|
+
import {
|
|
4
|
+
consoleAgentOptionsFixture,
|
|
5
|
+
consoleListItemsFixture,
|
|
6
|
+
consoleStatusOptionsFixture,
|
|
7
|
+
consoleStoryColorsFixture,
|
|
8
|
+
} from '../../testing/fixtures';
|
|
9
|
+
import { ConsoleQueuedList } from './ConsoleQueuedList';
|
|
10
|
+
|
|
11
|
+
const queuedItems = consoleListItemsFixture.filter(
|
|
12
|
+
(item) =>
|
|
13
|
+
(item.status === 'Awaiting Workspace' || item.status === 'Preparation') &&
|
|
14
|
+
item.dependedIssueUrls.length === 0,
|
|
15
|
+
);
|
|
16
|
+
const rows = buildConsoleListRows(queuedItems, {}, []);
|
|
17
|
+
|
|
18
|
+
describe('ConsoleQueuedList', () => {
|
|
19
|
+
it('renders group headers and queued item rows', () => {
|
|
20
|
+
const { container } = render(
|
|
21
|
+
<ConsoleQueuedList
|
|
22
|
+
rows={rows}
|
|
23
|
+
storyColors={consoleStoryColorsFixture}
|
|
24
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
25
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
26
|
+
activeItemId={null}
|
|
27
|
+
isLoading={false}
|
|
28
|
+
error={null}
|
|
29
|
+
onSelectItem={() => {}}
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
const buttons = container.querySelectorAll('button');
|
|
33
|
+
expect(buttons.length).toBe(queuedItems.length);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('calls onSelectItem with the clicked item', () => {
|
|
37
|
+
const onSelectItem = jest.fn();
|
|
38
|
+
const { getByText } = render(
|
|
39
|
+
<ConsoleQueuedList
|
|
40
|
+
rows={rows}
|
|
41
|
+
storyColors={consoleStoryColorsFixture}
|
|
42
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
43
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
44
|
+
activeItemId={null}
|
|
45
|
+
isLoading={false}
|
|
46
|
+
error={null}
|
|
47
|
+
onSelectItem={onSelectItem}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
fireEvent.click(getByText(queuedItems[0].title));
|
|
51
|
+
expect(onSelectItem).toHaveBeenCalledWith(queuedItems[0]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('shows the loading state', () => {
|
|
55
|
+
const { getByText } = render(
|
|
56
|
+
<ConsoleQueuedList
|
|
57
|
+
rows={[]}
|
|
58
|
+
storyColors={{}}
|
|
59
|
+
statusOptions={[]}
|
|
60
|
+
agentOptions={[]}
|
|
61
|
+
activeItemId={null}
|
|
62
|
+
isLoading
|
|
63
|
+
error={null}
|
|
64
|
+
onSelectItem={() => {}}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
expect(getByText('Loading list...')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('shows the empty state', () => {
|
|
71
|
+
const { getByText } = render(
|
|
72
|
+
<ConsoleQueuedList
|
|
73
|
+
rows={[]}
|
|
74
|
+
storyColors={{}}
|
|
75
|
+
statusOptions={[]}
|
|
76
|
+
agentOptions={[]}
|
|
77
|
+
activeItemId={null}
|
|
78
|
+
isLoading={false}
|
|
79
|
+
error={null}
|
|
80
|
+
onSelectItem={() => {}}
|
|
81
|
+
/>,
|
|
82
|
+
);
|
|
83
|
+
expect(getByText('No items')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('shows the error state', () => {
|
|
87
|
+
const { getByRole } = render(
|
|
88
|
+
<ConsoleQueuedList
|
|
89
|
+
rows={[]}
|
|
90
|
+
storyColors={{}}
|
|
91
|
+
statusOptions={[]}
|
|
92
|
+
agentOptions={[]}
|
|
93
|
+
activeItemId={null}
|
|
94
|
+
isLoading={false}
|
|
95
|
+
error="HTTP 503"
|
|
96
|
+
onSelectItem={() => {}}
|
|
97
|
+
/>,
|
|
98
|
+
);
|
|
99
|
+
expect(getByRole('alert')).toHaveTextContent('HTTP 503');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConsoleListRow,
|
|
3
|
+
resolveStoryColorEnum,
|
|
4
|
+
} from '../../logic/grouping';
|
|
5
|
+
import type {
|
|
6
|
+
ConsoleFieldOption,
|
|
7
|
+
ConsoleListItem,
|
|
8
|
+
ConsoleStoryColorSource,
|
|
9
|
+
} from '../../logic/types';
|
|
10
|
+
import { ConsoleQueuedItemRow } from './ConsoleQueuedItemRow';
|
|
11
|
+
import { ConsoleStorySummary } from './ConsoleStorySummary';
|
|
12
|
+
|
|
13
|
+
export type ConsoleQueuedListProps = {
|
|
14
|
+
rows: ConsoleListRow[];
|
|
15
|
+
storyColors: ConsoleStoryColorSource;
|
|
16
|
+
statusOptions: ConsoleFieldOption[];
|
|
17
|
+
agentOptions: ConsoleFieldOption[];
|
|
18
|
+
activeItemId: string | null;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
error: string | null;
|
|
21
|
+
onSelectItem: (item: ConsoleListItem) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const ConsoleQueuedList = ({
|
|
25
|
+
rows,
|
|
26
|
+
storyColors,
|
|
27
|
+
statusOptions,
|
|
28
|
+
agentOptions,
|
|
29
|
+
activeItemId,
|
|
30
|
+
isLoading,
|
|
31
|
+
error,
|
|
32
|
+
onSelectItem,
|
|
33
|
+
}: ConsoleQueuedListProps) => {
|
|
34
|
+
if (error !== null) {
|
|
35
|
+
return (
|
|
36
|
+
<p role="alert" className="console-list-message console-list-error">
|
|
37
|
+
Failed to load list: {error}
|
|
38
|
+
</p>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isLoading) {
|
|
43
|
+
return <p className="console-list-message">Loading list...</p>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (rows.length === 0) {
|
|
47
|
+
return <p className="console-list-empty">No items</p>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ul className="console-list">
|
|
52
|
+
{rows.map((row) =>
|
|
53
|
+
row.kind === 'group-header' ? (
|
|
54
|
+
<li key={`group:${row.story}`} className="console-list-group">
|
|
55
|
+
<ConsoleStorySummary
|
|
56
|
+
story={row.story}
|
|
57
|
+
count={row.count}
|
|
58
|
+
colorEnum={resolveStoryColorEnum(storyColors, row.story)}
|
|
59
|
+
/>
|
|
60
|
+
</li>
|
|
61
|
+
) : (
|
|
62
|
+
<li key={row.item.itemId} className="console-list-row">
|
|
63
|
+
<ConsoleQueuedItemRow
|
|
64
|
+
item={row.item}
|
|
65
|
+
isActive={row.item.itemId === activeItemId}
|
|
66
|
+
statusOptions={statusOptions}
|
|
67
|
+
agentOptions={agentOptions}
|
|
68
|
+
onSelect={onSelectItem}
|
|
69
|
+
/>
|
|
70
|
+
</li>
|
|
71
|
+
),
|
|
72
|
+
)}
|
|
73
|
+
</ul>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -34,6 +34,18 @@ describe('ConsoleOkAndAwaitingWorkspaceActions', () => {
|
|
|
34
34
|
expect(container.firstChild).toBeNull();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it('applies the approve green style class regardless of the status option color', () => {
|
|
38
|
+
const { getByRole } = render(
|
|
39
|
+
<ConsoleOkAndAwaitingWorkspaceActions
|
|
40
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
41
|
+
onOkAndAwaitingWorkspace={() => {}}
|
|
42
|
+
/>,
|
|
43
|
+
);
|
|
44
|
+
expect(
|
|
45
|
+
getByRole('button', { name: 'ok & Awaiting Workspace' }),
|
|
46
|
+
).toHaveClass('console-op-button-approve');
|
|
47
|
+
});
|
|
48
|
+
|
|
37
49
|
it('calls the callback with the awaiting workspace option on click', () => {
|
|
38
50
|
const onOkAndAwaitingWorkspace = jest.fn();
|
|
39
51
|
const { getByRole } = render(
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { colorFromEnum } from '../../logic/colors';
|
|
2
1
|
import type { ConsoleFieldOption } from '../../logic/types';
|
|
3
2
|
|
|
4
3
|
export type ConsoleOkAndAwaitingWorkspaceActionsProps = {
|
|
@@ -20,18 +19,11 @@ export const ConsoleOkAndAwaitingWorkspaceActions = ({
|
|
|
20
19
|
return null;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
const palette = colorFromEnum(option.color);
|
|
24
|
-
|
|
25
22
|
return (
|
|
26
23
|
<div className="console-op-group">
|
|
27
24
|
<button
|
|
28
25
|
type="button"
|
|
29
|
-
className="console-op-button"
|
|
30
|
-
style={{
|
|
31
|
-
color: palette.fg,
|
|
32
|
-
borderColor: palette.border,
|
|
33
|
-
backgroundColor: palette.bg,
|
|
34
|
-
}}
|
|
26
|
+
className="console-op-button console-op-button-approve"
|
|
35
27
|
onClick={() => onOkAndAwaitingWorkspace(option)}
|
|
36
28
|
>
|
|
37
29
|
ok & Awaiting Workspace
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts
CHANGED
|
@@ -272,6 +272,7 @@ describe('useConsoleTabData', () => {
|
|
|
272
272
|
prs: {
|
|
273
273
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
274
274
|
statusOptions: [],
|
|
275
|
+
agentOptions: [],
|
|
275
276
|
storyOptions: [],
|
|
276
277
|
storyColors: {},
|
|
277
278
|
stories: [],
|
|
@@ -303,6 +304,7 @@ describe('useConsoleTabData', () => {
|
|
|
303
304
|
'workflow-blocker': {
|
|
304
305
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
305
306
|
statusOptions: [],
|
|
307
|
+
agentOptions: [],
|
|
306
308
|
storyOptions: [],
|
|
307
309
|
storyColors: {},
|
|
308
310
|
items: [],
|
|
@@ -314,6 +316,7 @@ describe('useConsoleTabData', () => {
|
|
|
314
316
|
'failed-preparation': {
|
|
315
317
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
316
318
|
statusOptions: [],
|
|
319
|
+
agentOptions: [],
|
|
317
320
|
storyOptions: [],
|
|
318
321
|
storyColors: {},
|
|
319
322
|
items: [],
|
|
@@ -325,6 +328,7 @@ describe('useConsoleTabData', () => {
|
|
|
325
328
|
'todo-by-human': {
|
|
326
329
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
327
330
|
statusOptions: [],
|
|
331
|
+
agentOptions: [],
|
|
328
332
|
storyOptions: [],
|
|
329
333
|
storyColors: {},
|
|
330
334
|
items: [],
|
|
@@ -336,6 +340,19 @@ describe('useConsoleTabData', () => {
|
|
|
336
340
|
'todo-by-agent': {
|
|
337
341
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
338
342
|
statusOptions: [],
|
|
343
|
+
agentOptions: [],
|
|
344
|
+
storyOptions: [],
|
|
345
|
+
storyColors: {},
|
|
346
|
+
items: [],
|
|
347
|
+
stories: [],
|
|
348
|
+
defaultNameWithOwner: null,
|
|
349
|
+
fromCache: false,
|
|
350
|
+
storyOrder: [],
|
|
351
|
+
},
|
|
352
|
+
queued: {
|
|
353
|
+
generatedAt: '2026-08-01T10:00:00Z',
|
|
354
|
+
statusOptions: [],
|
|
355
|
+
agentOptions: [],
|
|
339
356
|
storyOptions: [],
|
|
340
357
|
storyColors: {},
|
|
341
358
|
items: [],
|
|
@@ -347,6 +364,7 @@ describe('useConsoleTabData', () => {
|
|
|
347
364
|
stories: {
|
|
348
365
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
349
366
|
statusOptions: [],
|
|
367
|
+
agentOptions: [],
|
|
350
368
|
storyOptions: [],
|
|
351
369
|
storyColors: {},
|
|
352
370
|
items: [],
|
|
@@ -390,6 +408,7 @@ describe('useConsoleTabData', () => {
|
|
|
390
408
|
prs: {
|
|
391
409
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
392
410
|
statusOptions: [],
|
|
411
|
+
agentOptions: [],
|
|
393
412
|
storyOptions: [],
|
|
394
413
|
storyColors: {},
|
|
395
414
|
items: [],
|
|
@@ -401,6 +420,7 @@ describe('useConsoleTabData', () => {
|
|
|
401
420
|
'workflow-blocker': {
|
|
402
421
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
403
422
|
statusOptions: [],
|
|
423
|
+
agentOptions: [],
|
|
404
424
|
storyOptions: [],
|
|
405
425
|
storyColors: {},
|
|
406
426
|
items: [],
|
|
@@ -412,6 +432,7 @@ describe('useConsoleTabData', () => {
|
|
|
412
432
|
'failed-preparation': {
|
|
413
433
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
414
434
|
statusOptions: [],
|
|
435
|
+
agentOptions: [],
|
|
415
436
|
storyOptions: [],
|
|
416
437
|
storyColors: {},
|
|
417
438
|
items: [],
|
|
@@ -423,6 +444,7 @@ describe('useConsoleTabData', () => {
|
|
|
423
444
|
'todo-by-human': {
|
|
424
445
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
425
446
|
statusOptions: [],
|
|
447
|
+
agentOptions: [],
|
|
426
448
|
storyOptions: [],
|
|
427
449
|
storyColors: {},
|
|
428
450
|
items: [],
|
|
@@ -434,6 +456,19 @@ describe('useConsoleTabData', () => {
|
|
|
434
456
|
'todo-by-agent': {
|
|
435
457
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
436
458
|
statusOptions: [],
|
|
459
|
+
agentOptions: [],
|
|
460
|
+
storyOptions: [],
|
|
461
|
+
storyColors: {},
|
|
462
|
+
items: [],
|
|
463
|
+
stories: [],
|
|
464
|
+
defaultNameWithOwner: null,
|
|
465
|
+
fromCache: false,
|
|
466
|
+
storyOrder: [],
|
|
467
|
+
},
|
|
468
|
+
queued: {
|
|
469
|
+
generatedAt: '2026-08-01T10:00:00Z',
|
|
470
|
+
statusOptions: [],
|
|
471
|
+
agentOptions: [],
|
|
437
472
|
storyOptions: [],
|
|
438
473
|
storyColors: {},
|
|
439
474
|
items: [],
|
|
@@ -445,6 +480,7 @@ describe('useConsoleTabData', () => {
|
|
|
445
480
|
stories: {
|
|
446
481
|
generatedAt: '2026-08-01T10:00:00Z',
|
|
447
482
|
statusOptions: [],
|
|
483
|
+
agentOptions: [],
|
|
448
484
|
storyOptions: [],
|
|
449
485
|
storyColors: {},
|
|
450
486
|
items: [],
|
|
@@ -472,4 +508,39 @@ describe('useConsoleTabData', () => {
|
|
|
472
508
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
473
509
|
jest.useRealTimers();
|
|
474
510
|
});
|
|
511
|
+
|
|
512
|
+
it('parses agentOptions from the snapshot payload', async () => {
|
|
513
|
+
const agentOptions = [
|
|
514
|
+
{ id: 'ag1', name: 'developer', color: 'GRAY' },
|
|
515
|
+
{ id: 'ag2', name: 'chore', color: 'GRAY' },
|
|
516
|
+
];
|
|
517
|
+
const fetchMock = jest.fn(async (url: string) => ({
|
|
518
|
+
ok: true,
|
|
519
|
+
status: 200,
|
|
520
|
+
json: async () => ({
|
|
521
|
+
pjcode: 'acme',
|
|
522
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
523
|
+
statusOptions: [],
|
|
524
|
+
storyColors: {},
|
|
525
|
+
items: [],
|
|
526
|
+
agentOptions: url.includes('/queued/') ? agentOptions : [],
|
|
527
|
+
}),
|
|
528
|
+
}));
|
|
529
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
530
|
+
|
|
531
|
+
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(result.current.isLoading).toBe(false);
|
|
534
|
+
});
|
|
535
|
+
expect(result.current.snapshots.queued?.agentOptions).toEqual(agentOptions);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('defaults agentOptions to empty array when missing from payload', async () => {
|
|
539
|
+
installNetworkFetch();
|
|
540
|
+
const { result } = renderHook(() => useConsoleTabData('acme'));
|
|
541
|
+
await waitFor(() => {
|
|
542
|
+
expect(result.current.isLoading).toBe(false);
|
|
543
|
+
});
|
|
544
|
+
expect(result.current.snapshots.prs?.agentOptions).toEqual([]);
|
|
545
|
+
});
|
|
475
546
|
});
|
|
@@ -13,6 +13,7 @@ export type ConsoleTabSnapshot = {
|
|
|
13
13
|
items: ConsoleListItem[];
|
|
14
14
|
generatedAt: string;
|
|
15
15
|
statusOptions: ConsoleFieldOption[];
|
|
16
|
+
agentOptions: ConsoleFieldOption[];
|
|
16
17
|
storyOptions: ConsoleFieldOption[];
|
|
17
18
|
storyColors: ConsoleStoryColorSource;
|
|
18
19
|
stories: ConsoleStoryEntry[];
|
|
@@ -73,6 +74,7 @@ const parseSnapshotData = (
|
|
|
73
74
|
? payload.generatedAt
|
|
74
75
|
: '',
|
|
75
76
|
statusOptions: isRecord(payload) ? parseOptions(payload.statusOptions) : [],
|
|
77
|
+
agentOptions: isRecord(payload) ? parseOptions(payload.agentOptions) : [],
|
|
76
78
|
storyOptions: isRecord(payload) ? parseOptions(payload.storyOptions) : [],
|
|
77
79
|
storyColors:
|
|
78
80
|
isRecord(payload) && isRecord(payload.storyColors)
|
package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts
CHANGED
|
@@ -76,6 +76,32 @@ describe('airplaneSnapshot', () => {
|
|
|
76
76
|
expect(result?.tabs.acme?.prs.statusOptions).toHaveLength(1);
|
|
77
77
|
expect(result?.tabs.acme?.prs.fromCache).toBe(false);
|
|
78
78
|
expect(result?.tabs.acme?.prs.storyOrder).toEqual([]);
|
|
79
|
+
expect(result?.tabs.acme?.prs.agentOptions).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('parses agentOptions from a tab snapshot', () => {
|
|
83
|
+
const raw = {
|
|
84
|
+
capturedAt: '2026-01-01T00:00:00Z',
|
|
85
|
+
tabs: {
|
|
86
|
+
acme: {
|
|
87
|
+
prs: {
|
|
88
|
+
generatedAt: '2026-01-01T00:00:00Z',
|
|
89
|
+
statusOptions: [],
|
|
90
|
+
agentOptions: [{ id: 'ag1', name: 'developer', color: 'GRAY' }],
|
|
91
|
+
storyOptions: [],
|
|
92
|
+
storyColors: {},
|
|
93
|
+
items: [],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
items: {},
|
|
98
|
+
failures: [],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = parseAirplaneSnapshot(raw);
|
|
102
|
+
expect(result?.tabs.acme?.prs.agentOptions).toEqual([
|
|
103
|
+
{ id: 'ag1', name: 'developer', color: 'GRAY' },
|
|
104
|
+
]);
|
|
79
105
|
});
|
|
80
106
|
|
|
81
107
|
it('parses storyOrder and fromCache from a tab snapshot', () => {
|
|
@@ -16,6 +16,7 @@ export type AirplaneTabSnapshot = {
|
|
|
16
16
|
items: ConsoleListItem[];
|
|
17
17
|
generatedAt: string;
|
|
18
18
|
statusOptions: ConsoleFieldOption[];
|
|
19
|
+
agentOptions: ConsoleFieldOption[];
|
|
19
20
|
storyOptions: ConsoleFieldOption[];
|
|
20
21
|
storyColors: ConsoleStoryColorSource;
|
|
21
22
|
stories: ConsoleStoryEntry[];
|
|
@@ -170,6 +171,7 @@ const parseTabSnapshot = (payload: unknown): AirplaneTabSnapshot => {
|
|
|
170
171
|
items: [],
|
|
171
172
|
generatedAt: '',
|
|
172
173
|
statusOptions: [],
|
|
174
|
+
agentOptions: [],
|
|
173
175
|
storyOptions: [],
|
|
174
176
|
storyColors: {},
|
|
175
177
|
stories: [],
|
|
@@ -188,6 +190,9 @@ const parseTabSnapshot = (payload: unknown): AirplaneTabSnapshot => {
|
|
|
188
190
|
)
|
|
189
191
|
? (payload.statusOptions.filter(isRecord) as ConsoleFieldOption[])
|
|
190
192
|
: [];
|
|
193
|
+
const agentOptions: ConsoleFieldOption[] = Array.isArray(payload.agentOptions)
|
|
194
|
+
? (payload.agentOptions.filter(isRecord) as ConsoleFieldOption[])
|
|
195
|
+
: [];
|
|
191
196
|
const storyOptions: ConsoleFieldOption[] = Array.isArray(payload.storyOptions)
|
|
192
197
|
? (payload.storyOptions.filter(isRecord) as ConsoleFieldOption[])
|
|
193
198
|
: [];
|
|
@@ -202,6 +207,7 @@ const parseTabSnapshot = (payload: unknown): AirplaneTabSnapshot => {
|
|
|
202
207
|
generatedAt:
|
|
203
208
|
typeof payload.generatedAt === 'string' ? payload.generatedAt : '',
|
|
204
209
|
statusOptions,
|
|
210
|
+
agentOptions,
|
|
205
211
|
storyOptions,
|
|
206
212
|
storyColors: isRecord(payload.storyColors)
|
|
207
213
|
? (payload.storyColors as ConsoleStoryColorSource)
|
|
@@ -71,3 +71,15 @@ export const colorFromEnum = (
|
|
|
71
71
|
}
|
|
72
72
|
return CONSOLE_COLOR_PALETTE.GRAY;
|
|
73
73
|
};
|
|
74
|
+
|
|
75
|
+
export const badgeStyle = (colorEnum: string | null) => {
|
|
76
|
+
const palette = colorFromEnum(colorEnum);
|
|
77
|
+
return {
|
|
78
|
+
backgroundColor: palette.bg,
|
|
79
|
+
borderColor: palette.border,
|
|
80
|
+
color: palette.fg,
|
|
81
|
+
border: '1px solid' as const,
|
|
82
|
+
borderRadius: '6px',
|
|
83
|
+
padding: '0 6px',
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -43,7 +43,15 @@ export type ConsoleStatusTab = {
|
|
|
43
43
|
items: ConsoleListItem[];
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
export type
|
|
46
|
+
export type ConsoleQueuedTab = {
|
|
47
|
+
pjcode: string;
|
|
48
|
+
generatedAt: string;
|
|
49
|
+
statusOptions: ConsoleFieldOption[];
|
|
50
|
+
agentOptions: ConsoleFieldOption[];
|
|
51
|
+
storyOrder: string[];
|
|
52
|
+
storyColors: Record<string, { color: ConsoleColor }>;
|
|
53
|
+
items: ConsoleListItem[];
|
|
54
|
+
};
|
|
47
55
|
|
|
48
56
|
export type ConsoleStoryColorSource = Record<
|
|
49
57
|
string,
|
|
@@ -138,6 +146,7 @@ export type ConsoleTabName =
|
|
|
138
146
|
| 'failed-preparation'
|
|
139
147
|
| 'todo-by-human'
|
|
140
148
|
| 'todo-by-agent'
|
|
149
|
+
| 'queued'
|
|
141
150
|
| 'stories';
|
|
142
151
|
|
|
143
152
|
export type ConsoleStoryEntry = {
|
|
@@ -159,5 +168,6 @@ export const CONSOLE_TABS: ConsoleTab[] = [
|
|
|
159
168
|
{ name: 'failed-preparation', label: 'Failed Preparation' },
|
|
160
169
|
{ name: 'todo-by-human', label: 'Todo by human' },
|
|
161
170
|
{ name: 'todo-by-agent', label: 'Todo by agent' },
|
|
171
|
+
{ name: 'queued', label: 'Queued' },
|
|
162
172
|
{ name: 'stories', label: 'Stories' },
|
|
163
173
|
];
|
|
@@ -1083,6 +1083,64 @@ describe('ConsolePage auto-advance tab', () => {
|
|
|
1083
1083
|
});
|
|
1084
1084
|
});
|
|
1085
1085
|
|
|
1086
|
+
it('shows the queued tab badge count when queued items are present', async () => {
|
|
1087
|
+
const queuedItem = {
|
|
1088
|
+
number: 900,
|
|
1089
|
+
title: 'Queued task waiting',
|
|
1090
|
+
url: 'https://github.com/o/r/issues/900',
|
|
1091
|
+
repo: 'o/r',
|
|
1092
|
+
nameWithOwner: 'o/r',
|
|
1093
|
+
projectItemId: 'PVTI_900',
|
|
1094
|
+
itemId: 'PVTI_900',
|
|
1095
|
+
isPr: false,
|
|
1096
|
+
relatedOpenPullRequestUrls: [],
|
|
1097
|
+
story: 'TDPM Console port',
|
|
1098
|
+
status: 'Awaiting Workspace',
|
|
1099
|
+
nextActionDate: null,
|
|
1100
|
+
nextActionHour: null,
|
|
1101
|
+
dependedIssueUrls: [],
|
|
1102
|
+
labels: [],
|
|
1103
|
+
createdAt: '2026-06-17T00:00:00.000Z',
|
|
1104
|
+
};
|
|
1105
|
+
global.fetch = jest.fn(async (url: string) => {
|
|
1106
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
1107
|
+
if (listMatch !== null) {
|
|
1108
|
+
const tab = listMatch[1];
|
|
1109
|
+
if (tab === 'queued') {
|
|
1110
|
+
return {
|
|
1111
|
+
ok: true,
|
|
1112
|
+
status: 200,
|
|
1113
|
+
json: async () => ({
|
|
1114
|
+
...listPayload(''),
|
|
1115
|
+
agentOptions: [],
|
|
1116
|
+
items: [queuedItem],
|
|
1117
|
+
}),
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
return { ok: true, status: 200, json: async () => listPayload(tab) };
|
|
1121
|
+
}
|
|
1122
|
+
if (url === '/api/projects') {
|
|
1123
|
+
return {
|
|
1124
|
+
ok: true,
|
|
1125
|
+
status: 200,
|
|
1126
|
+
json: async () => ({ pjcodes: ['acme'] }),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
1130
|
+
}) as unknown as typeof fetch;
|
|
1131
|
+
|
|
1132
|
+
const { container } = render(<ConsolePage />);
|
|
1133
|
+
|
|
1134
|
+
await waitFor(() => {
|
|
1135
|
+
const tabs = [...container.querySelectorAll('.console-tab')];
|
|
1136
|
+
const queuedTab = tabs.find((el) => el.textContent?.includes('Queued'));
|
|
1137
|
+
expect(queuedTab).toBeTruthy();
|
|
1138
|
+
expect(queuedTab?.querySelector('.console-tab-badge')?.textContent).toBe(
|
|
1139
|
+
'1',
|
|
1140
|
+
);
|
|
1141
|
+
});
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1086
1144
|
it('reads console-story-show-gray from localStorage and shows gray stories when true', async () => {
|
|
1087
1145
|
localStorage.setItem('console-story-show-gray', 'true');
|
|
1088
1146
|
window.history.replaceState({}, '', '/projects/acme/stories?k=token');
|