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.
- package/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +44 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.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-CrSqMAVR.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/webServer.js +2 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +3 -3
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/domain/entities/RequiredProjectField.js +0 -5
- package/bin/domain/entities/RequiredProjectField.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +12 -4
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +13 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +23 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +211 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +63 -0
- package/src/adapter/entry-points/console/consoleTabNames.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +61 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +63 -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/ConsoleStoryList.stories.tsx +77 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +196 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +163 -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.ts +15 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +29 -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 +10 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +43 -8
- package/src/adapter/entry-points/console/ui-dist/assets/index-CrSqMAVR.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.ts +3 -0
- package/src/adapter/repositories/GraphqlProjectRepository.ts +3 -3
- package/src/domain/entities/RequiredProjectField.ts +0 -5
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +14 -4
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +15 -5
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +16 -5
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +13 -1
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +86 -1
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +49 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +18 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CHUtr_4X.js +0 -86
- 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>,
|
|
@@ -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:
|
|
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: () =>
|
|
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
|
},
|
|
@@ -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
|
+
};
|