github-issue-tower-defence-management 2.10.2 → 2.12.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/CHANGELOG.md +14 -0
- package/README.md +9 -1
- package/bin/adapter/entry-points/cli/fleetConfig.js +7 -1
- package/bin/adapter/entry-points/cli/fleetConfig.js.map +1 -1
- package/bin/adapter/entry-points/cli/index.js +11 -3
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- 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-CvJgSVoN.js +88 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +2 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/CliGitHubGraphqlRateLimitRepository.js +24 -0
- package/bin/adapter/repositories/CliGitHubGraphqlRateLimitRepository.js.map +1 -0
- package/bin/adapter/repositories/KySlackRepository.js +2 -1
- package/bin/adapter/repositories/KySlackRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +22 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.js.map +1 -0
- 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/cli/fleetConfig.test.ts +99 -0
- package/src/adapter/entry-points/cli/fleetConfig.ts +25 -0
- package/src/adapter/entry-points/cli/index.test.ts +4 -0
- package/src/adapter/entry-points/cli/index.ts +16 -2
- 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/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-CvJgSVoN.js +88 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +2 -0
- package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +71 -0
- package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +21 -0
- package/src/adapter/repositories/KySlackRepository.test.ts +49 -1
- package/src/adapter/repositories/KySlackRepository.ts +3 -2
- package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +74 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +229 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +35 -0
- package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +3 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +84 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +39 -0
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +1 -0
- package/types/adapter/entry-points/cli/fleetConfig.d.ts +4 -0
- package/types/adapter/entry-points/cli/fleetConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/CliGitHubGraphqlRateLimitRepository.d.ts +8 -0
- package/types/adapter/repositories/CliGitHubGraphqlRateLimitRepository.d.ts.map +1 -0
- package/types/adapter/repositories/KySlackRepository.d.ts +1 -1
- package/types/adapter/repositories/KySlackRepository.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +5 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.d.ts +4 -0
- package/types/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.d.ts.map +1 -0
- 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
|
@@ -34,6 +34,7 @@ import { projectCacheDirectory } from '../../repositories/localStorageCacheDirec
|
|
|
34
34
|
import { NodeLocalCommandRunner } from '../../repositories/NodeLocalCommandRunner';
|
|
35
35
|
import { NodeTmuxSessionRepository } from '../../repositories/NodeTmuxSessionRepository';
|
|
36
36
|
import { ProcTakeOwnershipSpawnRepository } from '../../repositories/ProcTakeOwnershipSpawnRepository';
|
|
37
|
+
import { CliGitHubGraphqlRateLimitRepository } from '../../repositories/CliGitHubGraphqlRateLimitRepository';
|
|
37
38
|
import { DEFAULT_THRESHOLD_FOR_DISPATCH_LOOP } from '../../../domain/usecases/resolveNextStepAgentDispatchRepetition';
|
|
38
39
|
import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
|
|
39
40
|
import { SystemDateRepository } from '../../repositories/SystemDateRepository';
|
|
@@ -324,7 +325,7 @@ program
|
|
|
324
325
|
)
|
|
325
326
|
.option(
|
|
326
327
|
'--fleetConfigFilePath <path>',
|
|
327
|
-
'Path to the fleet-wide YAML config file holding the preparationWorker mapping (normalConcurrentLimit); falls back to the TDPM_FLEET_CONFIG environment variable, and to the built-in values when neither is set',
|
|
328
|
+
'Path to the fleet-wide YAML config file holding the preparationWorker mapping (normalConcurrentLimit, maxConcurrentWorkers, graphqlRateLimitFloor); falls back to the TDPM_FLEET_CONFIG environment variable, and to the built-in values when neither is set',
|
|
328
329
|
)
|
|
329
330
|
.action(async (options: StartDaemonOptions) => {
|
|
330
331
|
const token = process.env.GH_TOKEN;
|
|
@@ -423,8 +424,18 @@ program
|
|
|
423
424
|
);
|
|
424
425
|
const preparationWorkerSettings =
|
|
425
426
|
loadPreparationWorkerSettings(fleetConfigFilePath);
|
|
427
|
+
const fleetConfigSource =
|
|
428
|
+
fleetConfigFilePath !== null
|
|
429
|
+
? ' (source: fleetConfig)'
|
|
430
|
+
: ' (source: built-in default)';
|
|
426
431
|
console.log(
|
|
427
|
-
`Effective normalConcurrentLimit: ${preparationWorkerSettings.normalConcurrentLimit}${
|
|
432
|
+
`Effective normalConcurrentLimit: ${preparationWorkerSettings.normalConcurrentLimit}${fleetConfigSource}`,
|
|
433
|
+
);
|
|
434
|
+
console.log(
|
|
435
|
+
`Effective maxConcurrentWorkers: ${preparationWorkerSettings.maxConcurrentWorkers}${fleetConfigSource}`,
|
|
436
|
+
);
|
|
437
|
+
console.log(
|
|
438
|
+
`Effective graphqlRateLimitFloor: ${preparationWorkerSettings.graphqlRateLimitFloor}${fleetConfigSource}`,
|
|
428
439
|
);
|
|
429
440
|
|
|
430
441
|
const projectName = config.projectName ?? 'default';
|
|
@@ -496,6 +507,7 @@ program
|
|
|
496
507
|
localCommandRunner,
|
|
497
508
|
claudeTokenUsageRepository,
|
|
498
509
|
new ProcTakeOwnershipSpawnRepository(),
|
|
510
|
+
new CliGitHubGraphqlRateLimitRepository(localCommandRunner),
|
|
499
511
|
);
|
|
500
512
|
|
|
501
513
|
const rawAllowedIssueAuthors = config.allowedIssueAuthors;
|
|
@@ -545,6 +557,8 @@ program
|
|
|
545
557
|
labelsAsLlmAgentName: config.labelsAsLlmAgentName ?? null,
|
|
546
558
|
agents: config.agents ?? null,
|
|
547
559
|
normalConcurrentLimit: preparationWorkerSettings.normalConcurrentLimit,
|
|
560
|
+
maxConcurrentWorkers: preparationWorkerSettings.maxConcurrentWorkers,
|
|
561
|
+
graphqlRateLimitFloor: preparationWorkerSettings.graphqlRateLimitFloor,
|
|
548
562
|
});
|
|
549
563
|
if (preparationResult.rotationOrder !== null) {
|
|
550
564
|
writeRotationOrderFile(preparationResult.rotationOrder);
|
|
@@ -533,6 +533,47 @@ test('changes the color of a story row via the color palette in the stories tab'
|
|
|
533
533
|
await expect(palette).toHaveCount(0);
|
|
534
534
|
});
|
|
535
535
|
|
|
536
|
+
test('shows queued items grouped by story with colored status badges and navigates to detail on row click', async ({
|
|
537
|
+
page,
|
|
538
|
+
}) => {
|
|
539
|
+
await page.goto(harness.appRootUrl);
|
|
540
|
+
|
|
541
|
+
await tabByLabel(page, 'Queued').click();
|
|
542
|
+
await expect(activeTabLabel(page)).toHaveText('Queued');
|
|
543
|
+
await expect(tabBadge(page, 'Queued')).toHaveText('2');
|
|
544
|
+
|
|
545
|
+
const awaitingRow = itemRowByText(
|
|
546
|
+
page,
|
|
547
|
+
'Add telemetry to the TDPM cost dashboard',
|
|
548
|
+
);
|
|
549
|
+
const prepRow = itemRowByText(
|
|
550
|
+
page,
|
|
551
|
+
'Migrate the rate-limit store to a shared Redis backend',
|
|
552
|
+
);
|
|
553
|
+
await expect(awaitingRow).toBeVisible();
|
|
554
|
+
await expect(prepRow).toBeVisible();
|
|
555
|
+
|
|
556
|
+
const awaitingStatusBadge = awaitingRow
|
|
557
|
+
.locator('.console-queued-item-badge')
|
|
558
|
+
.first();
|
|
559
|
+
await expect(awaitingStatusBadge).toHaveText('Awaiting Workspace');
|
|
560
|
+
const awaitingStyle = await awaitingStatusBadge.getAttribute('style');
|
|
561
|
+
expect(awaitingStyle).toContain('rgba(56, 139, 253');
|
|
562
|
+
|
|
563
|
+
const prepStatusBadge = prepRow.locator('.console-queued-item-badge').first();
|
|
564
|
+
await expect(prepStatusBadge).toHaveText('Preparation');
|
|
565
|
+
const prepStyle = await prepStatusBadge.getAttribute('style');
|
|
566
|
+
expect(prepStyle).toContain('rgba(187, 128, 9');
|
|
567
|
+
|
|
568
|
+
const prepAgentBadge = prepRow.locator('.console-queued-item-badge').nth(1);
|
|
569
|
+
await expect(prepAgentBadge).toHaveText('developer');
|
|
570
|
+
|
|
571
|
+
await awaitingRow.click();
|
|
572
|
+
await expect(page).toHaveURL(/PVTI_lADOABCD1234zgQUE00930/, {
|
|
573
|
+
timeout: 3000,
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
536
577
|
test('posts a comment and moves the item to Awaiting Workspace when the Comment & Awaiting Workspace button is clicked', async ({
|
|
537
578
|
page,
|
|
538
579
|
}) => {
|
|
@@ -87,8 +87,10 @@ type ConsoleFixtureSnapshot = {
|
|
|
87
87
|
pjcode: string;
|
|
88
88
|
generatedAt: string;
|
|
89
89
|
statusOptions: ConsoleFixtureFieldOption[];
|
|
90
|
+
agentOptions: ConsoleFixtureFieldOption[];
|
|
90
91
|
storyOptions: ConsoleFixtureFieldOption[];
|
|
91
92
|
storyColors: Record<string, { color: string }>;
|
|
93
|
+
storyOrder: string[];
|
|
92
94
|
items: ConsoleFixtureListItem[];
|
|
93
95
|
};
|
|
94
96
|
|
|
@@ -132,6 +134,11 @@ const STATUS_OPTIONS: ConsoleFixtureFieldOption[] = [
|
|
|
132
134
|
{ id: 'e9f6a726', name: 'In Tmux by agent', color: 'YELLOW' },
|
|
133
135
|
];
|
|
134
136
|
|
|
137
|
+
const AGENT_OPTIONS: ConsoleFixtureFieldOption[] = [
|
|
138
|
+
{ id: 'agt00001', name: 'developer', color: 'BLUE' },
|
|
139
|
+
{ id: 'agt00002', name: 'chore', color: 'GRAY' },
|
|
140
|
+
];
|
|
141
|
+
|
|
135
142
|
const STORY_OPTIONS: ConsoleFixtureFieldOption[] = [
|
|
136
143
|
{ id: '1491051e', name: 'TDPM Console port', color: 'BLUE' },
|
|
137
144
|
{ id: '28415d6c', name: 'regular / workflow improvement', color: 'GRAY' },
|
|
@@ -204,12 +211,16 @@ const pullRequestItem = (
|
|
|
204
211
|
|
|
205
212
|
const buildSnapshot = (
|
|
206
213
|
items: ConsoleFixtureListItem[],
|
|
214
|
+
agentOptions: ConsoleFixtureFieldOption[] = [],
|
|
215
|
+
storyOrder: string[] = [],
|
|
207
216
|
): ConsoleFixtureSnapshot => ({
|
|
208
217
|
pjcode: CONSOLE_E2E_PJCODE,
|
|
209
218
|
generatedAt: '2026-06-18T01:22:09.000Z',
|
|
210
219
|
statusOptions: STATUS_OPTIONS,
|
|
220
|
+
agentOptions,
|
|
211
221
|
storyOptions: STORY_OPTIONS,
|
|
212
222
|
storyColors: STORY_COLORS,
|
|
223
|
+
storyOrder,
|
|
213
224
|
items,
|
|
214
225
|
});
|
|
215
226
|
|
|
@@ -265,6 +276,29 @@ export const CONSOLE_E2E_TAB_ITEMS: Record<string, ConsoleFixtureListItem[]> = {
|
|
|
265
276
|
agent: 'developer',
|
|
266
277
|
},
|
|
267
278
|
],
|
|
279
|
+
queued: [
|
|
280
|
+
{
|
|
281
|
+
...issueItem(
|
|
282
|
+
930,
|
|
283
|
+
'Add telemetry to the TDPM cost dashboard',
|
|
284
|
+
'QUE00930',
|
|
285
|
+
'TDPM Console port',
|
|
286
|
+
'2026-06-18T02:00:00.000Z',
|
|
287
|
+
),
|
|
288
|
+
status: 'Awaiting Workspace',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
...issueItem(
|
|
292
|
+
931,
|
|
293
|
+
'Migrate the rate-limit store to a shared Redis backend',
|
|
294
|
+
'QUE00931',
|
|
295
|
+
'regular / workflow improvement',
|
|
296
|
+
'2026-06-18T02:30:00.000Z',
|
|
297
|
+
),
|
|
298
|
+
status: 'Preparation',
|
|
299
|
+
agent: 'developer',
|
|
300
|
+
},
|
|
301
|
+
],
|
|
268
302
|
};
|
|
269
303
|
|
|
270
304
|
const CONSOLE_E2E_STORIES_SNAPSHOT = {
|
|
@@ -310,13 +344,20 @@ const CONSOLE_E2E_STORIES_SNAPSHOT = {
|
|
|
310
344
|
defaultNameWithOwner: REPO_NAME_WITH_OWNER,
|
|
311
345
|
};
|
|
312
346
|
|
|
347
|
+
const QUEUED_STORY_ORDER = [
|
|
348
|
+
'TDPM Console port',
|
|
349
|
+
'regular / workflow improvement',
|
|
350
|
+
];
|
|
351
|
+
|
|
313
352
|
const writeFixtureData = (consoleDataOutputDir: string): void => {
|
|
314
353
|
for (const [tab, items] of Object.entries(CONSOLE_E2E_TAB_ITEMS)) {
|
|
315
354
|
const tabDir = path.join(consoleDataOutputDir, CONSOLE_E2E_PJCODE, tab);
|
|
316
355
|
fs.mkdirSync(tabDir, { recursive: true });
|
|
356
|
+
const agentOptions = tab === 'queued' ? AGENT_OPTIONS : [];
|
|
357
|
+
const storyOrder = tab === 'queued' ? QUEUED_STORY_ORDER : [];
|
|
317
358
|
fs.writeFileSync(
|
|
318
359
|
path.join(tabDir, 'list.json'),
|
|
319
|
-
JSON.stringify(buildSnapshot(items)),
|
|
360
|
+
JSON.stringify(buildSnapshot(items, agentOptions, storyOrder)),
|
|
320
361
|
);
|
|
321
362
|
}
|
|
322
363
|
const storiesTabDir = path.join(
|
|
@@ -367,7 +408,16 @@ const buildE2eProject = (): Project => ({
|
|
|
367
408
|
remainingEstimationMinutes: null,
|
|
368
409
|
dependedIssueUrlSeparatedByComma: null,
|
|
369
410
|
completionDate50PercentConfidence: null,
|
|
370
|
-
agent:
|
|
411
|
+
agent: {
|
|
412
|
+
name: 'Agent',
|
|
413
|
+
fieldId: 'PVTSSF_agent',
|
|
414
|
+
options: AGENT_OPTIONS.map((option) => ({
|
|
415
|
+
id: option.id,
|
|
416
|
+
name: option.name,
|
|
417
|
+
color: option.color as Project['status']['statuses'][number]['color'],
|
|
418
|
+
description: '',
|
|
419
|
+
})),
|
|
420
|
+
},
|
|
371
421
|
});
|
|
372
422
|
|
|
373
423
|
const buildIssueForUrl = (url: string): Issue => ({
|
|
@@ -36,6 +36,7 @@ const counts: Record<ConsoleTabName, number> = {
|
|
|
36
36
|
'failed-preparation': 2,
|
|
37
37
|
'todo-by-human': 66,
|
|
38
38
|
'todo-by-agent': 24,
|
|
39
|
+
queued: 8,
|
|
39
40
|
stories: 3,
|
|
40
41
|
};
|
|
41
42
|
|
|
@@ -63,6 +64,7 @@ export const ZeroCountTabsHidden: Story = {
|
|
|
63
64
|
'failed-preparation': 0,
|
|
64
65
|
'todo-by-human': 18,
|
|
65
66
|
'todo-by-agent': 0,
|
|
67
|
+
queued: 0,
|
|
66
68
|
stories: 0,
|
|
67
69
|
},
|
|
68
70
|
},
|
|
@@ -77,6 +79,7 @@ export const ZeroCountActiveTabStaysVisible: Story = {
|
|
|
77
79
|
'failed-preparation': 0,
|
|
78
80
|
'todo-by-human': 18,
|
|
79
81
|
'todo-by-agent': 0,
|
|
82
|
+
queued: 0,
|
|
80
83
|
stories: 0,
|
|
81
84
|
},
|
|
82
85
|
},
|
|
@@ -91,6 +94,7 @@ export const AfterAutoAdvanceToNextTab: Story = {
|
|
|
91
94
|
'failed-preparation': 2,
|
|
92
95
|
'todo-by-human': 4,
|
|
93
96
|
'todo-by-agent': 0,
|
|
97
|
+
queued: 0,
|
|
94
98
|
stories: 0,
|
|
95
99
|
},
|
|
96
100
|
onSelectTab: () => {},
|
|
@@ -3,6 +3,7 @@ import { buildConsoleListRows } from '../../logic/grouping';
|
|
|
3
3
|
import type { ConsoleOverlay } from '../../logic/types';
|
|
4
4
|
import {
|
|
5
5
|
consoleListItemsFixture,
|
|
6
|
+
consoleStatusOptionsFixture,
|
|
6
7
|
consoleStoryColorsFixture,
|
|
7
8
|
} from '../../testing/fixtures';
|
|
8
9
|
import { ConsoleItemList } from './ConsoleItemList';
|
|
@@ -10,7 +11,10 @@ import { ConsoleItemList } from './ConsoleItemList';
|
|
|
10
11
|
const meta: Meta<typeof ConsoleItemList> = {
|
|
11
12
|
title: 'Console/ConsoleItemList',
|
|
12
13
|
component: ConsoleItemList,
|
|
13
|
-
args: {
|
|
14
|
+
args: {
|
|
15
|
+
now: Date.parse('2026-06-19T12:00:00.000Z'),
|
|
16
|
+
statusOptions: consoleStatusOptionsFixture,
|
|
17
|
+
},
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export default meta;
|
package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
resolveStoryColorEnum,
|
|
4
4
|
} from '../../logic/grouping';
|
|
5
5
|
import type {
|
|
6
|
+
ConsoleFieldOption,
|
|
6
7
|
ConsoleListItem,
|
|
7
8
|
ConsoleStoryColorSource,
|
|
8
9
|
} from '../../logic/types';
|
|
@@ -12,6 +13,7 @@ import { ConsoleStorySummary } from './ConsoleStorySummary';
|
|
|
12
13
|
export type ConsoleListViewProps = {
|
|
13
14
|
rows: ConsoleListRow[];
|
|
14
15
|
storyColors: ConsoleStoryColorSource;
|
|
16
|
+
statusOptions?: ConsoleFieldOption[];
|
|
15
17
|
activeItemId: string | null;
|
|
16
18
|
now: number;
|
|
17
19
|
isLoading: boolean;
|
|
@@ -22,6 +24,7 @@ export type ConsoleListViewProps = {
|
|
|
22
24
|
export const ConsoleItemList = ({
|
|
23
25
|
rows,
|
|
24
26
|
storyColors,
|
|
27
|
+
statusOptions = [],
|
|
25
28
|
activeItemId,
|
|
26
29
|
now,
|
|
27
30
|
isLoading,
|
|
@@ -61,6 +64,7 @@ export const ConsoleItemList = ({
|
|
|
61
64
|
item={row.item}
|
|
62
65
|
isActive={row.item.itemId === activeItemId}
|
|
63
66
|
now={now}
|
|
67
|
+
statusOptions={statusOptions}
|
|
64
68
|
onSelect={onSelectItem}
|
|
65
69
|
/>
|
|
66
70
|
</li>
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
consoleListItemsFixture,
|
|
4
|
+
consoleStatusOptionsFixture,
|
|
5
|
+
} from '../../testing/fixtures';
|
|
3
6
|
import { ConsoleItemSummary } from './ConsoleItemSummary';
|
|
4
7
|
|
|
5
8
|
const meta: Meta<typeof ConsoleItemSummary> = {
|
|
6
9
|
title: 'Console/ConsoleItemSummary',
|
|
7
10
|
component: ConsoleItemSummary,
|
|
8
|
-
args: {
|
|
11
|
+
args: {
|
|
12
|
+
onSelect: () => {},
|
|
13
|
+
now: Date.parse('2026-06-19T12:00:00.000Z'),
|
|
14
|
+
statusOptions: consoleStatusOptionsFixture,
|
|
15
|
+
},
|
|
9
16
|
};
|
|
10
17
|
|
|
11
18
|
export default meta;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { fireEvent, render } from '@testing-library/react';
|
|
2
2
|
import { formatFullTimestamp } from '../../logic/relativeTime';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
consoleListItemsFixture,
|
|
5
|
+
consoleStatusOptionsFixture,
|
|
6
|
+
} from '../../testing/fixtures';
|
|
4
7
|
import { ConsoleItemSummary } from './ConsoleItemSummary';
|
|
5
8
|
|
|
6
9
|
const now = Date.parse('2026-06-19T12:00:00.000Z');
|
|
@@ -174,4 +177,37 @@ describe('ConsoleItemSummary', () => {
|
|
|
174
177
|
);
|
|
175
178
|
expect(queryByText('Agent')).not.toBeInTheDocument();
|
|
176
179
|
});
|
|
180
|
+
|
|
181
|
+
it('applies the Project V2 status color from statusOptions to the status badge', () => {
|
|
182
|
+
const awaitingWorkspaceItem = {
|
|
183
|
+
...prItem,
|
|
184
|
+
status: 'Awaiting Workspace',
|
|
185
|
+
};
|
|
186
|
+
const { getByText } = render(
|
|
187
|
+
<ConsoleItemSummary
|
|
188
|
+
item={awaitingWorkspaceItem}
|
|
189
|
+
isActive={false}
|
|
190
|
+
now={now}
|
|
191
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
192
|
+
onSelect={() => {}}
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
const badge = getByText('Awaiting Workspace');
|
|
196
|
+
expect(badge.style.backgroundColor).toBe('rgba(56, 139, 253, 0.1)');
|
|
197
|
+
expect(badge.style.color).toBe('rgb(56, 139, 253)');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('applies gray color to the status badge when statusOptions is empty', () => {
|
|
201
|
+
const { getByText } = render(
|
|
202
|
+
<ConsoleItemSummary
|
|
203
|
+
item={prItem}
|
|
204
|
+
isActive={false}
|
|
205
|
+
now={now}
|
|
206
|
+
statusOptions={[]}
|
|
207
|
+
onSelect={() => {}}
|
|
208
|
+
/>,
|
|
209
|
+
);
|
|
210
|
+
const badge = getByText(prItem.status as string);
|
|
211
|
+
expect(badge.style.backgroundColor).toBe('rgba(110, 118, 129, 0.1)');
|
|
212
|
+
});
|
|
177
213
|
});
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { badgeStyle } from '../../logic/colors';
|
|
1
2
|
import {
|
|
2
3
|
formatFullTimestamp,
|
|
3
4
|
formatRelativeTime,
|
|
4
5
|
} from '../../logic/relativeTime';
|
|
5
|
-
import type { ConsoleListItem } from '../../logic/types';
|
|
6
|
+
import type { ConsoleFieldOption, ConsoleListItem } from '../../logic/types';
|
|
6
7
|
import { ConsoleItemIcon } from '../detail/ConsoleItemIcon';
|
|
7
8
|
|
|
8
9
|
export type ConsoleListItemRowProps = {
|
|
9
10
|
item: ConsoleListItem;
|
|
10
11
|
isActive: boolean;
|
|
11
12
|
now: number;
|
|
13
|
+
statusOptions?: ConsoleFieldOption[];
|
|
12
14
|
onSelect: (item: ConsoleListItem) => void;
|
|
13
15
|
};
|
|
14
16
|
|
|
@@ -16,6 +18,7 @@ export const ConsoleItemSummary = ({
|
|
|
16
18
|
item,
|
|
17
19
|
isActive,
|
|
18
20
|
now,
|
|
21
|
+
statusOptions = [],
|
|
19
22
|
onSelect,
|
|
20
23
|
}: ConsoleListItemRowProps) => (
|
|
21
24
|
<button
|
|
@@ -68,7 +71,15 @@ export const ConsoleItemSummary = ({
|
|
|
68
71
|
{item.status !== null && item.status !== '' && (
|
|
69
72
|
<span className="console-item-field">
|
|
70
73
|
<span className="console-item-field-label">Status</span>
|
|
71
|
-
|
|
74
|
+
<span
|
|
75
|
+
className="console-item-status-badge"
|
|
76
|
+
style={badgeStyle(
|
|
77
|
+
statusOptions.find((o) => o.name === item.status)?.color ??
|
|
78
|
+
null,
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
{item.status}
|
|
82
|
+
</span>
|
|
72
83
|
</span>
|
|
73
84
|
)}
|
|
74
85
|
{item.agent !== null && item.agent !== '' && (
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
consoleAgentOptionsFixture,
|
|
4
|
+
consoleListItemsFixture,
|
|
5
|
+
consoleStatusOptionsFixture,
|
|
6
|
+
} from '../../testing/fixtures';
|
|
7
|
+
import { ConsoleQueuedItemRow } from './ConsoleQueuedItemRow';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof ConsoleQueuedItemRow> = {
|
|
10
|
+
title: 'Console/ConsoleQueuedItemRow',
|
|
11
|
+
component: ConsoleQueuedItemRow,
|
|
12
|
+
args: {
|
|
13
|
+
onSelect: () => {},
|
|
14
|
+
statusOptions: consoleStatusOptionsFixture,
|
|
15
|
+
agentOptions: consoleAgentOptionsFixture,
|
|
16
|
+
isActive: false,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
type Story = StoryObj<typeof ConsoleQueuedItemRow>;
|
|
23
|
+
|
|
24
|
+
export const AwaitingWorkspaceRow: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
item: {
|
|
27
|
+
...consoleListItemsFixture[1],
|
|
28
|
+
status: 'Awaiting Workspace',
|
|
29
|
+
agent: null,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const PreparationWithAgent: Story = {
|
|
35
|
+
args: { item: consoleListItemsFixture[5] },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const PreparationNoAgent: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
item: {
|
|
41
|
+
...consoleListItemsFixture[1],
|
|
42
|
+
status: 'Preparation',
|
|
43
|
+
agent: null,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const ActiveRow: Story = {
|
|
49
|
+
args: { item: consoleListItemsFixture[5], isActive: true },
|
|
50
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
2
|
+
import {
|
|
3
|
+
consoleAgentOptionsFixture,
|
|
4
|
+
consoleListItemsFixture,
|
|
5
|
+
consoleStatusOptionsFixture,
|
|
6
|
+
} from '../../testing/fixtures';
|
|
7
|
+
import { ConsoleQueuedItemRow } from './ConsoleQueuedItemRow';
|
|
8
|
+
|
|
9
|
+
const awaitingWorkspaceItem = {
|
|
10
|
+
...consoleListItemsFixture[1],
|
|
11
|
+
status: 'Awaiting Workspace',
|
|
12
|
+
agent: null,
|
|
13
|
+
dependedIssueUrls: [],
|
|
14
|
+
};
|
|
15
|
+
const preparationWithAgentItem = consoleListItemsFixture[5];
|
|
16
|
+
|
|
17
|
+
describe('ConsoleQueuedItemRow', () => {
|
|
18
|
+
it('renders the item title', () => {
|
|
19
|
+
const { getByText } = render(
|
|
20
|
+
<ConsoleQueuedItemRow
|
|
21
|
+
item={awaitingWorkspaceItem}
|
|
22
|
+
isActive={false}
|
|
23
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
24
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
25
|
+
onSelect={() => {}}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
expect(getByText(awaitingWorkspaceItem.title)).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders the status badge with Project V2 color', () => {
|
|
32
|
+
const { getByText } = render(
|
|
33
|
+
<ConsoleQueuedItemRow
|
|
34
|
+
item={awaitingWorkspaceItem}
|
|
35
|
+
isActive={false}
|
|
36
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
37
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
38
|
+
onSelect={() => {}}
|
|
39
|
+
/>,
|
|
40
|
+
);
|
|
41
|
+
const badge = getByText('Awaiting Workspace');
|
|
42
|
+
expect(badge.style.backgroundColor).toBe('rgba(56, 139, 253, 0.1)');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders the agent badge when the item has an agent', () => {
|
|
46
|
+
const { getByText } = render(
|
|
47
|
+
<ConsoleQueuedItemRow
|
|
48
|
+
item={preparationWithAgentItem}
|
|
49
|
+
isActive={false}
|
|
50
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
51
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
52
|
+
onSelect={() => {}}
|
|
53
|
+
/>,
|
|
54
|
+
);
|
|
55
|
+
expect(getByText('developer')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('omits the agent badge when agent is null', () => {
|
|
59
|
+
const { queryByText } = render(
|
|
60
|
+
<ConsoleQueuedItemRow
|
|
61
|
+
item={awaitingWorkspaceItem}
|
|
62
|
+
isActive={false}
|
|
63
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
64
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
65
|
+
onSelect={() => {}}
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
expect(queryByText('developer')).not.toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('calls onSelect with the item on click', () => {
|
|
72
|
+
const onSelect = jest.fn();
|
|
73
|
+
const { getByRole } = render(
|
|
74
|
+
<ConsoleQueuedItemRow
|
|
75
|
+
item={awaitingWorkspaceItem}
|
|
76
|
+
isActive={false}
|
|
77
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
78
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
79
|
+
onSelect={onSelect}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
fireEvent.click(getByRole('button'));
|
|
83
|
+
expect(onSelect).toHaveBeenCalledWith(awaitingWorkspaceItem);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('marks the active row with data-active', () => {
|
|
87
|
+
const { getByRole } = render(
|
|
88
|
+
<ConsoleQueuedItemRow
|
|
89
|
+
item={awaitingWorkspaceItem}
|
|
90
|
+
isActive
|
|
91
|
+
statusOptions={consoleStatusOptionsFixture}
|
|
92
|
+
agentOptions={consoleAgentOptionsFixture}
|
|
93
|
+
onSelect={() => {}}
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
expect(getByRole('button')).toHaveAttribute('data-active', 'true');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { badgeStyle } from '../../logic/colors';
|
|
2
|
+
import type { ConsoleFieldOption, ConsoleListItem } from '../../logic/types';
|
|
3
|
+
import { ConsoleItemIcon } from '../detail/ConsoleItemIcon';
|
|
4
|
+
|
|
5
|
+
export type ConsoleQueuedItemRowProps = {
|
|
6
|
+
item: ConsoleListItem;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
statusOptions: ConsoleFieldOption[];
|
|
9
|
+
agentOptions: ConsoleFieldOption[];
|
|
10
|
+
onSelect: (item: ConsoleListItem) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const queuedBadgeStyle = (colorEnum: string | null) => ({
|
|
14
|
+
...badgeStyle(colorEnum),
|
|
15
|
+
fontSize: '12px',
|
|
16
|
+
whiteSpace: 'nowrap' as const,
|
|
17
|
+
flexShrink: 0,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const ConsoleQueuedItemRow = ({
|
|
21
|
+
item,
|
|
22
|
+
isActive,
|
|
23
|
+
statusOptions,
|
|
24
|
+
agentOptions,
|
|
25
|
+
onSelect,
|
|
26
|
+
}: ConsoleQueuedItemRowProps) => {
|
|
27
|
+
const statusOption = statusOptions.find((o) => o.name === item.status);
|
|
28
|
+
const agentOption = agentOptions.find((o) => o.name === item.agent);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
className="console-item-row console-queued-item-row"
|
|
34
|
+
aria-current={isActive ? 'true' : undefined}
|
|
35
|
+
data-active={isActive ? 'true' : undefined}
|
|
36
|
+
onClick={() => onSelect(item)}
|
|
37
|
+
>
|
|
38
|
+
<ConsoleItemIcon
|
|
39
|
+
isPr={item.isPr}
|
|
40
|
+
state="open"
|
|
41
|
+
merged={false}
|
|
42
|
+
isDraft={false}
|
|
43
|
+
stateReason=""
|
|
44
|
+
/>
|
|
45
|
+
<span className="console-queued-item-title">{item.title}</span>
|
|
46
|
+
<span className="console-queued-item-badges">
|
|
47
|
+
{item.status !== null && item.status !== '' && (
|
|
48
|
+
<span
|
|
49
|
+
className="console-queued-item-badge"
|
|
50
|
+
style={queuedBadgeStyle(statusOption?.color ?? null)}
|
|
51
|
+
>
|
|
52
|
+
{item.status}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
{item.agent !== null && item.agent !== '' && (
|
|
56
|
+
<span
|
|
57
|
+
className="console-queued-item-badge"
|
|
58
|
+
style={queuedBadgeStyle(agentOption?.color ?? null)}
|
|
59
|
+
>
|
|
60
|
+
{item.agent}
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
</span>
|
|
64
|
+
</button>
|
|
65
|
+
);
|
|
66
|
+
};
|