github-issue-tower-defence-management 1.165.0 → 1.166.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/consoleOperationApi.js +44 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleTabNames.js +1 -0
  5. package/bin/adapter/entry-points/console/consoleTabNames.js.map +1 -1
  6. package/bin/adapter/entry-points/console/ui-dist/assets/index-CrSqMAVR.js +86 -0
  7. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  8. package/bin/adapter/entry-points/console/webServer.js +2 -0
  9. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  10. package/bin/adapter/repositories/GraphqlProjectRepository.js +3 -3
  11. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  12. package/bin/domain/entities/RequiredProjectField.js +0 -5
  13. package/bin/domain/entities/RequiredProjectField.js.map +1 -1
  14. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +12 -4
  15. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  16. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +13 -1
  17. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -1
  18. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +23 -0
  19. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +211 -0
  22. package/src/adapter/entry-points/console/consoleOperationApi.ts +63 -0
  23. package/src/adapter/entry-points/console/consoleTabNames.ts +1 -0
  24. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +61 -0
  25. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +63 -2
  26. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +4 -0
  27. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +1 -0
  28. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +77 -0
  29. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +196 -0
  30. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +163 -0
  31. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +1 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +15 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +29 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +1 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +10 -1
  36. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +43 -8
  37. package/src/adapter/entry-points/console/ui-dist/assets/index-CrSqMAVR.js +86 -0
  38. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  39. package/src/adapter/entry-points/console/webServer.ts +3 -0
  40. package/src/adapter/repositories/GraphqlProjectRepository.ts +3 -3
  41. package/src/domain/entities/RequiredProjectField.ts +0 -5
  42. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +14 -4
  43. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +15 -5
  44. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +16 -5
  45. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +13 -1
  46. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +86 -1
  47. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +49 -1
  48. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +1 -0
  49. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  50. package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
  51. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  52. package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
  53. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -1
  54. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  55. package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -1
  56. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +18 -1
  57. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  58. package/bin/adapter/entry-points/console/ui-dist/assets/index-CHUtr_4X.js +0 -86
  59. package/src/adapter/entry-points/console/ui-dist/assets/index-CHUtr_4X.js +0 -86
@@ -10,6 +10,7 @@ import {
10
10
  ConsoleProjectBinding,
11
11
  handleAttachmentUpload,
12
12
  handleComment,
13
+ handleCreateIssue,
13
14
  handleIntmux,
14
15
  handleReview,
15
16
  handleReviewComment,
@@ -1428,4 +1429,214 @@ describe('consoleOperationApi', () => {
1428
1429
  });
1429
1430
  });
1430
1431
  });
1432
+
1433
+ describe('handleCreateIssue', () => {
1434
+ const projectWithStory = (): Project => ({
1435
+ ...project,
1436
+ story: {
1437
+ name: 'Story',
1438
+ fieldId: 'storyField',
1439
+ databaseId: 1,
1440
+ stories: [
1441
+ {
1442
+ id: 'opt_blue',
1443
+ name: 'Portal redesign',
1444
+ color: 'BLUE',
1445
+ description: '',
1446
+ },
1447
+ {
1448
+ id: 'opt_green',
1449
+ name: 'Move to Okinawa',
1450
+ color: 'GREEN',
1451
+ description: '',
1452
+ },
1453
+ ],
1454
+ workflowManagementStory: { id: 'wms', name: 'workflow' },
1455
+ },
1456
+ });
1457
+
1458
+ beforeEach(() => {
1459
+ issueRepository.createNewIssue.mockResolvedValue(42);
1460
+ issueRepository.addIssueToProject.mockResolvedValue(undefined);
1461
+ });
1462
+
1463
+ it('creates the issue, adds it to the project, and sets the story', async () => {
1464
+ const storyProject = projectWithStory();
1465
+ const createdIssue: Issue = {
1466
+ ...mock<Issue>(),
1467
+ url: 'https://github.com/acme-labs/portal/issues/42',
1468
+ itemId: 'PVTI_new',
1469
+ };
1470
+ issueRepository.get.mockResolvedValue(createdIssue);
1471
+
1472
+ const response = await handleCreateIssue(
1473
+ contextForProject(storyProject),
1474
+ {
1475
+ pjcode: 'acme',
1476
+ title: 'New task title',
1477
+ storyOptionId: 'opt_blue',
1478
+ nameWithOwner: 'acme-labs/portal',
1479
+ },
1480
+ );
1481
+
1482
+ expect(response.statusCode).toBe(200);
1483
+ expect(response.body).toEqual({
1484
+ ok: true,
1485
+ issueUrl: 'https://github.com/acme-labs/portal/issues/42',
1486
+ });
1487
+ expect(issueRepository.createNewIssue).toHaveBeenCalledWith(
1488
+ 'acme-labs',
1489
+ 'portal',
1490
+ 'New task title',
1491
+ '',
1492
+ [],
1493
+ [],
1494
+ );
1495
+ expect(issueRepository.addIssueToProject).toHaveBeenCalledWith(
1496
+ storyProject,
1497
+ 'https://github.com/acme-labs/portal/issues/42',
1498
+ );
1499
+ expect(issueRepository.updateStory).toHaveBeenCalledWith(
1500
+ expect.objectContaining({ story: storyProject.story }),
1501
+ createdIssue,
1502
+ 'opt_blue',
1503
+ );
1504
+ });
1505
+
1506
+ it('resolves the issue repository from a proxy url of the target repo', async () => {
1507
+ const resolvedUrls: string[] = [];
1508
+ const recordingContext: ConsoleOperationContext = {
1509
+ ...contextForProject(projectWithStory()),
1510
+ resolveIssueRepository: (url: string) => {
1511
+ resolvedUrls.push(url);
1512
+ return issueRepository;
1513
+ },
1514
+ };
1515
+ issueRepository.get.mockResolvedValue(null);
1516
+
1517
+ await handleCreateIssue(recordingContext, {
1518
+ pjcode: 'acme',
1519
+ title: 'Task',
1520
+ storyOptionId: 'opt_blue',
1521
+ nameWithOwner: 'acme-labs/portal',
1522
+ });
1523
+
1524
+ expect(resolvedUrls).toContain(
1525
+ 'https://github.com/acme-labs/portal/issues/0',
1526
+ );
1527
+ });
1528
+
1529
+ it('skips updateStory when get returns null after addIssueToProject', async () => {
1530
+ issueRepository.get.mockResolvedValue(null);
1531
+
1532
+ const response = await handleCreateIssue(
1533
+ contextForProject(projectWithStory()),
1534
+ {
1535
+ pjcode: 'acme',
1536
+ title: 'Task without project item',
1537
+ storyOptionId: 'opt_green',
1538
+ nameWithOwner: 'acme-labs/portal',
1539
+ },
1540
+ );
1541
+
1542
+ expect(response.statusCode).toBe(200);
1543
+ expect(issueRepository.updateStory).not.toHaveBeenCalled();
1544
+ });
1545
+
1546
+ it('rejects when title is missing', async () => {
1547
+ const response = await handleCreateIssue(context, {
1548
+ pjcode: 'acme',
1549
+ storyOptionId: 'opt_blue',
1550
+ nameWithOwner: 'acme-labs/portal',
1551
+ });
1552
+ expect(response).toEqual({
1553
+ statusCode: 400,
1554
+ body: { error: 'title is required' },
1555
+ });
1556
+ });
1557
+
1558
+ it('rejects when storyOptionId is missing', async () => {
1559
+ const response = await handleCreateIssue(context, {
1560
+ pjcode: 'acme',
1561
+ title: 'Task',
1562
+ nameWithOwner: 'acme-labs/portal',
1563
+ });
1564
+ expect(response).toEqual({
1565
+ statusCode: 400,
1566
+ body: { error: 'storyOptionId is required' },
1567
+ });
1568
+ });
1569
+
1570
+ it('rejects when nameWithOwner is missing', async () => {
1571
+ const response = await handleCreateIssue(context, {
1572
+ pjcode: 'acme',
1573
+ title: 'Task',
1574
+ storyOptionId: 'opt_blue',
1575
+ });
1576
+ expect(response).toEqual({
1577
+ statusCode: 400,
1578
+ body: { error: 'nameWithOwner is required' },
1579
+ });
1580
+ });
1581
+
1582
+ it('rejects when nameWithOwner has no slash', async () => {
1583
+ const response = await handleCreateIssue(context, {
1584
+ pjcode: 'acme',
1585
+ title: 'Task',
1586
+ storyOptionId: 'opt_blue',
1587
+ nameWithOwner: 'noslash',
1588
+ });
1589
+ expect(response).toEqual({
1590
+ statusCode: 400,
1591
+ body: { error: 'nameWithOwner must be in owner/repo format' },
1592
+ });
1593
+ });
1594
+
1595
+ it('rejects when pjcode is not configured', async () => {
1596
+ const response = await handleCreateIssue(context, {
1597
+ pjcode: 'unknown',
1598
+ title: 'Task',
1599
+ storyOptionId: 'opt_blue',
1600
+ nameWithOwner: 'acme-labs/portal',
1601
+ });
1602
+ expect(response).toEqual({
1603
+ statusCode: 400,
1604
+ body: { error: 'no project configured for pjcode "unknown"' },
1605
+ });
1606
+ });
1607
+
1608
+ it('rejects when the project has no story field', async () => {
1609
+ const projectWithoutStory: Project = { ...project, story: null };
1610
+
1611
+ const response = await handleCreateIssue(
1612
+ contextForProject(projectWithoutStory),
1613
+ {
1614
+ pjcode: 'acme',
1615
+ title: 'Task',
1616
+ storyOptionId: 'opt_blue',
1617
+ nameWithOwner: 'acme-labs/portal',
1618
+ },
1619
+ );
1620
+ expect(response).toEqual({
1621
+ statusCode: 400,
1622
+ body: { error: 'project does not have a story field' },
1623
+ });
1624
+ });
1625
+
1626
+ it('rejects when the storyOptionId is not found in the project', async () => {
1627
+ const response = await handleCreateIssue(
1628
+ contextForProject(projectWithStory()),
1629
+ {
1630
+ pjcode: 'acme',
1631
+ title: 'Task',
1632
+ storyOptionId: 'nonexistent',
1633
+ nameWithOwner: 'acme-labs/portal',
1634
+ },
1635
+ );
1636
+ expect(response).toEqual({
1637
+ statusCode: 400,
1638
+ body: { error: 'story option "nonexistent" not found in project' },
1639
+ });
1640
+ });
1641
+ });
1431
1642
  });
@@ -582,6 +582,69 @@ export const handleAttachmentUpload = async (
582
582
  };
583
583
  };
584
584
 
585
+ export const handleCreateIssue = async (
586
+ context: ConsoleOperationContext,
587
+ body: Record<string, unknown>,
588
+ ): Promise<ConsoleOperationResponse> => {
589
+ const title = body.title;
590
+ const storyOptionId = body.storyOptionId;
591
+ const nameWithOwner = body.nameWithOwner;
592
+ if (!isNonEmptyString(title)) {
593
+ return badRequest('title is required');
594
+ }
595
+ if (!isNonEmptyString(storyOptionId)) {
596
+ return badRequest('storyOptionId is required');
597
+ }
598
+ if (!isNonEmptyString(nameWithOwner)) {
599
+ return badRequest('nameWithOwner is required');
600
+ }
601
+ const slashIndex = nameWithOwner.indexOf('/');
602
+ if (slashIndex <= 0 || slashIndex === nameWithOwner.length - 1) {
603
+ return badRequest('nameWithOwner must be in owner/repo format');
604
+ }
605
+ const org = nameWithOwner.slice(0, slashIndex);
606
+ const repo = nameWithOwner.slice(slashIndex + 1);
607
+
608
+ const binding = await resolveBinding(context, body);
609
+ if (isOperationResponse(binding)) {
610
+ return binding;
611
+ }
612
+ const { project } = binding;
613
+
614
+ if (project.story === null) {
615
+ return badRequest('project does not have a story field');
616
+ }
617
+ const storyOption = project.story.stories.find((s) => s.id === storyOptionId);
618
+ if (storyOption === undefined) {
619
+ return badRequest(`story option "${storyOptionId}" not found in project`);
620
+ }
621
+
622
+ const proxyUrl = `https://github.com/${nameWithOwner}/issues/0`;
623
+ const issueRepository = context.resolveIssueRepository(proxyUrl);
624
+ const issueNumber = await issueRepository.createNewIssue(
625
+ org,
626
+ repo,
627
+ title,
628
+ '',
629
+ [],
630
+ [],
631
+ );
632
+ const issueUrl = `https://github.com/${nameWithOwner}/issues/${issueNumber}`;
633
+
634
+ await issueRepository.addIssueToProject(project, issueUrl);
635
+
636
+ const addedIssue = await issueRepository.get(issueUrl, project);
637
+ if (addedIssue !== null) {
638
+ await issueRepository.updateStory(
639
+ { ...project, story: project.story },
640
+ addedIssue,
641
+ storyOptionId,
642
+ );
643
+ }
644
+
645
+ return { statusCode: 200, body: { ok: true, issueUrl } };
646
+ };
647
+
585
648
  export const handleReviewComment = async (
586
649
  context: ConsoleOperationContext,
587
650
  body: Record<string, unknown>,
@@ -8,6 +8,7 @@ export const CONSOLE_LIST_TAB_NAMES: ConsoleTabName[] = [
8
8
  'failed-preparation',
9
9
  'todo-by-human',
10
10
  'todo-by-agent',
11
+ 'stories',
11
12
  ];
12
13
 
13
14
  const UNKNOWN_STORY_SORT_INDEX = 999999;
@@ -336,3 +336,64 @@ test('opens the comment input with the item detail, keeps it on screen while the
336
336
  )?.height;
337
337
  expect(dockHeightAfterPosting).toBe(dockHeightBeforePosting);
338
338
  });
339
+
340
+ test('renders the stories tab with non-gray stories, their open item counts, and an add-task button', async ({
341
+ page,
342
+ }) => {
343
+ await page.goto(harness.appRootUrl);
344
+
345
+ await tabByLabel(page, 'Stories').click();
346
+ await expect(activeTabLabel(page)).toHaveText('Stories');
347
+
348
+ const tdpmRow = page.locator('.console-story-list-row', {
349
+ hasText: 'TDPM Console port',
350
+ });
351
+ await expect(tdpmRow.locator('.console-story-count')).toHaveText('6');
352
+ await expect(
353
+ tdpmRow.locator('.console-op-button', { hasText: 'Add task' }),
354
+ ).toBeVisible();
355
+
356
+ const publishRow = page.locator('.console-story-list-row', {
357
+ hasText: 'Publish product documentation site',
358
+ });
359
+ await expect(publishRow.locator('.console-story-count')).toHaveText('1');
360
+
361
+ await expect(
362
+ page.locator('.console-story-list-row', {
363
+ hasText: 'regular / workflow improvement',
364
+ }),
365
+ ).toHaveCount(0);
366
+ });
367
+
368
+ test('creates an issue for a story when the add-task button and form are used', async ({
369
+ page,
370
+ }) => {
371
+ await page.goto(harness.appRootUrl);
372
+
373
+ await tabByLabel(page, 'Stories').click();
374
+
375
+ const tdpmRow = page.locator('.console-story-list-row', {
376
+ hasText: 'TDPM Console port',
377
+ });
378
+ await tdpmRow.locator('.console-op-button', { hasText: 'Add task' }).click();
379
+
380
+ await page
381
+ .locator('.console-story-create-input')
382
+ .fill('New task for TDPM Console port');
383
+ await page
384
+ .locator('.console-story-create-form .console-op-button', {
385
+ hasText: 'Create',
386
+ })
387
+ .click();
388
+
389
+ await expect
390
+ .poll(() => harness.createIssueCalls.length, { timeout: 10000 })
391
+ .toBe(1);
392
+ expect(harness.createIssueCalls[0].title).toBe(
393
+ 'New task for TDPM Console port',
394
+ );
395
+ expect(harness.createIssueCalls[0].org).toBe('HiromiShikata');
396
+ expect(harness.createIssueCalls[0].repo).toBe(
397
+ 'npm-cli-github-issue-tower-defence-management',
398
+ );
399
+ });
@@ -38,6 +38,12 @@ export type ConsoleE2eRequestChangesCall = {
38
38
  inlineCommentLocation: { line: number; side: string } | null;
39
39
  };
40
40
 
41
+ export type ConsoleE2eCreateIssueCall = {
42
+ org: string;
43
+ repo: string;
44
+ title: string;
45
+ };
46
+
41
47
  type ConsoleFixtureListItem = {
42
48
  number: number;
43
49
  title: string;
@@ -275,6 +281,39 @@ export const CONSOLE_E2E_TAB_ITEMS: Record<string, ConsoleFixtureListItem[]> = {
275
281
  ],
276
282
  };
277
283
 
284
+ const CONSOLE_E2E_STORIES_SNAPSHOT = {
285
+ pjcode: CONSOLE_E2E_PJCODE,
286
+ generatedAt: '2026-06-18T01:22:09.000Z',
287
+ stories: [
288
+ {
289
+ storyName: 'TDPM Console port',
290
+ storyOptionId: '1491051e',
291
+ color: 'BLUE',
292
+ openItemCount: 6,
293
+ },
294
+ {
295
+ storyName: 'Publish product documentation site',
296
+ storyOptionId: 'f7cd5cbc',
297
+ color: 'GREEN',
298
+ openItemCount: 1,
299
+ },
300
+ {
301
+ storyName: 'regular / WORKFLOW BLOCKER',
302
+ storyOptionId: 'a3b9c4d2',
303
+ color: 'RED',
304
+ openItemCount: 1,
305
+ },
306
+ ],
307
+ storyOrder: [
308
+ 'TDPM Console port',
309
+ 'regular / workflow improvement',
310
+ 'Publish product documentation site',
311
+ 'regular / WORKFLOW BLOCKER',
312
+ ],
313
+ storyColors: STORY_COLORS,
314
+ defaultNameWithOwner: REPO_NAME_WITH_OWNER,
315
+ };
316
+
278
317
  const writeFixtureData = (consoleDataOutputDir: string): void => {
279
318
  for (const [tab, items] of Object.entries(CONSOLE_E2E_TAB_ITEMS)) {
280
319
  const tabDir = path.join(consoleDataOutputDir, CONSOLE_E2E_PJCODE, tab);
@@ -284,6 +323,16 @@ const writeFixtureData = (consoleDataOutputDir: string): void => {
284
323
  JSON.stringify(buildSnapshot(items)),
285
324
  );
286
325
  }
326
+ const storiesTabDir = path.join(
327
+ consoleDataOutputDir,
328
+ CONSOLE_E2E_PJCODE,
329
+ 'stories',
330
+ );
331
+ fs.mkdirSync(storiesTabDir, { recursive: true });
332
+ fs.writeFileSync(
333
+ path.join(storiesTabDir, 'list.json'),
334
+ JSON.stringify(CONSOLE_E2E_STORIES_SNAPSHOT),
335
+ );
287
336
  };
288
337
 
289
338
  const buildE2eProject = (): Project => ({
@@ -408,13 +457,21 @@ const inlineCommentPullRequestDetail: PullRequestDetail = {
408
457
  const createStubIssueRepository = (
409
458
  reviewCommentCalls: ConsoleE2eReviewCommentCall[],
410
459
  requestChangesCalls: ConsoleE2eRequestChangesCall[],
460
+ createIssueCalls: ConsoleE2eCreateIssueCall[],
411
461
  ): IssueRepository => ({
412
462
  getAllIssues: () => notImplemented('getAllIssues'),
413
463
  getIssueByUrl: async (url: string): Promise<Issue | null> =>
414
464
  buildIssueForUrl(url),
415
465
  getIssueBodyByUrl: async (url: string): Promise<string | null> =>
416
466
  buildIssueForUrl(url)?.body ?? null,
417
- createNewIssue: () => notImplemented('createNewIssue'),
467
+ createNewIssue: async (
468
+ org: string,
469
+ repo: string,
470
+ title: string,
471
+ ): Promise<number> => {
472
+ createIssueCalls.push({ org, repo, title });
473
+ return 9001;
474
+ },
418
475
  searchIssue: () => notImplemented('searchIssue'),
419
476
  updateIssue: () => notImplemented('updateIssue'),
420
477
  updateIssueBody: () => notImplemented('updateIssueBody'),
@@ -507,7 +564,7 @@ const createStubIssueRepository = (
507
564
  createCommentByUrl: async (): Promise<void> => undefined,
508
565
  getAllOpened: () => notImplemented('getAllOpened'),
509
566
  getStoryObjectMap: () => notImplemented('getStoryObjectMap'),
510
- addIssueToProject: () => notImplemented('addIssueToProject'),
567
+ addIssueToProject: async (): Promise<void> => undefined,
511
568
  setDependedIssueUrl: () => notImplemented('setDependedIssueUrl'),
512
569
  getIssueOrPullRequestBody: async (): Promise<string> =>
513
570
  [
@@ -572,6 +629,7 @@ export type ConsoleE2eHarness = {
572
629
  consoleDataOutputDir: string;
573
630
  reviewCommentCalls: ConsoleE2eReviewCommentCall[];
574
631
  requestChangesCalls: ConsoleE2eRequestChangesCall[];
632
+ createIssueCalls: ConsoleE2eCreateIssueCall[];
575
633
  stop: () => Promise<void>;
576
634
  };
577
635
 
@@ -592,6 +650,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
592
650
 
593
651
  const reviewCommentCalls: ConsoleE2eReviewCommentCall[] = [];
594
652
  const requestChangesCalls: ConsoleE2eRequestChangesCall[] = [];
653
+ const createIssueCalls: ConsoleE2eCreateIssueCall[] = [];
595
654
 
596
655
  const server = await startWebServer({
597
656
  accessToken: CONSOLE_E2E_TOKEN,
@@ -600,6 +659,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
600
659
  issueRepository: createStubIssueRepository(
601
660
  reviewCommentCalls,
602
661
  requestChangesCalls,
662
+ createIssueCalls,
603
663
  ),
604
664
  resolveProject,
605
665
  isPjcodeConfigured,
@@ -629,6 +689,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
629
689
  consoleDataOutputDir,
630
690
  reviewCommentCalls,
631
691
  requestChangesCalls,
692
+ createIssueCalls,
632
693
  stop: async (): Promise<void> => {
633
694
  await closeServer(server);
634
695
  fs.rmSync(tmpRoot, { recursive: true, force: true });
@@ -26,6 +26,7 @@ const counts: Record<ConsoleTabName, number> = {
26
26
  'failed-preparation': 2,
27
27
  'todo-by-human': 66,
28
28
  'todo-by-agent': 24,
29
+ stories: 3,
29
30
  };
30
31
 
31
32
  export const AllTabsWithCounts: Story = {
@@ -46,6 +47,7 @@ export const ZeroCountTabsHidden: Story = {
46
47
  'failed-preparation': 0,
47
48
  'todo-by-human': 0,
48
49
  'todo-by-agent': 0,
50
+ stories: 0,
49
51
  },
50
52
  },
51
53
  };
@@ -61,6 +63,7 @@ export const ZeroCountActiveTabStaysVisible: Story = {
61
63
  'failed-preparation': 0,
62
64
  'todo-by-human': 0,
63
65
  'todo-by-agent': 0,
66
+ stories: 0,
64
67
  },
65
68
  },
66
69
  };
@@ -76,6 +79,7 @@ export const AfterAutoAdvanceToNextTab: Story = {
76
79
  'failed-preparation': 2,
77
80
  'todo-by-human': 4,
78
81
  'todo-by-agent': 0,
82
+ stories: 0,
79
83
  },
80
84
  onSelectTab: () => {},
81
85
  },
@@ -10,6 +10,7 @@ const counts: Record<ConsoleTabName, number> = {
10
10
  'failed-preparation': 0,
11
11
  'todo-by-human': 2,
12
12
  'todo-by-agent': 3,
13
+ stories: 0,
13
14
  };
14
15
 
15
16
  const baseProps = {
@@ -0,0 +1,77 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsoleStoryList } from './ConsoleStoryList';
3
+
4
+ const stories = [
5
+ {
6
+ storyName: 'TDPM Console port',
7
+ storyOptionId: '1491051e',
8
+ color: 'BLUE' as const,
9
+ openItemCount: 12,
10
+ },
11
+ {
12
+ storyName: 'Publish product documentation site',
13
+ storyOptionId: 'f7cd5cbc',
14
+ color: 'GREEN' as const,
15
+ openItemCount: 3,
16
+ },
17
+ {
18
+ storyName: 'Move to Okinawa',
19
+ storyOptionId: '564803ee',
20
+ color: 'PURPLE' as const,
21
+ openItemCount: 0,
22
+ },
23
+ {
24
+ storyName: 'regular / regular payment invoice tax',
25
+ storyOptionId: 'd7cdcb61',
26
+ color: 'YELLOW' as const,
27
+ openItemCount: 7,
28
+ },
29
+ {
30
+ storyName: 'regular / WORKFLOW BLOCKER',
31
+ storyOptionId: 'e35b3da2',
32
+ color: 'RED' as const,
33
+ openItemCount: 2,
34
+ },
35
+ ];
36
+
37
+ const meta: Meta<typeof ConsoleStoryList> = {
38
+ title: 'Console/ConsoleStoryList',
39
+ component: ConsoleStoryList,
40
+ args: { onCreateIssue: () => Promise.resolve() },
41
+ };
42
+
43
+ export default meta;
44
+
45
+ type Story = StoryObj<typeof ConsoleStoryList>;
46
+
47
+ export const WithStories: Story = {
48
+ args: {
49
+ stories,
50
+ isLoading: false,
51
+ error: null,
52
+ },
53
+ };
54
+
55
+ export const Loading: Story = {
56
+ args: {
57
+ stories: [],
58
+ isLoading: true,
59
+ error: null,
60
+ },
61
+ };
62
+
63
+ export const Empty: Story = {
64
+ args: {
65
+ stories: [],
66
+ isLoading: false,
67
+ error: null,
68
+ },
69
+ };
70
+
71
+ export const ErrorState: Story = {
72
+ args: {
73
+ stories: [],
74
+ isLoading: false,
75
+ error: 'HTTP 404',
76
+ },
77
+ };