github-issue-tower-defence-management 1.185.1 → 1.186.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 +17 -0
- package/README.md +2 -0
- package/bin/adapter/entry-points/cli/index.js +2 -1
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +43 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js +7 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-wj6o8qvo.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-yZy1TUOo.css +2 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/bin/adapter/entry-points/console/webServer.js +3 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +5 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +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/issue/ApiV3CheerioRestIssueRepository.js +36 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +12 -0
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/domain/entities/Project.js +11 -1
- package/bin/domain/entities/Project.js.map +1 -1
- package/bin/domain/entities/RequiredProjectField.js +3 -2
- package/bin/domain/entities/RequiredProjectField.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +12 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueNoStatusUpdateUseCase.js +36 -0
- package/bin/domain/usecases/IssueNoStatusUpdateUseCase.js.map +1 -0
- package/bin/domain/usecases/IssueRejectionEvaluator.js +19 -0
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/QualityCheckAdvanceUseCase.js +1 -0
- package/bin/domain/usecases/QualityCheckAdvanceUseCase.js.map +1 -1
- package/bin/domain/usecases/ReopenedDoneIssueRevertUseCase.js +36 -0
- package/bin/domain/usecases/ReopenedDoneIssueRevertUseCase.js.map +1 -0
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +2 -5
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +5 -2
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +14 -10
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +199 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +65 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +26 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.ts +8 -1
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +40 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +18 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +114 -141
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +84 -3
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +23 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +73 -0
- package/src/adapter/entry-points/console/ui/src/index.css +35 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-wj6o8qvo.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-yZy1TUOo.css +2 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/src/adapter/entry-points/console/webServer.ts +7 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +11 -0
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +1 -0
- 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/ownerCallFileCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +1 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +90 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +48 -2
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +107 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +20 -0
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +1 -0
- package/src/domain/entities/Issue.ts +1 -0
- package/src/domain/entities/Project.ts +11 -2
- package/src/domain/entities/RequiredProjectField.ts +3 -1
- package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +1 -0
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +1 -0
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +1 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +144 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +16 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +1 -0
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +165 -0
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +44 -0
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +120 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +21 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +1 -0
- package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +34 -0
- package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +1 -0
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +188 -0
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +57 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +44 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +4 -12
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +1 -0
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +1 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +111 -1
- package/src/domain/usecases/StartPreparationUseCase.ts +6 -2
- package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +5 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +1 -0
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +1 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +1 -0
- 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/console/consoleOperationApi.d.ts +2 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +2 -0
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/domain/entities/Issue.d.ts +1 -0
- package/types/domain/entities/Issue.d.ts.map +1 -1
- package/types/domain/entities/Project.d.ts +2 -1
- package/types/domain/entities/Project.d.ts.map +1 -1
- package/types/domain/entities/RequiredProjectField.d.ts +1 -0
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +6 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueNoStatusUpdateUseCase.d.ts +12 -0
- package/types/domain/usecases/IssueNoStatusUpdateUseCase.d.ts.map +1 -0
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -0
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/QualityCheckAdvanceUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ReopenedDoneIssueRevertUseCase.d.ts +12 -0
- package/types/domain/usecases/ReopenedDoneIssueRevertUseCase.d.ts.map +1 -0
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-8Ni5iu-C.css +0 -2
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-8Ni5iu-C.css +0 -2
- package/src/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +0 -86
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
handleReview,
|
|
23
23
|
handleReviewComment,
|
|
24
24
|
handleStoryAdd,
|
|
25
|
+
handleStoryColor,
|
|
25
26
|
handleTriage,
|
|
26
27
|
} from './consoleOperationApi';
|
|
27
28
|
|
|
@@ -115,6 +116,7 @@ describe('consoleOperationApi', () => {
|
|
|
115
116
|
updateStoryList: null,
|
|
116
117
|
resolveProjectRepository: null,
|
|
117
118
|
invalidateProject: null,
|
|
119
|
+
updateProjectCacheEntry: null,
|
|
118
120
|
};
|
|
119
121
|
});
|
|
120
122
|
|
|
@@ -130,6 +132,7 @@ describe('consoleOperationApi', () => {
|
|
|
130
132
|
updateStoryList: null,
|
|
131
133
|
resolveProjectRepository: null,
|
|
132
134
|
invalidateProject: null,
|
|
135
|
+
updateProjectCacheEntry: null,
|
|
133
136
|
});
|
|
134
137
|
|
|
135
138
|
afterEach(() => {
|
|
@@ -875,6 +878,7 @@ describe('consoleOperationApi', () => {
|
|
|
875
878
|
updateStoryList: null,
|
|
876
879
|
resolveProjectRepository: null,
|
|
877
880
|
invalidateProject: null,
|
|
881
|
+
updateProjectCacheEntry: null,
|
|
878
882
|
};
|
|
879
883
|
});
|
|
880
884
|
|
|
@@ -1123,6 +1127,7 @@ describe('consoleOperationApi', () => {
|
|
|
1123
1127
|
updateStoryList: null,
|
|
1124
1128
|
resolveProjectRepository: null,
|
|
1125
1129
|
invalidateProject: null,
|
|
1130
|
+
updateProjectCacheEntry: null,
|
|
1126
1131
|
};
|
|
1127
1132
|
const response = await handleTriage(multiContext, {
|
|
1128
1133
|
pjcode: 'globex',
|
|
@@ -2114,6 +2119,7 @@ describe('consoleOperationApi', () => {
|
|
|
2114
2119
|
updateStoryList: consecutiveUpdateStoryList,
|
|
2115
2120
|
})),
|
|
2116
2121
|
invalidateProject,
|
|
2122
|
+
updateProjectCacheEntry: null,
|
|
2117
2123
|
};
|
|
2118
2124
|
|
|
2119
2125
|
await handleStoryAdd(consecutiveContext, {
|
|
@@ -2137,4 +2143,197 @@ describe('consoleOperationApi', () => {
|
|
|
2137
2143
|
);
|
|
2138
2144
|
});
|
|
2139
2145
|
});
|
|
2146
|
+
|
|
2147
|
+
describe('handleStoryColor', () => {
|
|
2148
|
+
const projectWithStory = (): Project => ({
|
|
2149
|
+
...project,
|
|
2150
|
+
story: {
|
|
2151
|
+
name: 'Story',
|
|
2152
|
+
fieldId: 'storyField',
|
|
2153
|
+
databaseId: 1,
|
|
2154
|
+
stories: [
|
|
2155
|
+
{
|
|
2156
|
+
id: 'opt_blue',
|
|
2157
|
+
name: 'Portal redesign',
|
|
2158
|
+
color: 'BLUE',
|
|
2159
|
+
description: '',
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
id: 'opt_green',
|
|
2163
|
+
name: 'Move to Okinawa',
|
|
2164
|
+
color: 'GREEN',
|
|
2165
|
+
description: '',
|
|
2166
|
+
},
|
|
2167
|
+
],
|
|
2168
|
+
workflowManagementStory: { id: 'wms', name: 'workflow' },
|
|
2169
|
+
},
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
beforeEach(() => {
|
|
2173
|
+
issueRepository.updateStoryOptionColor.mockResolvedValue(undefined);
|
|
2174
|
+
});
|
|
2175
|
+
|
|
2176
|
+
it('calls updateStoryOptionColor with the correct project, storyOptionId, and newColor', async () => {
|
|
2177
|
+
const storyProject = projectWithStory();
|
|
2178
|
+
const response = await handleStoryColor(contextForProject(storyProject), {
|
|
2179
|
+
pjcode: 'acme',
|
|
2180
|
+
storyOptionId: 'opt_blue',
|
|
2181
|
+
newColor: 'RED',
|
|
2182
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
expect(response.statusCode).toBe(200);
|
|
2186
|
+
expect(issueRepository.updateStoryOptionColor).toHaveBeenCalledWith(
|
|
2187
|
+
expect.objectContaining({ story: storyProject.story }),
|
|
2188
|
+
'opt_blue',
|
|
2189
|
+
'RED',
|
|
2190
|
+
);
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
it('resolves the issue repository from a proxy url of the target repo', async () => {
|
|
2194
|
+
const resolvedUrls: string[] = [];
|
|
2195
|
+
const recordingContext: ConsoleOperationContext = {
|
|
2196
|
+
...contextForProject(projectWithStory()),
|
|
2197
|
+
resolveIssueRepository: (url: string) => {
|
|
2198
|
+
resolvedUrls.push(url);
|
|
2199
|
+
return issueRepository;
|
|
2200
|
+
},
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
await handleStoryColor(recordingContext, {
|
|
2204
|
+
pjcode: 'acme',
|
|
2205
|
+
storyOptionId: 'opt_blue',
|
|
2206
|
+
newColor: 'GREEN',
|
|
2207
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2208
|
+
});
|
|
2209
|
+
|
|
2210
|
+
expect(resolvedUrls).toContain(
|
|
2211
|
+
'https://github.com/acme-labs/portal/issues/0',
|
|
2212
|
+
);
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
it('rejects when storyOptionId is missing', async () => {
|
|
2216
|
+
const response = await handleStoryColor(
|
|
2217
|
+
contextForProject(projectWithStory()),
|
|
2218
|
+
{
|
|
2219
|
+
pjcode: 'acme',
|
|
2220
|
+
newColor: 'RED',
|
|
2221
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2222
|
+
},
|
|
2223
|
+
);
|
|
2224
|
+
expect(response).toEqual({
|
|
2225
|
+
statusCode: 400,
|
|
2226
|
+
body: { error: 'storyOptionId is required' },
|
|
2227
|
+
});
|
|
2228
|
+
});
|
|
2229
|
+
|
|
2230
|
+
it('rejects when newColor is not a valid ConsoleColor', async () => {
|
|
2231
|
+
const response = await handleStoryColor(
|
|
2232
|
+
contextForProject(projectWithStory()),
|
|
2233
|
+
{
|
|
2234
|
+
pjcode: 'acme',
|
|
2235
|
+
storyOptionId: 'opt_blue',
|
|
2236
|
+
newColor: 'MAGENTA',
|
|
2237
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2238
|
+
},
|
|
2239
|
+
);
|
|
2240
|
+
expect(response).toEqual({
|
|
2241
|
+
statusCode: 400,
|
|
2242
|
+
body: {
|
|
2243
|
+
error:
|
|
2244
|
+
'newColor must be one of GRAY, BLUE, GREEN, YELLOW, ORANGE, RED, PINK, PURPLE',
|
|
2245
|
+
},
|
|
2246
|
+
});
|
|
2247
|
+
});
|
|
2248
|
+
|
|
2249
|
+
it('rejects when nameWithOwner is missing', async () => {
|
|
2250
|
+
const response = await handleStoryColor(
|
|
2251
|
+
contextForProject(projectWithStory()),
|
|
2252
|
+
{
|
|
2253
|
+
pjcode: 'acme',
|
|
2254
|
+
storyOptionId: 'opt_blue',
|
|
2255
|
+
newColor: 'RED',
|
|
2256
|
+
},
|
|
2257
|
+
);
|
|
2258
|
+
expect(response).toEqual({
|
|
2259
|
+
statusCode: 400,
|
|
2260
|
+
body: { error: 'nameWithOwner is required' },
|
|
2261
|
+
});
|
|
2262
|
+
});
|
|
2263
|
+
|
|
2264
|
+
it('rejects when the project has no story field', async () => {
|
|
2265
|
+
const projectWithoutStory: Project = { ...project, story: null };
|
|
2266
|
+
|
|
2267
|
+
const response = await handleStoryColor(
|
|
2268
|
+
contextForProject(projectWithoutStory),
|
|
2269
|
+
{
|
|
2270
|
+
pjcode: 'acme',
|
|
2271
|
+
storyOptionId: 'opt_blue',
|
|
2272
|
+
newColor: 'RED',
|
|
2273
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2274
|
+
},
|
|
2275
|
+
);
|
|
2276
|
+
expect(response).toEqual({
|
|
2277
|
+
statusCode: 400,
|
|
2278
|
+
body: { error: 'project does not have a story field' },
|
|
2279
|
+
});
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
it('rejects when storyOptionId is not found in project.story.stories', async () => {
|
|
2283
|
+
const response = await handleStoryColor(
|
|
2284
|
+
contextForProject(projectWithStory()),
|
|
2285
|
+
{
|
|
2286
|
+
pjcode: 'acme',
|
|
2287
|
+
storyOptionId: 'opt_nonexistent',
|
|
2288
|
+
newColor: 'RED',
|
|
2289
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2290
|
+
},
|
|
2291
|
+
);
|
|
2292
|
+
expect(response).toEqual({
|
|
2293
|
+
statusCode: 400,
|
|
2294
|
+
body: { error: 'story option "opt_nonexistent" not found in project' },
|
|
2295
|
+
});
|
|
2296
|
+
expect(issueRepository.updateStoryOptionColor).not.toHaveBeenCalled();
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
it('calls updateProjectCacheEntry with the updated project after a successful color change', async () => {
|
|
2300
|
+
const storyProject = projectWithStory();
|
|
2301
|
+
const updatedEntries: { pjcode: string; project: Project }[] = [];
|
|
2302
|
+
const contextWithCache: ConsoleOperationContext = {
|
|
2303
|
+
...contextForProject(storyProject),
|
|
2304
|
+
updateProjectCacheEntry: (pjcode, updatedProject) => {
|
|
2305
|
+
updatedEntries.push({ pjcode, project: updatedProject });
|
|
2306
|
+
},
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
await handleStoryColor(contextWithCache, {
|
|
2310
|
+
pjcode: 'acme',
|
|
2311
|
+
storyOptionId: 'opt_blue',
|
|
2312
|
+
newColor: 'RED',
|
|
2313
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
expect(updatedEntries).toHaveLength(1);
|
|
2317
|
+
expect(updatedEntries[0].pjcode).toBe('acme');
|
|
2318
|
+
expect(updatedEntries[0].project.story?.stories).toEqual(
|
|
2319
|
+
expect.arrayContaining([
|
|
2320
|
+
expect.objectContaining({ id: 'opt_blue', color: 'RED' }),
|
|
2321
|
+
expect.objectContaining({ id: 'opt_green', color: 'GREEN' }),
|
|
2322
|
+
]),
|
|
2323
|
+
);
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
it('does not call updateProjectCacheEntry when updateProjectCacheEntry is null', async () => {
|
|
2327
|
+
const response = await handleStoryColor(
|
|
2328
|
+
contextForProject(projectWithStory()),
|
|
2329
|
+
{
|
|
2330
|
+
pjcode: 'acme',
|
|
2331
|
+
storyOptionId: 'opt_blue',
|
|
2332
|
+
newColor: 'RED',
|
|
2333
|
+
nameWithOwner: 'acme-labs/portal',
|
|
2334
|
+
},
|
|
2335
|
+
);
|
|
2336
|
+
expect(response.statusCode).toBe(200);
|
|
2337
|
+
});
|
|
2338
|
+
});
|
|
2140
2339
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Issue } from '../../../domain/entities/Issue';
|
|
2
2
|
import {
|
|
3
3
|
buildStoryListWithNew,
|
|
4
|
+
FIELD_OPTION_COLORS,
|
|
4
5
|
type FieldOption,
|
|
5
6
|
type Project,
|
|
6
7
|
} from '../../../domain/entities/Project';
|
|
@@ -52,6 +53,8 @@ export type ConsoleOperationContext = {
|
|
|
52
53
|
| null;
|
|
53
54
|
resolveProjectRepository: ConsoleProjectRepositoryResolver | null;
|
|
54
55
|
invalidateProject: ((pjcode: string) => void) | null;
|
|
56
|
+
updateProjectCacheEntry:
|
|
57
|
+
((pjcode: string, updatedProject: Project) => void) | null;
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
export type ConsoleOperationResponse = {
|
|
@@ -138,6 +141,7 @@ const projectItemReference = (
|
|
|
138
141
|
author: '',
|
|
139
142
|
closingIssueReferenceUrls: [],
|
|
140
143
|
agent: null,
|
|
144
|
+
stateReason: null,
|
|
141
145
|
});
|
|
142
146
|
|
|
143
147
|
const recordDone = (
|
|
@@ -698,6 +702,67 @@ export const handleReviewComment = async (
|
|
|
698
702
|
return ok();
|
|
699
703
|
};
|
|
700
704
|
|
|
705
|
+
const isStoryColor = (value: unknown): value is FieldOption['color'] =>
|
|
706
|
+
FIELD_OPTION_COLORS.some((c) => c === value);
|
|
707
|
+
|
|
708
|
+
export const handleStoryColor = async (
|
|
709
|
+
context: ConsoleOperationContext,
|
|
710
|
+
body: Record<string, unknown>,
|
|
711
|
+
): Promise<ConsoleOperationResponse> => {
|
|
712
|
+
const storyOptionId = body.storyOptionId;
|
|
713
|
+
const newColor = body.newColor;
|
|
714
|
+
const nameWithOwner = body.nameWithOwner;
|
|
715
|
+
|
|
716
|
+
if (!isNonEmptyString(storyOptionId)) {
|
|
717
|
+
return badRequest('storyOptionId is required');
|
|
718
|
+
}
|
|
719
|
+
if (!isStoryColor(newColor)) {
|
|
720
|
+
return badRequest(
|
|
721
|
+
'newColor must be one of GRAY, BLUE, GREEN, YELLOW, ORANGE, RED, PINK, PURPLE',
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (!isNonEmptyString(nameWithOwner)) {
|
|
725
|
+
return badRequest('nameWithOwner is required');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const binding = await resolveBinding(context, body);
|
|
729
|
+
if (isOperationResponse(binding)) {
|
|
730
|
+
return binding;
|
|
731
|
+
}
|
|
732
|
+
const { project, pjcode } = binding;
|
|
733
|
+
|
|
734
|
+
if (project.story === null) {
|
|
735
|
+
return badRequest('project does not have a story field');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const storyOption = project.story.stories.find((s) => s.id === storyOptionId);
|
|
739
|
+
if (storyOption === undefined) {
|
|
740
|
+
return badRequest(`story option "${storyOptionId}" not found in project`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const proxyUrl = `https://github.com/${nameWithOwner}/issues/0`;
|
|
744
|
+
await context
|
|
745
|
+
.resolveIssueRepository(proxyUrl)
|
|
746
|
+
.updateStoryOptionColor(
|
|
747
|
+
{ ...project, story: project.story },
|
|
748
|
+
storyOptionId,
|
|
749
|
+
newColor,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
if (context.updateProjectCacheEntry !== null) {
|
|
753
|
+
const updatedStories = project.story.stories.map((s) =>
|
|
754
|
+
s.id === storyOptionId ? { ...s, color: newColor } : s,
|
|
755
|
+
);
|
|
756
|
+
const updatedProject: Project = {
|
|
757
|
+
...project,
|
|
758
|
+
story: { ...project.story, stories: updatedStories },
|
|
759
|
+
};
|
|
760
|
+
context.updateProjectCacheEntry(pjcode, updatedProject);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return ok();
|
|
764
|
+
};
|
|
765
|
+
|
|
701
766
|
export const handleIntmux = async (
|
|
702
767
|
context: ConsoleOperationContext,
|
|
703
768
|
body: Record<string, unknown>,
|
|
@@ -127,6 +127,32 @@ describe('createConsoleProjectResolver', () => {
|
|
|
127
127
|
});
|
|
128
128
|
expect(loadProject).toHaveBeenCalledTimes(2);
|
|
129
129
|
});
|
|
130
|
+
|
|
131
|
+
it('updateEntry replaces the cached project so the next resolve returns the updated project', async () => {
|
|
132
|
+
const updatedProject: Project = { ...acmeProject, id: 'PVT_acme_updated' };
|
|
133
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
134
|
+
const { resolve, updateEntry } = createConsoleProjectResolver(
|
|
135
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
136
|
+
loadProject,
|
|
137
|
+
);
|
|
138
|
+
await resolve('acme');
|
|
139
|
+
updateEntry('acme', updatedProject);
|
|
140
|
+
const result = await resolve('acme');
|
|
141
|
+
expect(result?.project).toBe(updatedProject);
|
|
142
|
+
expect(loadProject).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('updateEntry is a no-op for a pjcode that was never resolved', async () => {
|
|
146
|
+
const updatedProject: Project = { ...acmeProject, id: 'PVT_acme_updated' };
|
|
147
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
148
|
+
const { resolve, updateEntry } = createConsoleProjectResolver(
|
|
149
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
150
|
+
loadProject,
|
|
151
|
+
);
|
|
152
|
+
updateEntry('acme', updatedProject);
|
|
153
|
+
const result = await resolve('acme');
|
|
154
|
+
expect(result?.project).toBe(acmeProject);
|
|
155
|
+
});
|
|
130
156
|
});
|
|
131
157
|
|
|
132
158
|
describe('createConsoleProjectLoader', () => {
|
|
@@ -71,6 +71,7 @@ export const createPjcodeConfigChecker = (
|
|
|
71
71
|
export type ConsoleProjectResolverBundle = {
|
|
72
72
|
resolve: ConsoleProjectResolver;
|
|
73
73
|
invalidate: (pjcode: string) => void;
|
|
74
|
+
updateEntry: (pjcode: string, updatedProject: Project) => void;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
export const createConsoleProjectResolver = (
|
|
@@ -100,5 +101,11 @@ export const createConsoleProjectResolver = (
|
|
|
100
101
|
const invalidate = (pjcode: string): void => {
|
|
101
102
|
cache.delete(pjcode);
|
|
102
103
|
};
|
|
103
|
-
|
|
104
|
+
const updateEntry = (pjcode: string, updatedProject: Project): void => {
|
|
105
|
+
const existing = cache.get(pjcode);
|
|
106
|
+
if (existing !== undefined) {
|
|
107
|
+
cache.set(pjcode, { ...existing, project: updatedProject });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
return { resolve, invalidate, updateEntry };
|
|
104
111
|
};
|
|
@@ -514,6 +514,46 @@ test('creates a new story when the add-story button and form are used', async ({
|
|
|
514
514
|
).toHaveCount(0);
|
|
515
515
|
});
|
|
516
516
|
|
|
517
|
+
test('changes the color of a story row via the color palette in the stories tab', async ({
|
|
518
|
+
page,
|
|
519
|
+
}) => {
|
|
520
|
+
await page.goto(harness.appRootUrl);
|
|
521
|
+
|
|
522
|
+
await tabByLabel(page, 'Stories').click();
|
|
523
|
+
|
|
524
|
+
const tdpmRow = page.locator('.console-story-list-row', {
|
|
525
|
+
hasText: 'TDPM Console port',
|
|
526
|
+
});
|
|
527
|
+
await expect(tdpmRow).toBeVisible();
|
|
528
|
+
|
|
529
|
+
const changeColorButton = tdpmRow.locator('.console-op-button', {
|
|
530
|
+
hasText: 'Change color',
|
|
531
|
+
});
|
|
532
|
+
await expect(changeColorButton).toBeVisible();
|
|
533
|
+
|
|
534
|
+
await changeColorButton.click();
|
|
535
|
+
|
|
536
|
+
const palette = tdpmRow.locator('.console-story-color-palette');
|
|
537
|
+
await expect(palette).toBeVisible();
|
|
538
|
+
|
|
539
|
+
const swatches = palette.locator('.console-story-color-swatch');
|
|
540
|
+
await expect(swatches).toHaveCount(8);
|
|
541
|
+
|
|
542
|
+
const graySwatch = swatches.filter({ hasText: 'disable' });
|
|
543
|
+
await expect(graySwatch).toHaveCount(1);
|
|
544
|
+
|
|
545
|
+
const greenSwatch = palette.locator('[aria-label="GREEN"]');
|
|
546
|
+
await greenSwatch.click();
|
|
547
|
+
|
|
548
|
+
await expect
|
|
549
|
+
.poll(() => harness.storyColorCalls.length, { timeout: 10000 })
|
|
550
|
+
.toBe(1);
|
|
551
|
+
expect(harness.storyColorCalls[0].storyOptionId).toBe('1491051e');
|
|
552
|
+
expect(harness.storyColorCalls[0].newColor).toBe('GREEN');
|
|
553
|
+
|
|
554
|
+
await expect(palette).toHaveCount(0);
|
|
555
|
+
});
|
|
556
|
+
|
|
517
557
|
test('reorders stories on the triage tab with up/down buttons', async ({
|
|
518
558
|
page,
|
|
519
559
|
}) => {
|
|
@@ -47,6 +47,11 @@ export type ConsoleE2eCreateIssueCall = {
|
|
|
47
47
|
title: string;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
export type ConsoleE2eStoryColorCall = {
|
|
51
|
+
storyOptionId: string;
|
|
52
|
+
newColor: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
50
55
|
type ConsoleFixtureListItem = {
|
|
51
56
|
number: number;
|
|
52
57
|
title: string;
|
|
@@ -391,6 +396,7 @@ const buildIssueForUrl = (url: string): Issue => ({
|
|
|
391
396
|
author: 'HiromiShikata',
|
|
392
397
|
closingIssueReferenceUrls: [],
|
|
393
398
|
agent: null,
|
|
399
|
+
stateReason: null,
|
|
394
400
|
});
|
|
395
401
|
|
|
396
402
|
const notImplemented = (method: string): never => {
|
|
@@ -453,6 +459,7 @@ const createStubIssueRepository = (
|
|
|
453
459
|
reviewCommentCalls: ConsoleE2eReviewCommentCall[],
|
|
454
460
|
requestChangesCalls: ConsoleE2eRequestChangesCall[],
|
|
455
461
|
createIssueCalls: ConsoleE2eCreateIssueCall[],
|
|
462
|
+
storyColorCalls: ConsoleE2eStoryColorCall[],
|
|
456
463
|
): IssueRepository => ({
|
|
457
464
|
getAllIssues: () => notImplemented('getAllIssues'),
|
|
458
465
|
getIssueByUrl: async (url: string): Promise<Issue | null> =>
|
|
@@ -615,6 +622,13 @@ const createStubIssueRepository = (
|
|
|
615
622
|
}
|
|
616
623
|
: null,
|
|
617
624
|
setIssueAgentField: async (): Promise<void> => undefined,
|
|
625
|
+
updateStoryOptionColor: async (
|
|
626
|
+
_project,
|
|
627
|
+
storyOptionId: string,
|
|
628
|
+
newColor: string,
|
|
629
|
+
): Promise<void> => {
|
|
630
|
+
storyColorCalls.push({ storyOptionId, newColor });
|
|
631
|
+
},
|
|
618
632
|
});
|
|
619
633
|
|
|
620
634
|
export type ConsoleE2eReorderStoryCall = {
|
|
@@ -635,6 +649,7 @@ export type ConsoleE2eHarness = {
|
|
|
635
649
|
createIssueCalls: ConsoleE2eCreateIssueCall[];
|
|
636
650
|
reorderStoryCalls: ConsoleE2eReorderStoryCall[];
|
|
637
651
|
addStoryCalls: ConsoleE2eAddStoryCall[];
|
|
652
|
+
storyColorCalls: ConsoleE2eStoryColorCall[];
|
|
638
653
|
stop: () => Promise<void>;
|
|
639
654
|
};
|
|
640
655
|
|
|
@@ -658,6 +673,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
658
673
|
const createIssueCalls: ConsoleE2eCreateIssueCall[] = [];
|
|
659
674
|
const reorderStoryCalls: ConsoleE2eReorderStoryCall[] = [];
|
|
660
675
|
const addStoryCalls: ConsoleE2eAddStoryCall[] = [];
|
|
676
|
+
const storyColorCalls: ConsoleE2eStoryColorCall[] = [];
|
|
661
677
|
|
|
662
678
|
const server = await startWebServer({
|
|
663
679
|
accessToken: CONSOLE_E2E_TOKEN,
|
|
@@ -667,6 +683,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
667
683
|
reviewCommentCalls,
|
|
668
684
|
requestChangesCalls,
|
|
669
685
|
createIssueCalls,
|
|
686
|
+
storyColorCalls,
|
|
670
687
|
),
|
|
671
688
|
projectRepository: {
|
|
672
689
|
updateStoryList: async (_updatedProject, stories) => {
|
|
@@ -726,6 +743,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
726
743
|
createIssueCalls,
|
|
727
744
|
reorderStoryCalls,
|
|
728
745
|
addStoryCalls,
|
|
746
|
+
storyColorCalls,
|
|
729
747
|
stop: async (): Promise<void> => {
|
|
730
748
|
await closeServer(server);
|
|
731
749
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
@@ -53,6 +53,10 @@ const meta: Meta<typeof ConsoleStoryList> = {
|
|
|
53
53
|
args: {
|
|
54
54
|
onCreateIssue: () => Promise.resolve(),
|
|
55
55
|
onAddStory: () => Promise.resolve(),
|
|
56
|
+
onSelectColor: () => undefined,
|
|
57
|
+
optimisticColors: {},
|
|
58
|
+
colorChangeInFlight: null,
|
|
59
|
+
colorErrors: {},
|
|
56
60
|
},
|
|
57
61
|
};
|
|
58
62
|
|