github-issue-tower-defence-management 1.193.0 → 1.194.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 +21 -0
- package/README.md +4 -0
- package/bin/adapter/entry-points/cli/index.js +19 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/NodeLocalCommandRunner.js +5 -0
- package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
- package/bin/adapter/repositories/NodeTmuxSessionRepository.js +29 -0
- package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +4 -2
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +8 -3
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -2
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +12 -2
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +1 -1
- package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +1 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +1 -3
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js +12 -4
- package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +102 -0
- package/src/adapter/entry-points/cli/index.ts +43 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +45 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +7 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +31 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +96 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +20 -2
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +159 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +17 -1
- package/src/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
- package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +4 -1
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +2 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +4 -0
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +1 -0
- package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +16 -0
- package/src/adapter/repositories/NodeLocalCommandRunner.ts +5 -1
- package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +1 -0
- package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +132 -0
- package/src/adapter/repositories/NodeTmuxSessionRepository.ts +37 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +38 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -2
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +2 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +78 -4
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +15 -3
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +9 -2
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +10 -2
- package/src/domain/usecases/StartPreparationUseCase.test.ts +110 -2
- package/src/domain/usecases/StartPreparationUseCase.ts +34 -17
- package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +4 -0
- package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +1 -1
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +5 -4
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +9 -11
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +63 -0
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +13 -4
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -0
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
- package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +1 -0
- package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts.map +1 -1
- package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +1 -1
- package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +1 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
- package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts +1 -0
- package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts.map +1 -1
|
@@ -74,8 +74,12 @@ class GenerateTokenStatusUseCase {
|
|
|
74
74
|
}
|
|
75
75
|
const fiveHourReset = snapshot.fiveHourReset > 0 ? snapshot.fiveHourReset : null;
|
|
76
76
|
const sevenDayReset = snapshot.sevenDayReset > 0 ? snapshot.sevenDayReset : null;
|
|
77
|
-
const fiveHourExpired = fiveHourReset !== null &&
|
|
78
|
-
|
|
77
|
+
const fiveHourExpired = fiveHourReset !== null &&
|
|
78
|
+
fiveHourReset < nowEpochSeconds &&
|
|
79
|
+
snapshot.lastUpdatedEpoch >= fiveHourReset;
|
|
80
|
+
const sevenDayExpired = sevenDayReset !== null &&
|
|
81
|
+
sevenDayReset < nowEpochSeconds &&
|
|
82
|
+
snapshot.lastUpdatedEpoch >= sevenDayReset;
|
|
79
83
|
return {
|
|
80
84
|
fiveHourUtilizationPercent: fiveHourExpired
|
|
81
85
|
? 0
|
|
@@ -108,8 +112,12 @@ class GenerateTokenStatusUseCase {
|
|
|
108
112
|
partial: true,
|
|
109
113
|
};
|
|
110
114
|
}
|
|
111
|
-
const fiveHourExpired = snapshot.fiveHourReset > 0 &&
|
|
112
|
-
|
|
115
|
+
const fiveHourExpired = snapshot.fiveHourReset > 0 &&
|
|
116
|
+
snapshot.fiveHourReset < nowEpochSeconds &&
|
|
117
|
+
snapshot.lastUpdatedEpoch >= snapshot.fiveHourReset;
|
|
118
|
+
const sevenDayExpired = snapshot.sevenDayReset > 0 &&
|
|
119
|
+
snapshot.sevenDayReset < nowEpochSeconds &&
|
|
120
|
+
snapshot.lastUpdatedEpoch >= snapshot.sevenDayReset;
|
|
113
121
|
return {
|
|
114
122
|
fiveHourUtilization: fiveHourExpired ? 0 : snapshot.fiveHourUtilization,
|
|
115
123
|
sevenDayUtilization: sevenDayExpired ? 0 : snapshot.sevenDayUtilization,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerateTokenStatusUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"GenerateTokenStatusUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ jest.mock('fs', () => {
|
|
|
8
8
|
return {
|
|
9
9
|
...actualFs,
|
|
10
10
|
readFileSync: jest.fn(actualFs.readFileSync),
|
|
11
|
+
existsSync: jest.fn(actualFs.existsSync),
|
|
11
12
|
};
|
|
12
13
|
});
|
|
13
14
|
import {
|
|
@@ -70,9 +71,11 @@ const mockRunCommand = jest.fn<
|
|
|
70
71
|
Promise<{ stdout: string; stderr: string; exitCode: number }>,
|
|
71
72
|
[string, string[]]
|
|
72
73
|
>();
|
|
74
|
+
const mockSpawnInteractive = jest.fn<void, [string, string[]]>();
|
|
73
75
|
jest.mock('../../repositories/NodeLocalCommandRunner', () => ({
|
|
74
76
|
NodeLocalCommandRunner: jest.fn().mockImplementation(() => ({
|
|
75
77
|
runCommand: mockRunCommand,
|
|
78
|
+
spawnInteractive: mockSpawnInteractive,
|
|
76
79
|
})),
|
|
77
80
|
}));
|
|
78
81
|
jest.mock('../../repositories/OauthAPIClaudeRepository', () => ({
|
|
@@ -2463,6 +2466,105 @@ mysteryKey: 'value'
|
|
|
2463
2466
|
});
|
|
2464
2467
|
});
|
|
2465
2468
|
|
|
2469
|
+
describe('attachOrCreate', () => {
|
|
2470
|
+
const issueUrl = 'https://github.com/owner/repo/issues/9';
|
|
2471
|
+
let scopeLibPath = '';
|
|
2472
|
+
|
|
2473
|
+
beforeEach(() => {
|
|
2474
|
+
scopeLibPath = path.join(
|
|
2475
|
+
os.tmpdir(),
|
|
2476
|
+
`test-cl-scope-lib-${Date.now()}.sh`,
|
|
2477
|
+
);
|
|
2478
|
+
fs.writeFileSync(scopeLibPath, '#!/bin/bash\n');
|
|
2479
|
+
process.env.CL_SCOPE_LIB_PATH = scopeLibPath;
|
|
2480
|
+
});
|
|
2481
|
+
|
|
2482
|
+
afterEach(() => {
|
|
2483
|
+
if (fs.existsSync(scopeLibPath)) {
|
|
2484
|
+
fs.unlinkSync(scopeLibPath);
|
|
2485
|
+
}
|
|
2486
|
+
delete process.env.CL_SCOPE_LIB_PATH;
|
|
2487
|
+
});
|
|
2488
|
+
|
|
2489
|
+
it('attaches to the registered session when registry returns a live session', async () => {
|
|
2490
|
+
mockRunCommand
|
|
2491
|
+
.mockResolvedValueOnce({ stdout: 'wq7x3mk\n', stderr: '', exitCode: 0 })
|
|
2492
|
+
.mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 });
|
|
2493
|
+
|
|
2494
|
+
await program.parseAsync([
|
|
2495
|
+
'node',
|
|
2496
|
+
'test',
|
|
2497
|
+
'attachOrCreate',
|
|
2498
|
+
'--issueUrl',
|
|
2499
|
+
issueUrl,
|
|
2500
|
+
]);
|
|
2501
|
+
|
|
2502
|
+
expect(mockRunCommand.mock.calls[0]).toEqual([
|
|
2503
|
+
'bash',
|
|
2504
|
+
[scopeLibPath, 'registry-get-session', issueUrl],
|
|
2505
|
+
]);
|
|
2506
|
+
expect(mockRunCommand.mock.calls[1]).toEqual([
|
|
2507
|
+
'tmux',
|
|
2508
|
+
['has-session', '-t', '=wq7x3mk'],
|
|
2509
|
+
]);
|
|
2510
|
+
expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
|
|
2511
|
+
'attach-session',
|
|
2512
|
+
'-t',
|
|
2513
|
+
'=wq7x3mk',
|
|
2514
|
+
]);
|
|
2515
|
+
});
|
|
2516
|
+
|
|
2517
|
+
it('creates a new session when no registered session is found', async () => {
|
|
2518
|
+
mockRunCommand.mockResolvedValueOnce({
|
|
2519
|
+
stdout: '',
|
|
2520
|
+
stderr: '',
|
|
2521
|
+
exitCode: 0,
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
await program.parseAsync([
|
|
2525
|
+
'node',
|
|
2526
|
+
'test',
|
|
2527
|
+
'attachOrCreate',
|
|
2528
|
+
'--issueUrl',
|
|
2529
|
+
issueUrl,
|
|
2530
|
+
]);
|
|
2531
|
+
|
|
2532
|
+
expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
|
|
2533
|
+
'new-session',
|
|
2534
|
+
'-A',
|
|
2535
|
+
'-s',
|
|
2536
|
+
issueUrl,
|
|
2537
|
+
'cl',
|
|
2538
|
+
issueUrl,
|
|
2539
|
+
]);
|
|
2540
|
+
});
|
|
2541
|
+
|
|
2542
|
+
it('creates a new session when no scope lib path is available', async () => {
|
|
2543
|
+
jest
|
|
2544
|
+
.mocked(fs.existsSync)
|
|
2545
|
+
.mockReturnValueOnce(false)
|
|
2546
|
+
.mockReturnValueOnce(false);
|
|
2547
|
+
|
|
2548
|
+
await program.parseAsync([
|
|
2549
|
+
'node',
|
|
2550
|
+
'test',
|
|
2551
|
+
'attachOrCreate',
|
|
2552
|
+
'--issueUrl',
|
|
2553
|
+
issueUrl,
|
|
2554
|
+
]);
|
|
2555
|
+
|
|
2556
|
+
expect(mockRunCommand.mock.calls).toHaveLength(0);
|
|
2557
|
+
expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
|
|
2558
|
+
'new-session',
|
|
2559
|
+
'-A',
|
|
2560
|
+
'-s',
|
|
2561
|
+
issueUrl,
|
|
2562
|
+
'cl',
|
|
2563
|
+
issueUrl,
|
|
2564
|
+
]);
|
|
2565
|
+
});
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2466
2568
|
describe('ownerCallFileAppend and ownerCallFileDelete', () => {
|
|
2467
2569
|
const issueUrl = 'https://github.com/OWNER/REPO/issues/1';
|
|
2468
2570
|
const otherIssueUrl = 'https://github.com/OWNER/REPO/issues/2';
|
|
@@ -36,6 +36,7 @@ import { ProcTakeOwnershipSpawnRepository } from '../../repositories/ProcTakeOwn
|
|
|
36
36
|
import { DEFAULT_THRESHOLD_FOR_DISPATCH_LOOP } from '../../../domain/usecases/resolveNextStepAgentDispatchRepetition';
|
|
37
37
|
import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
|
|
38
38
|
import { SystemDateRepository } from '../../repositories/SystemDateRepository';
|
|
39
|
+
import * as os from 'os';
|
|
39
40
|
import {
|
|
40
41
|
createConsoleGithubTokenResolver,
|
|
41
42
|
createConsoleGithubTokenResolverByItemUrl,
|
|
@@ -198,6 +199,27 @@ type KillTmuxSessionOptions = {
|
|
|
198
199
|
self?: boolean;
|
|
199
200
|
};
|
|
200
201
|
|
|
202
|
+
type AttachOrCreateOptions = {
|
|
203
|
+
issueUrl: string;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const resolveScopeLibPath = (): string | null => {
|
|
207
|
+
const explicitPath = process.env.CL_SCOPE_LIB_PATH;
|
|
208
|
+
if (explicitPath !== undefined && fs.existsSync(explicitPath)) {
|
|
209
|
+
return explicitPath;
|
|
210
|
+
}
|
|
211
|
+
const defaultPath = path.join(
|
|
212
|
+
os.homedir(),
|
|
213
|
+
'git',
|
|
214
|
+
'secretary',
|
|
215
|
+
'machine',
|
|
216
|
+
'sk',
|
|
217
|
+
'sh',
|
|
218
|
+
'cl-scope-lib.sh',
|
|
219
|
+
);
|
|
220
|
+
return fs.existsSync(defaultPath) ? defaultPath : null;
|
|
221
|
+
};
|
|
222
|
+
|
|
201
223
|
const buildGithubRepositoryParams = (
|
|
202
224
|
localStorageRepository: LocalStorageRepository,
|
|
203
225
|
token: string,
|
|
@@ -1302,6 +1324,27 @@ program
|
|
|
1302
1324
|
});
|
|
1303
1325
|
});
|
|
1304
1326
|
|
|
1327
|
+
program
|
|
1328
|
+
.command('attachOrCreate')
|
|
1329
|
+
.description(
|
|
1330
|
+
'Attach to an existing tmux session registered for the given issue URL, or create a new one. Looks up the cl session registry via cl-scope-lib.sh to find a registered session name; if that session is still alive it attaches to it, otherwise creates a new interactive session running cl for the issue URL.',
|
|
1331
|
+
)
|
|
1332
|
+
.requiredOption(
|
|
1333
|
+
'--issueUrl <url>',
|
|
1334
|
+
'GitHub issue URL to attach to or create a session for',
|
|
1335
|
+
)
|
|
1336
|
+
.action(async (options: AttachOrCreateOptions) => {
|
|
1337
|
+
const localCommandRunner = new NodeLocalCommandRunner();
|
|
1338
|
+
const tmuxSessionRepository = new NodeTmuxSessionRepository(
|
|
1339
|
+
localCommandRunner,
|
|
1340
|
+
);
|
|
1341
|
+
const scopeLibPath = resolveScopeLibPath();
|
|
1342
|
+
await tmuxSessionRepository.attachOrCreateInteractiveSession(
|
|
1343
|
+
options.issueUrl,
|
|
1344
|
+
scopeLibPath,
|
|
1345
|
+
);
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1305
1348
|
export const reportFatalErrorAndExit = (error: unknown): void => {
|
|
1306
1349
|
console.error(error);
|
|
1307
1350
|
process.exit(1);
|
|
@@ -420,6 +420,51 @@ test('renders the stories tab with non-gray stories, their open item counts, and
|
|
|
420
420
|
).toHaveCount(0);
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
test('shows and hides gray stories with the Show archived toggle button on the stories tab', async ({
|
|
424
|
+
page,
|
|
425
|
+
}) => {
|
|
426
|
+
await page.goto(harness.appRootUrl);
|
|
427
|
+
|
|
428
|
+
await tabByLabel(page, 'Stories').click();
|
|
429
|
+
await expect(activeTabLabel(page)).toHaveText('Stories');
|
|
430
|
+
|
|
431
|
+
await expect(
|
|
432
|
+
page.locator('.console-op-button', { hasText: 'Show archived' }),
|
|
433
|
+
).toBeVisible();
|
|
434
|
+
|
|
435
|
+
await expect(
|
|
436
|
+
page.locator('.console-story-list-row', {
|
|
437
|
+
hasText: 'regular / workflow improvement',
|
|
438
|
+
}),
|
|
439
|
+
).toHaveCount(0);
|
|
440
|
+
|
|
441
|
+
await page
|
|
442
|
+
.locator('.console-op-button', { hasText: 'Show archived' })
|
|
443
|
+
.click();
|
|
444
|
+
|
|
445
|
+
await expect(
|
|
446
|
+
page.locator('.console-story-list-row', {
|
|
447
|
+
hasText: 'regular / workflow improvement',
|
|
448
|
+
}),
|
|
449
|
+
).toBeVisible();
|
|
450
|
+
await expect(
|
|
451
|
+
page.locator('.console-op-button', { hasText: 'Hide archived' }),
|
|
452
|
+
).toBeVisible();
|
|
453
|
+
|
|
454
|
+
await page
|
|
455
|
+
.locator('.console-op-button', { hasText: 'Hide archived' })
|
|
456
|
+
.click();
|
|
457
|
+
|
|
458
|
+
await expect(
|
|
459
|
+
page.locator('.console-story-list-row', {
|
|
460
|
+
hasText: 'regular / workflow improvement',
|
|
461
|
+
}),
|
|
462
|
+
).toHaveCount(0);
|
|
463
|
+
await expect(
|
|
464
|
+
page.locator('.console-op-button', { hasText: 'Show archived' }),
|
|
465
|
+
).toBeVisible();
|
|
466
|
+
});
|
|
467
|
+
|
|
423
468
|
test('shows the agent label and value in the list row and the agent chip in the detail view', async ({
|
|
424
469
|
page,
|
|
425
470
|
}) => {
|
|
@@ -295,6 +295,13 @@ const CONSOLE_E2E_STORIES_SNAPSHOT = {
|
|
|
295
295
|
storyViewUrl:
|
|
296
296
|
'https://github.com/orgs/HiromiShikata/projects/6/views/1?sliceBy%5Bvalue%5D=TDPM%20Console%20port',
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
storyName: 'regular / workflow improvement',
|
|
300
|
+
storyOptionId: '28415d6c',
|
|
301
|
+
color: 'GRAY',
|
|
302
|
+
openItemCount: 1,
|
|
303
|
+
storyViewUrl: null,
|
|
304
|
+
},
|
|
298
305
|
{
|
|
299
306
|
storyName: 'Publish product documentation site',
|
|
300
307
|
storyOptionId: 'f7cd5cbc',
|
|
@@ -47,13 +47,26 @@ const storiesWithUrl = storiesWithoutUrl.map((s) => ({
|
|
|
47
47
|
storyViewUrl: `${BASE_VIEW_URL}?sliceBy%5Bvalue%5D=${encodeURI(s.storyName).replace(/#/g, '%23').replace(/&/g, '%26')}`,
|
|
48
48
|
}));
|
|
49
49
|
|
|
50
|
+
const storiesWithArchived = [
|
|
51
|
+
...storiesWithoutUrl,
|
|
52
|
+
{
|
|
53
|
+
storyName: 'regular / NO STORY; SET STORY FIELD',
|
|
54
|
+
storyOptionId: '28415d6c',
|
|
55
|
+
color: 'GRAY' as const,
|
|
56
|
+
openItemCount: 5,
|
|
57
|
+
storyViewUrl: null,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
50
61
|
const meta: Meta<typeof ConsoleStoryList> = {
|
|
51
62
|
title: 'Console/ConsoleStoryList',
|
|
52
63
|
component: ConsoleStoryList,
|
|
53
64
|
args: {
|
|
65
|
+
showGray: false,
|
|
54
66
|
onCreateIssue: () => Promise.resolve(),
|
|
55
67
|
onAddStory: () => Promise.resolve(),
|
|
56
68
|
onSelectColor: () => undefined,
|
|
69
|
+
onToggleGray: () => undefined,
|
|
57
70
|
optimisticColors: {},
|
|
58
71
|
colorChangeInFlight: null,
|
|
59
72
|
colorErrors: {},
|
|
@@ -103,3 +116,21 @@ export const ErrorState: Story = {
|
|
|
103
116
|
error: 'HTTP 404',
|
|
104
117
|
},
|
|
105
118
|
};
|
|
119
|
+
|
|
120
|
+
export const WithArchivedHidden: Story = {
|
|
121
|
+
args: {
|
|
122
|
+
stories: storiesWithArchived,
|
|
123
|
+
isLoading: false,
|
|
124
|
+
error: null,
|
|
125
|
+
showGray: false,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const WithArchivedShown: Story = {
|
|
130
|
+
args: {
|
|
131
|
+
stories: storiesWithArchived,
|
|
132
|
+
isLoading: false,
|
|
133
|
+
error: null,
|
|
134
|
+
showGray: true,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -19,13 +19,25 @@ const storyEntries: ConsoleStoryEntry[] = [
|
|
|
19
19
|
},
|
|
20
20
|
];
|
|
21
21
|
|
|
22
|
+
const grayStoryEntry: ConsoleStoryEntry = {
|
|
23
|
+
storyName: 'Archived Story',
|
|
24
|
+
storyOptionId: 'gray-id',
|
|
25
|
+
color: 'GRAY',
|
|
26
|
+
openItemCount: 3,
|
|
27
|
+
storyViewUrl: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const entriesWithGray: ConsoleStoryEntry[] = [...storyEntries, grayStoryEntry];
|
|
31
|
+
|
|
22
32
|
const defaultProps = {
|
|
23
33
|
stories: storyEntries,
|
|
24
34
|
isLoading: false,
|
|
25
35
|
error: null,
|
|
36
|
+
showGray: false,
|
|
26
37
|
onCreateIssue: () => Promise.resolve(),
|
|
27
38
|
onAddStory: () => Promise.resolve(),
|
|
28
39
|
onSelectColor: () => undefined,
|
|
40
|
+
onToggleGray: () => undefined,
|
|
29
41
|
optimisticColors: {} as Record<string, ConsoleColor>,
|
|
30
42
|
colorChangeInFlight: null as string | null,
|
|
31
43
|
colorErrors: {} as Record<string, string>,
|
|
@@ -370,4 +382,88 @@ describe('ConsoleStoryList', () => {
|
|
|
370
382
|
});
|
|
371
383
|
expect(writeText).toHaveBeenCalledWith(storyEntries[0].storyName);
|
|
372
384
|
});
|
|
385
|
+
|
|
386
|
+
describe('gray story toggle', () => {
|
|
387
|
+
it('hides gray stories when showGray is false', () => {
|
|
388
|
+
const { queryByText } = render(
|
|
389
|
+
<ConsoleStoryList
|
|
390
|
+
{...defaultProps}
|
|
391
|
+
stories={entriesWithGray}
|
|
392
|
+
showGray={false}
|
|
393
|
+
/>,
|
|
394
|
+
);
|
|
395
|
+
expect(queryByText('Archived Story')).toBeNull();
|
|
396
|
+
expect(queryByText('TDPM Console port')).toBeInTheDocument();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('shows gray stories when showGray is true', () => {
|
|
400
|
+
const { getByText } = render(
|
|
401
|
+
<ConsoleStoryList
|
|
402
|
+
{...defaultProps}
|
|
403
|
+
stories={entriesWithGray}
|
|
404
|
+
showGray={true}
|
|
405
|
+
/>,
|
|
406
|
+
);
|
|
407
|
+
expect(getByText('Archived Story')).toBeInTheDocument();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('shows the toggle button labeled Show archived when at least one gray story exists and showGray is false', () => {
|
|
411
|
+
const { getByRole } = render(
|
|
412
|
+
<ConsoleStoryList
|
|
413
|
+
{...defaultProps}
|
|
414
|
+
stories={entriesWithGray}
|
|
415
|
+
showGray={false}
|
|
416
|
+
/>,
|
|
417
|
+
);
|
|
418
|
+
expect(
|
|
419
|
+
getByRole('button', { name: 'Show archived' }),
|
|
420
|
+
).toBeInTheDocument();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('shows the toggle button labeled Hide archived when showGray is true', () => {
|
|
424
|
+
const { getByRole } = render(
|
|
425
|
+
<ConsoleStoryList
|
|
426
|
+
{...defaultProps}
|
|
427
|
+
stories={entriesWithGray}
|
|
428
|
+
showGray={true}
|
|
429
|
+
/>,
|
|
430
|
+
);
|
|
431
|
+
expect(
|
|
432
|
+
getByRole('button', { name: 'Hide archived' }),
|
|
433
|
+
).toBeInTheDocument();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('does not show the toggle button when no gray stories exist', () => {
|
|
437
|
+
const { queryByRole } = render(
|
|
438
|
+
<ConsoleStoryList {...defaultProps} stories={storyEntries} />,
|
|
439
|
+
);
|
|
440
|
+
expect(queryByRole('button', { name: 'Show archived' })).toBeNull();
|
|
441
|
+
expect(queryByRole('button', { name: 'Hide archived' })).toBeNull();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('calls onToggleGray when the toggle button is clicked', () => {
|
|
445
|
+
const onToggleGray = jest.fn();
|
|
446
|
+
const { getByRole } = render(
|
|
447
|
+
<ConsoleStoryList
|
|
448
|
+
{...defaultProps}
|
|
449
|
+
stories={entriesWithGray}
|
|
450
|
+
showGray={false}
|
|
451
|
+
onToggleGray={onToggleGray}
|
|
452
|
+
/>,
|
|
453
|
+
);
|
|
454
|
+
fireEvent.click(getByRole('button', { name: 'Show archived' }));
|
|
455
|
+
expect(onToggleGray).toHaveBeenCalledTimes(1);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('shows No active stories when all stories are gray and showGray is false', () => {
|
|
459
|
+
const { getByText } = render(
|
|
460
|
+
<ConsoleStoryList
|
|
461
|
+
{...defaultProps}
|
|
462
|
+
stories={[grayStoryEntry]}
|
|
463
|
+
showGray={false}
|
|
464
|
+
/>,
|
|
465
|
+
);
|
|
466
|
+
expect(getByText('No active stories')).toBeInTheDocument();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
373
469
|
});
|
|
@@ -132,9 +132,11 @@ export type ConsoleStoryListProps = {
|
|
|
132
132
|
stories: ConsoleStoryEntry[];
|
|
133
133
|
isLoading: boolean;
|
|
134
134
|
error: string | null;
|
|
135
|
+
showGray: boolean;
|
|
135
136
|
onCreateIssue: (storyOptionId: string, title: string) => Promise<void>;
|
|
136
137
|
onAddStory: (storyName: string) => Promise<void>;
|
|
137
138
|
onSelectColor: (storyOptionId: string, newColor: ConsoleColor) => void;
|
|
139
|
+
onToggleGray: () => void;
|
|
138
140
|
optimisticColors: Record<string, ConsoleColor>;
|
|
139
141
|
colorChangeInFlight: string | null;
|
|
140
142
|
colorErrors: Record<string, string>;
|
|
@@ -144,9 +146,11 @@ export const ConsoleStoryList = ({
|
|
|
144
146
|
stories,
|
|
145
147
|
isLoading,
|
|
146
148
|
error,
|
|
149
|
+
showGray,
|
|
147
150
|
onCreateIssue,
|
|
148
151
|
onAddStory,
|
|
149
152
|
onSelectColor,
|
|
153
|
+
onToggleGray,
|
|
150
154
|
optimisticColors,
|
|
151
155
|
colorChangeInFlight,
|
|
152
156
|
colorErrors,
|
|
@@ -197,13 +201,18 @@ export const ConsoleStoryList = ({
|
|
|
197
201
|
onSelectColor(storyOptionId, newColor);
|
|
198
202
|
};
|
|
199
203
|
|
|
204
|
+
const visibleStories = showGray
|
|
205
|
+
? stories
|
|
206
|
+
: stories.filter((s) => s.color !== 'GRAY');
|
|
207
|
+
const hasGrayStories = stories.some((s) => s.color === 'GRAY');
|
|
208
|
+
|
|
200
209
|
return (
|
|
201
210
|
<div className="console-story-list-container">
|
|
202
|
-
{
|
|
211
|
+
{visibleStories.length === 0 ? (
|
|
203
212
|
<p className="console-list-empty">No active stories</p>
|
|
204
213
|
) : (
|
|
205
214
|
<ul className="console-story-list">
|
|
206
|
-
{
|
|
215
|
+
{visibleStories.map((entry) => {
|
|
207
216
|
const displayColor: ConsoleColor =
|
|
208
217
|
optimisticColors[entry.storyOptionId] ?? entry.color;
|
|
209
218
|
const palette = colorFromEnum(displayColor);
|
|
@@ -285,6 +294,15 @@ export const ConsoleStoryList = ({
|
|
|
285
294
|
})}
|
|
286
295
|
</ul>
|
|
287
296
|
)}
|
|
297
|
+
{hasGrayStories && (
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
className="console-op-button"
|
|
301
|
+
onClick={onToggleGray}
|
|
302
|
+
>
|
|
303
|
+
{showGray ? 'Hide archived' : 'Show archived'}
|
|
304
|
+
</button>
|
|
305
|
+
)}
|
|
288
306
|
<div className="console-add-story-section">
|
|
289
307
|
<button
|
|
290
308
|
type="button"
|