github-issue-tower-defence-management 1.162.4 → 1.163.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/domain/entities/RequiredProjectField.js +1 -0
- package/bin/domain/entities/RequiredProjectField.js.map +1 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +31 -0
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -1
- package/bin/domain/usecases/SetNoStoryIssueToStoryUseCase.js +1 -1
- package/bin/domain/usecases/SetNoStoryIssueToStoryUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/entities/RequiredProjectField.ts +1 -0
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +464 -28
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +56 -3
- package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +17 -13
- package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +1 -1
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +3 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.163.0](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.5...v1.163.0) (2026-08-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **project:** add regular / inquiry story option below WORKFLOW BLOCKER on every TDPM project ([#1622](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1622)) ([41275d3](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/41275d34f6ff673c3f559439ca77c705fb91965c)), closes [#1621](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1621)
|
|
7
|
+
|
|
8
|
+
## [1.162.5](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.4...v1.162.5) (2026-08-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **use-case:** run SetNoStoryIssueToStoryUseCase regardless of cacheUsed ([#1618](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1618)) ([3cee86d](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/3cee86d7dde60afb9f0bab841672a127ffe249ce)), closes [#1614](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1614)
|
|
14
|
+
|
|
1
15
|
## [1.162.4](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.3...v1.162.4) (2026-08-19)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -5,6 +5,7 @@ const storyOption = (name, color) => ({ name, color, description: '' });
|
|
|
5
5
|
const REQUIRED_STORY_OPTIONS = [
|
|
6
6
|
storyOption('regular / NO STORY', 'RED'),
|
|
7
7
|
storyOption('regular / WORKFLOW BLOCKER', 'RED'),
|
|
8
|
+
storyOption('regular / inquiry', 'RED'),
|
|
8
9
|
storyOption('regular / high priority', 'RED'),
|
|
9
10
|
storyOption('regular / workflow management', 'YELLOW'),
|
|
10
11
|
storyOption('regular / routine management', 'YELLOW'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequiredProjectField.js","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":";;;AAUA,MAAM,WAAW,GAAG,CAClB,IAAY,EACZ,KAA2B,EACF,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AAEjE,MAAM,sBAAsB,GAA8B;IACxD,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAC;IACxC,WAAW,CAAC,4BAA4B,EAAE,KAAK,CAAC;IAChD,WAAW,CAAC,yBAAyB,EAAE,KAAK,CAAC;IAC7C,WAAW,CAAC,+BAA+B,EAAE,QAAQ,CAAC;IACtD,WAAW,CAAC,8BAA8B,EAAE,QAAQ,CAAC;IACrD,WAAW,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAC1C,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF,MAAM,iCAAiC,GAA8B,KAAK,CAAC,IAAI,CAC7E,EAAE,MAAM,EAAE,EAAE,EAAE,EACd,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACb,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE;IACpB,KAAK,EAAE,MAAe;IACtB,WAAW,EAAE,EAAE;CAChB,CAAC,CACH,CAAC;AAEW,QAAA,gBAAgB,GAAG,OAAO,CAAC;AAC3B,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,6BAA6B,GACxC,uCAAuC,CAAC;AAE7B,QAAA,uBAAuB,GAAqC;IACvE;QACE,IAAI,EAAE,wBAAgB;QACtB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,sBAAsB;KAChC;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,iCAAiC;KAC3C;IACD;QACE,IAAI,EAAE,qCAA6B;QACnC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"RequiredProjectField.js","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":";;;AAUA,MAAM,WAAW,GAAG,CAClB,IAAY,EACZ,KAA2B,EACF,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AAEjE,MAAM,sBAAsB,GAA8B;IACxD,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAC;IACxC,WAAW,CAAC,4BAA4B,EAAE,KAAK,CAAC;IAChD,WAAW,CAAC,mBAAmB,EAAE,KAAK,CAAC;IACvC,WAAW,CAAC,yBAAyB,EAAE,KAAK,CAAC;IAC7C,WAAW,CAAC,+BAA+B,EAAE,QAAQ,CAAC;IACtD,WAAW,CAAC,8BAA8B,EAAE,QAAQ,CAAC;IACrD,WAAW,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAC1C,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF,MAAM,iCAAiC,GAA8B,KAAK,CAAC,IAAI,CAC7E,EAAE,MAAM,EAAE,EAAE,EAAE,EACd,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACb,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE;IACpB,KAAK,EAAE,MAAe;IACtB,WAAW,EAAE,EAAE;CAChB,CAAC,CACH,CAAC;AAEW,QAAA,gBAAgB,GAAG,OAAO,CAAC;AAC3B,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,6BAA6B,GACxC,uCAAuC,CAAC;AAE7B,QAAA,uBAAuB,GAAqC;IACvE;QACE,IAAI,EAAE,wBAAgB;QACtB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,sBAAsB;KAChC;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,iCAAiC;KAC3C;IACD;QACE,IAAI,EAAE,qCAA6B;QACnC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC"}
|
|
@@ -9,6 +9,7 @@ class ProjectRequiredFieldCreateUseCase {
|
|
|
9
9
|
this.run = async (params) => {
|
|
10
10
|
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
11
11
|
await this.createMissingFields(project);
|
|
12
|
+
await this.reconcileStoryOptions(project);
|
|
12
13
|
};
|
|
13
14
|
this.createMissingFields = async (project) => {
|
|
14
15
|
const existingFieldNames = (await this.projectRepository.listFieldNames(project)).map(ProjectFieldName_1.normalizeProjectFieldName);
|
|
@@ -19,6 +20,36 @@ class ProjectRequiredFieldCreateUseCase {
|
|
|
19
20
|
await this.projectRepository.createField(project, required);
|
|
20
21
|
}
|
|
21
22
|
};
|
|
23
|
+
this.reconcileStoryOptions = async (project) => {
|
|
24
|
+
if (!project.story) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const storyFieldDefinition = RequiredProjectField_1.REQUIRED_PROJECT_FIELDS.find((f) => (0, ProjectFieldName_1.normalizeProjectFieldName)(f.name) ===
|
|
28
|
+
(0, ProjectFieldName_1.normalizeProjectFieldName)(RequiredProjectField_1.STORY_FIELD_NAME));
|
|
29
|
+
if (!storyFieldDefinition) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const requiredOptions = storyFieldDefinition.options;
|
|
33
|
+
const mergedOptions = project.story.stories.map((o) => ({ ...o }));
|
|
34
|
+
let addedCount = 0;
|
|
35
|
+
let previousRequiredIndex = -1;
|
|
36
|
+
for (const required of requiredOptions) {
|
|
37
|
+
const existingIndex = mergedOptions.findIndex((o) => this.optionNameSatisfies(o.name, required.name));
|
|
38
|
+
if (existingIndex >= 0) {
|
|
39
|
+
previousRequiredIndex = existingIndex;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const insertIndex = previousRequiredIndex + 1;
|
|
43
|
+
mergedOptions.splice(insertIndex, 0, { ...required, id: null });
|
|
44
|
+
previousRequiredIndex = insertIndex;
|
|
45
|
+
addedCount += 1;
|
|
46
|
+
}
|
|
47
|
+
if (addedCount === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
await this.projectRepository.updateStoryList(project, mergedOptions);
|
|
51
|
+
};
|
|
52
|
+
this.optionNameSatisfies = (currentName, requiredName) => (0, ProjectFieldName_1.normalizeProjectFieldName)(currentName).startsWith((0, ProjectFieldName_1.normalizeProjectFieldName)(requiredName));
|
|
22
53
|
}
|
|
23
54
|
}
|
|
24
55
|
exports.ProjectRequiredFieldCreateUseCase = ProjectRequiredFieldCreateUseCase;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectRequiredFieldCreateUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":";;;AACA,mEAAyE;AACzE,
|
|
1
|
+
{"version":3,"file":"ProjectRequiredFieldCreateUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ class SetNoStoryIssueToStoryUseCase {
|
|
|
6
6
|
this.issueRepository = issueRepository;
|
|
7
7
|
this.run = async (input) => {
|
|
8
8
|
const story = input.project.story;
|
|
9
|
-
if (!story
|
|
9
|
+
if (!story) {
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const earliestTargetDate = input.targetDates[0] ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SetNoStoryIssueToStoryUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,6BAA6B;IACxC,YAAqB,eAAqD;QAArD,oBAAe,GAAf,eAAe,CAAsC;QAE1E,QAAG,GAAG,KAAK,EAAE,KAKZ,EAAiB,EAAE;YAClB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"SetNoStoryIssueToStoryUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,6BAA6B;IACxC,YAAqB,eAAqD;QAArD,oBAAe,GAAf,eAAe,CAAsC;QAE1E,QAAG,GAAG,KAAK,EAAE,KAKZ,EAAiB,EAAE;YAClB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;YACT,CAAC;YACD,MAAM,kBAAkB,GAAgB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACrE,MAAM,aAAa,GAAG,CAAC,KAAY,EAAW,EAAE;gBAC9C,OAAO,CACL,KAAK,CAAC,KAAK,KAAK,IAAI;oBACpB,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CACzC;oBACD,CAAC,KAAK,CAAC,cAAc,KAAK,IAAI;wBAC5B,CAAC,kBAAkB,KAAK,IAAI;4BAC1B,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC,CAAC;oBACpE,KAAK,CAAC,cAAc,KAAK,IAAI;oBAC7B,KAAK,CAAC,KAAK,KAAK,MAAM,CACvB,CAAC;YACJ,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,CAAC,eAAe,CAAC,WAAW,CACpC,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,EAC3B,KAAK,EACL,UAAU,CAAC,EAAE,CACd,CAAC;gBACF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC;IAzC2E,CAAC;CA0C/E;AA3CD,sEA2CC"}
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ const storyOption = (
|
|
|
16
16
|
const REQUIRED_STORY_OPTIONS: Omit<FieldOption, 'id'>[] = [
|
|
17
17
|
storyOption('regular / NO STORY', 'RED'),
|
|
18
18
|
storyOption('regular / WORKFLOW BLOCKER', 'RED'),
|
|
19
|
+
storyOption('regular / inquiry', 'RED'),
|
|
19
20
|
storyOption('regular / high priority', 'RED'),
|
|
20
21
|
storyOption('regular / workflow management', 'YELLOW'),
|
|
21
22
|
storyOption('regular / routine management', 'YELLOW'),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mock } from 'jest-mock-extended';
|
|
2
|
-
import { Project } from '../entities/Project';
|
|
2
|
+
import { FieldOption, Project } from '../entities/Project';
|
|
3
3
|
import {
|
|
4
4
|
DEPENDED_ISSUE_URL_FIELD_NAME,
|
|
5
5
|
NEXT_ACTION_DATE_FIELD_NAME,
|
|
@@ -11,7 +11,7 @@ import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
|
11
11
|
|
|
12
12
|
describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
13
13
|
const projectUrl = 'https://github.com/orgs/acme-labs/projects/18';
|
|
14
|
-
const
|
|
14
|
+
const projectWithoutStory: Project = {
|
|
15
15
|
id: 'PVT_kwDOAcmeLabs4AbCdEf',
|
|
16
16
|
url: projectUrl,
|
|
17
17
|
databaseId: 18,
|
|
@@ -29,14 +29,34 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
29
29
|
completionDate50PercentConfidence: null,
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const buildProjectWithStory = (stories: FieldOption[]): Project => ({
|
|
33
|
+
...projectWithoutStory,
|
|
34
|
+
story: {
|
|
35
|
+
name: STORY_FIELD_NAME,
|
|
36
|
+
fieldId: 'PVTSSF_story',
|
|
37
|
+
databaseId: 42,
|
|
38
|
+
stories,
|
|
39
|
+
workflowManagementStory: {
|
|
40
|
+
id:
|
|
41
|
+
stories.find((s) => s.name === 'regular / workflow management')?.id ??
|
|
42
|
+
'opt_wfm',
|
|
43
|
+
name: 'regular / workflow management',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const createUseCase = (existingFieldNames: string[], project: Project) => {
|
|
33
49
|
const projectRepository =
|
|
34
50
|
mock<
|
|
35
|
-
Pick<
|
|
51
|
+
Pick<
|
|
52
|
+
ProjectRepository,
|
|
53
|
+
'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'
|
|
54
|
+
>
|
|
36
55
|
>();
|
|
37
56
|
projectRepository.getByUrl.mockResolvedValue(project);
|
|
38
57
|
projectRepository.listFieldNames.mockResolvedValue(existingFieldNames);
|
|
39
58
|
projectRepository.createField.mockResolvedValue(undefined);
|
|
59
|
+
projectRepository.updateStoryList.mockResolvedValue([]);
|
|
40
60
|
return {
|
|
41
61
|
projectRepository,
|
|
42
62
|
useCase: new ProjectRequiredFieldCreateUseCase(projectRepository),
|
|
@@ -44,11 +64,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
44
64
|
};
|
|
45
65
|
|
|
46
66
|
it('should create every required field when the project has none of them', async () => {
|
|
47
|
-
const { projectRepository, useCase } = createUseCase(
|
|
48
|
-
'Title',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]);
|
|
67
|
+
const { projectRepository, useCase } = createUseCase(
|
|
68
|
+
['Title', 'Assignees', 'Status'],
|
|
69
|
+
projectWithoutStory,
|
|
70
|
+
);
|
|
52
71
|
|
|
53
72
|
await useCase.run({ projectUrl });
|
|
54
73
|
|
|
@@ -64,14 +83,17 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
64
83
|
});
|
|
65
84
|
|
|
66
85
|
it('should create nothing when the project already has every required field', async () => {
|
|
67
|
-
const { projectRepository, useCase } = createUseCase(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
const { projectRepository, useCase } = createUseCase(
|
|
87
|
+
[
|
|
88
|
+
'Title',
|
|
89
|
+
'Status',
|
|
90
|
+
'story',
|
|
91
|
+
'nextactiondate',
|
|
92
|
+
'nextactionhour',
|
|
93
|
+
'Depended Issue URL separated by comma',
|
|
94
|
+
],
|
|
95
|
+
projectWithoutStory,
|
|
96
|
+
);
|
|
75
97
|
|
|
76
98
|
await useCase.run({ projectUrl });
|
|
77
99
|
|
|
@@ -79,12 +101,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
79
101
|
});
|
|
80
102
|
|
|
81
103
|
it('should create only the missing fields', async () => {
|
|
82
|
-
const { projectRepository, useCase } = createUseCase(
|
|
83
|
-
'Title',
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
'Next Action Date',
|
|
87
|
-
]);
|
|
104
|
+
const { projectRepository, useCase } = createUseCase(
|
|
105
|
+
['Title', 'Status', 'Story', 'Next Action Date'],
|
|
106
|
+
projectWithoutStory,
|
|
107
|
+
);
|
|
88
108
|
|
|
89
109
|
await useCase.run({ projectUrl });
|
|
90
110
|
|
|
@@ -97,8 +117,11 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
97
117
|
]);
|
|
98
118
|
});
|
|
99
119
|
|
|
100
|
-
it('should create the story field with the standard story options', async () => {
|
|
101
|
-
const { projectRepository, useCase } = createUseCase(
|
|
120
|
+
it('should create the story field with the standard story options including regular / inquiry', async () => {
|
|
121
|
+
const { projectRepository, useCase } = createUseCase(
|
|
122
|
+
['Title', 'Status'],
|
|
123
|
+
projectWithoutStory,
|
|
124
|
+
);
|
|
102
125
|
|
|
103
126
|
await useCase.run({ projectUrl });
|
|
104
127
|
|
|
@@ -109,6 +132,7 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
109
132
|
expect(storyCall?.[1].options.map((option) => option.name)).toEqual([
|
|
110
133
|
'regular / NO STORY',
|
|
111
134
|
'regular / WORKFLOW BLOCKER',
|
|
135
|
+
'regular / inquiry',
|
|
112
136
|
'regular / high priority',
|
|
113
137
|
'regular / workflow management',
|
|
114
138
|
'regular / routine management',
|
|
@@ -120,7 +144,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
120
144
|
});
|
|
121
145
|
|
|
122
146
|
it('should create the next action hour field with the hour options from 1 to 23', async () => {
|
|
123
|
-
const { projectRepository, useCase } = createUseCase(
|
|
147
|
+
const { projectRepository, useCase } = createUseCase(
|
|
148
|
+
['Title', 'Status'],
|
|
149
|
+
projectWithoutStory,
|
|
150
|
+
);
|
|
124
151
|
|
|
125
152
|
await useCase.run({ projectUrl });
|
|
126
153
|
|
|
@@ -134,7 +161,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
134
161
|
});
|
|
135
162
|
|
|
136
163
|
it('should create the depended issue url field as a text field', async () => {
|
|
137
|
-
const { projectRepository, useCase } = createUseCase(
|
|
164
|
+
const { projectRepository, useCase } = createUseCase(
|
|
165
|
+
['Title', 'Status'],
|
|
166
|
+
projectWithoutStory,
|
|
167
|
+
);
|
|
138
168
|
|
|
139
169
|
await useCase.run({ projectUrl });
|
|
140
170
|
|
|
@@ -146,7 +176,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
146
176
|
});
|
|
147
177
|
|
|
148
178
|
it('should create the next action date field as a date field', async () => {
|
|
149
|
-
const { projectRepository, useCase } = createUseCase(
|
|
179
|
+
const { projectRepository, useCase } = createUseCase(
|
|
180
|
+
['Title', 'Status'],
|
|
181
|
+
projectWithoutStory,
|
|
182
|
+
);
|
|
150
183
|
|
|
151
184
|
await useCase.run({ projectUrl });
|
|
152
185
|
|
|
@@ -156,4 +189,407 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
|
|
|
156
189
|
expect(dateCall?.[1].dataType).toBe('DATE');
|
|
157
190
|
expect(dateCall?.[1].options).toEqual([]);
|
|
158
191
|
});
|
|
192
|
+
|
|
193
|
+
describe('reconcileStoryOptions', () => {
|
|
194
|
+
const existingStoriesWithoutInquiry: FieldOption[] = [
|
|
195
|
+
{ id: 'opt1', name: 'regular / NO STORY', color: 'RED', description: '' },
|
|
196
|
+
{
|
|
197
|
+
id: 'opt2',
|
|
198
|
+
name: 'regular / WORKFLOW BLOCKER',
|
|
199
|
+
color: 'RED',
|
|
200
|
+
description: '',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'opt3',
|
|
204
|
+
name: 'regular / high priority',
|
|
205
|
+
color: 'RED',
|
|
206
|
+
description: '',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'opt4',
|
|
210
|
+
name: 'regular / workflow management',
|
|
211
|
+
color: 'YELLOW',
|
|
212
|
+
description: '',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'opt5',
|
|
216
|
+
name: 'regular / routine management',
|
|
217
|
+
color: 'YELLOW',
|
|
218
|
+
description: '',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'opt6',
|
|
222
|
+
name: 'regular / middle bug',
|
|
223
|
+
color: 'GRAY',
|
|
224
|
+
description: '',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'opt7',
|
|
228
|
+
name: 'regular / minor bug',
|
|
229
|
+
color: 'GRAY',
|
|
230
|
+
description: '',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'opt8',
|
|
234
|
+
name: 'regular / refactor',
|
|
235
|
+
color: 'GRAY',
|
|
236
|
+
description: '',
|
|
237
|
+
},
|
|
238
|
+
{ id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const existingStoriesComplete: FieldOption[] = [
|
|
242
|
+
{ id: 'opt1', name: 'regular / NO STORY', color: 'RED', description: '' },
|
|
243
|
+
{
|
|
244
|
+
id: 'opt2',
|
|
245
|
+
name: 'regular / WORKFLOW BLOCKER',
|
|
246
|
+
color: 'RED',
|
|
247
|
+
description: '',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'opt_inq',
|
|
251
|
+
name: 'regular / inquiry',
|
|
252
|
+
color: 'RED',
|
|
253
|
+
description: '',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'opt3',
|
|
257
|
+
name: 'regular / high priority',
|
|
258
|
+
color: 'RED',
|
|
259
|
+
description: '',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: 'opt4',
|
|
263
|
+
name: 'regular / workflow management',
|
|
264
|
+
color: 'YELLOW',
|
|
265
|
+
description: '',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'opt5',
|
|
269
|
+
name: 'regular / routine management',
|
|
270
|
+
color: 'YELLOW',
|
|
271
|
+
description: '',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'opt6',
|
|
275
|
+
name: 'regular / middle bug',
|
|
276
|
+
color: 'GRAY',
|
|
277
|
+
description: '',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: 'opt7',
|
|
281
|
+
name: 'regular / minor bug',
|
|
282
|
+
color: 'GRAY',
|
|
283
|
+
description: '',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'opt8',
|
|
287
|
+
name: 'regular / refactor',
|
|
288
|
+
color: 'GRAY',
|
|
289
|
+
description: '',
|
|
290
|
+
},
|
|
291
|
+
{ id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
it('should not call updateStoryList when story field does not exist on the project', async () => {
|
|
295
|
+
const { projectRepository, useCase } = createUseCase(
|
|
296
|
+
[
|
|
297
|
+
'Title',
|
|
298
|
+
'Status',
|
|
299
|
+
'Story',
|
|
300
|
+
'Next Action Date',
|
|
301
|
+
'Next Action Hour',
|
|
302
|
+
'Depended Issue URL separated by comma',
|
|
303
|
+
],
|
|
304
|
+
projectWithoutStory,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
await useCase.run({ projectUrl });
|
|
308
|
+
|
|
309
|
+
expect(projectRepository.updateStoryList).not.toHaveBeenCalled();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should not call updateStoryList when all required story options are already present', async () => {
|
|
313
|
+
const project = buildProjectWithStory(existingStoriesComplete);
|
|
314
|
+
const { projectRepository, useCase } = createUseCase(
|
|
315
|
+
[
|
|
316
|
+
'Title',
|
|
317
|
+
'Status',
|
|
318
|
+
'Story',
|
|
319
|
+
'Next Action Date',
|
|
320
|
+
'Next Action Hour',
|
|
321
|
+
'Depended Issue URL separated by comma',
|
|
322
|
+
],
|
|
323
|
+
project,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
await useCase.run({ projectUrl });
|
|
327
|
+
|
|
328
|
+
expect(projectRepository.updateStoryList).not.toHaveBeenCalled();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should call updateStoryList when regular / inquiry is missing from the existing story field', async () => {
|
|
332
|
+
const project = buildProjectWithStory(existingStoriesWithoutInquiry);
|
|
333
|
+
const { projectRepository, useCase } = createUseCase(
|
|
334
|
+
[
|
|
335
|
+
'Title',
|
|
336
|
+
'Status',
|
|
337
|
+
'Story',
|
|
338
|
+
'Next Action Date',
|
|
339
|
+
'Next Action Hour',
|
|
340
|
+
'Depended Issue URL separated by comma',
|
|
341
|
+
],
|
|
342
|
+
project,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
await useCase.run({ projectUrl });
|
|
346
|
+
|
|
347
|
+
expect(projectRepository.updateStoryList).toHaveBeenCalledTimes(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should place regular / inquiry immediately after regular / WORKFLOW BLOCKER in the merged option list', async () => {
|
|
351
|
+
const project = buildProjectWithStory(existingStoriesWithoutInquiry);
|
|
352
|
+
const { projectRepository, useCase } = createUseCase(
|
|
353
|
+
[
|
|
354
|
+
'Title',
|
|
355
|
+
'Status',
|
|
356
|
+
'Story',
|
|
357
|
+
'Next Action Date',
|
|
358
|
+
'Next Action Hour',
|
|
359
|
+
'Depended Issue URL separated by comma',
|
|
360
|
+
],
|
|
361
|
+
project,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
await useCase.run({ projectUrl });
|
|
365
|
+
|
|
366
|
+
const submittedOptions =
|
|
367
|
+
projectRepository.updateStoryList.mock.calls[0][1];
|
|
368
|
+
const names = submittedOptions.map((o) => o.name);
|
|
369
|
+
const workflowBlockerIndex = names.indexOf('regular / WORKFLOW BLOCKER');
|
|
370
|
+
const inquiryIndex = names.indexOf('regular / inquiry');
|
|
371
|
+
expect(inquiryIndex).toBe(workflowBlockerIndex + 1);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should submit the new option without an id', async () => {
|
|
375
|
+
const project = buildProjectWithStory(existingStoriesWithoutInquiry);
|
|
376
|
+
const { projectRepository, useCase } = createUseCase(
|
|
377
|
+
[
|
|
378
|
+
'Title',
|
|
379
|
+
'Status',
|
|
380
|
+
'Story',
|
|
381
|
+
'Next Action Date',
|
|
382
|
+
'Next Action Hour',
|
|
383
|
+
'Depended Issue URL separated by comma',
|
|
384
|
+
],
|
|
385
|
+
project,
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
await useCase.run({ projectUrl });
|
|
389
|
+
|
|
390
|
+
const submittedOptions =
|
|
391
|
+
projectRepository.updateStoryList.mock.calls[0][1];
|
|
392
|
+
const inquiryOption = submittedOptions.find(
|
|
393
|
+
(o) => o.name === 'regular / inquiry',
|
|
394
|
+
);
|
|
395
|
+
expect(inquiryOption?.id).toBeNull();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should preserve the existing option ids in the merged list', async () => {
|
|
399
|
+
const project = buildProjectWithStory(existingStoriesWithoutInquiry);
|
|
400
|
+
const { projectRepository, useCase } = createUseCase(
|
|
401
|
+
[
|
|
402
|
+
'Title',
|
|
403
|
+
'Status',
|
|
404
|
+
'Story',
|
|
405
|
+
'Next Action Date',
|
|
406
|
+
'Next Action Hour',
|
|
407
|
+
'Depended Issue URL separated by comma',
|
|
408
|
+
],
|
|
409
|
+
project,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
await useCase.run({ projectUrl });
|
|
413
|
+
|
|
414
|
+
const submittedOptions =
|
|
415
|
+
projectRepository.updateStoryList.mock.calls[0][1];
|
|
416
|
+
for (const original of existingStoriesWithoutInquiry) {
|
|
417
|
+
const submitted = submittedOptions.find(
|
|
418
|
+
(o) => o.name === original.name,
|
|
419
|
+
);
|
|
420
|
+
expect(submitted?.id).toBe(original.id);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should include extra options that are not in the required list and preserve their ids', async () => {
|
|
425
|
+
const extraOption: FieldOption = {
|
|
426
|
+
id: 'opt_extra',
|
|
427
|
+
name: 'regular / special project',
|
|
428
|
+
color: 'BLUE',
|
|
429
|
+
description: '',
|
|
430
|
+
};
|
|
431
|
+
const storiesWithExtra = [...existingStoriesWithoutInquiry, extraOption];
|
|
432
|
+
const project = buildProjectWithStory(storiesWithExtra);
|
|
433
|
+
const { projectRepository, useCase } = createUseCase(
|
|
434
|
+
[
|
|
435
|
+
'Title',
|
|
436
|
+
'Status',
|
|
437
|
+
'Story',
|
|
438
|
+
'Next Action Date',
|
|
439
|
+
'Next Action Hour',
|
|
440
|
+
'Depended Issue URL separated by comma',
|
|
441
|
+
],
|
|
442
|
+
project,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
await useCase.run({ projectUrl });
|
|
446
|
+
|
|
447
|
+
const submittedOptions =
|
|
448
|
+
projectRepository.updateStoryList.mock.calls[0][1];
|
|
449
|
+
const submittedExtra = submittedOptions.find(
|
|
450
|
+
(o) => o.name === 'regular / special project',
|
|
451
|
+
);
|
|
452
|
+
expect(submittedExtra?.id).toBe('opt_extra');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should submit regular / inquiry with color RED', async () => {
|
|
456
|
+
const project = buildProjectWithStory(existingStoriesWithoutInquiry);
|
|
457
|
+
const { projectRepository, useCase } = createUseCase(
|
|
458
|
+
[
|
|
459
|
+
'Title',
|
|
460
|
+
'Status',
|
|
461
|
+
'Story',
|
|
462
|
+
'Next Action Date',
|
|
463
|
+
'Next Action Hour',
|
|
464
|
+
'Depended Issue URL separated by comma',
|
|
465
|
+
],
|
|
466
|
+
project,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
await useCase.run({ projectUrl });
|
|
470
|
+
|
|
471
|
+
const submittedOptions =
|
|
472
|
+
projectRepository.updateStoryList.mock.calls[0][1];
|
|
473
|
+
const inquiryOption = submittedOptions.find(
|
|
474
|
+
(o) => o.name === 'regular / inquiry',
|
|
475
|
+
);
|
|
476
|
+
expect(inquiryOption?.color).toBe('RED');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const storiesWithDescriptiveNoStoryAndInterleavedExtras: FieldOption[] = [
|
|
480
|
+
{
|
|
481
|
+
id: 'opt1',
|
|
482
|
+
name: "regular / NO STORY; DON'T WORK ON THIS STORY, NEED TO SET STORY FIELD",
|
|
483
|
+
color: 'RED',
|
|
484
|
+
description: '',
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: 'opt_extra_a',
|
|
488
|
+
name: 'acme / rebuild the billing screen',
|
|
489
|
+
color: 'BLUE',
|
|
490
|
+
description: '',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
id: 'opt2',
|
|
494
|
+
name: 'regular / WORKFLOW BLOCKER',
|
|
495
|
+
color: 'RED',
|
|
496
|
+
description: '',
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
id: 'opt_extra_b',
|
|
500
|
+
name: 'acme / migrate the search index',
|
|
501
|
+
color: 'GREEN',
|
|
502
|
+
description: '',
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: 'opt4',
|
|
506
|
+
name: 'regular / workflow management',
|
|
507
|
+
color: 'YELLOW',
|
|
508
|
+
description: '',
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
id: 'opt5',
|
|
512
|
+
name: 'regular / routine management',
|
|
513
|
+
color: 'YELLOW',
|
|
514
|
+
description: '',
|
|
515
|
+
},
|
|
516
|
+
{ id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
const runWithStories = async (stories: FieldOption[]) => {
|
|
520
|
+
const project = buildProjectWithStory(stories);
|
|
521
|
+
const { projectRepository, useCase } = createUseCase(
|
|
522
|
+
[
|
|
523
|
+
'Title',
|
|
524
|
+
'Status',
|
|
525
|
+
'Story',
|
|
526
|
+
'Next Action Date',
|
|
527
|
+
'Next Action Hour',
|
|
528
|
+
'Depended Issue URL separated by comma',
|
|
529
|
+
],
|
|
530
|
+
project,
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
await useCase.run({ projectUrl });
|
|
534
|
+
|
|
535
|
+
return projectRepository.updateStoryList.mock.calls[0]?.[1];
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
it('should treat an existing option whose name extends a required name as that required option', async () => {
|
|
539
|
+
const submittedOptions = await runWithStories(
|
|
540
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras,
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
const noStoryNames = (submittedOptions ?? [])
|
|
544
|
+
.map((o) => o.name)
|
|
545
|
+
.filter((name) => name.toLowerCase().includes('no story'));
|
|
546
|
+
expect(noStoryNames).toEqual([
|
|
547
|
+
"regular / NO STORY; DON'T WORK ON THIS STORY, NEED TO SET STORY FIELD",
|
|
548
|
+
]);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('should add only the options that are genuinely absent', async () => {
|
|
552
|
+
const submittedOptions = await runWithStories(
|
|
553
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const addedNames = (submittedOptions ?? [])
|
|
557
|
+
.filter((o) => o.id === null)
|
|
558
|
+
.map((o) => o.name);
|
|
559
|
+
expect(addedNames).toEqual([
|
|
560
|
+
'regular / inquiry',
|
|
561
|
+
'regular / high priority',
|
|
562
|
+
'regular / middle bug',
|
|
563
|
+
'regular / minor bug',
|
|
564
|
+
'regular / refactor',
|
|
565
|
+
]);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should keep the relative order of every option that already exists', async () => {
|
|
569
|
+
const submittedOptions = await runWithStories(
|
|
570
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras,
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
const submittedNames = (submittedOptions ?? []).map((o) => o.name);
|
|
574
|
+
const existingNamesInSubmittedOrder = submittedNames.filter((name) =>
|
|
575
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras.some(
|
|
576
|
+
(existing) => existing.name === name,
|
|
577
|
+
),
|
|
578
|
+
);
|
|
579
|
+
expect(existingNamesInSubmittedOrder).toEqual(
|
|
580
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras.map((o) => o.name),
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should insert regular / inquiry directly below regular / WORKFLOW BLOCKER when extras are interleaved', async () => {
|
|
585
|
+
const submittedOptions = await runWithStories(
|
|
586
|
+
storiesWithDescriptiveNoStoryAndInterleavedExtras,
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
const submittedNames = (submittedOptions ?? []).map((o) => o.name);
|
|
590
|
+
expect(submittedNames.indexOf('regular / inquiry')).toBe(
|
|
591
|
+
submittedNames.indexOf('regular / WORKFLOW BLOCKER') + 1,
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
159
595
|
});
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import { Project } from '../entities/Project';
|
|
1
|
+
import { FieldOption, Project } from '../entities/Project';
|
|
2
2
|
import { normalizeProjectFieldName } from '../entities/ProjectFieldName';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
REQUIRED_PROJECT_FIELDS,
|
|
5
|
+
STORY_FIELD_NAME,
|
|
6
|
+
} from '../entities/RequiredProjectField';
|
|
4
7
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
5
8
|
|
|
9
|
+
type StoryOptionToSubmit = Omit<FieldOption, 'id'> & {
|
|
10
|
+
id: FieldOption['id'] | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
6
13
|
export class ProjectRequiredFieldCreateUseCase {
|
|
7
14
|
constructor(
|
|
8
15
|
private readonly projectRepository: Pick<
|
|
9
16
|
ProjectRepository,
|
|
10
|
-
'getByUrl' | 'listFieldNames' | 'createField'
|
|
17
|
+
'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'
|
|
11
18
|
>,
|
|
12
19
|
) {}
|
|
13
20
|
|
|
14
21
|
run = async (params: { projectUrl: string }): Promise<void> => {
|
|
15
22
|
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
16
23
|
await this.createMissingFields(project);
|
|
24
|
+
await this.reconcileStoryOptions(project);
|
|
17
25
|
};
|
|
18
26
|
|
|
19
27
|
private createMissingFields = async (project: Project): Promise<void> => {
|
|
@@ -29,4 +37,49 @@ export class ProjectRequiredFieldCreateUseCase {
|
|
|
29
37
|
await this.projectRepository.createField(project, required);
|
|
30
38
|
}
|
|
31
39
|
};
|
|
40
|
+
|
|
41
|
+
private reconcileStoryOptions = async (project: Project): Promise<void> => {
|
|
42
|
+
if (!project.story) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const storyFieldDefinition = REQUIRED_PROJECT_FIELDS.find(
|
|
46
|
+
(f) =>
|
|
47
|
+
normalizeProjectFieldName(f.name) ===
|
|
48
|
+
normalizeProjectFieldName(STORY_FIELD_NAME),
|
|
49
|
+
);
|
|
50
|
+
if (!storyFieldDefinition) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const requiredOptions = storyFieldDefinition.options;
|
|
54
|
+
const mergedOptions: StoryOptionToSubmit[] = project.story.stories.map(
|
|
55
|
+
(o) => ({ ...o }),
|
|
56
|
+
);
|
|
57
|
+
let addedCount = 0;
|
|
58
|
+
let previousRequiredIndex = -1;
|
|
59
|
+
for (const required of requiredOptions) {
|
|
60
|
+
const existingIndex = mergedOptions.findIndex((o) =>
|
|
61
|
+
this.optionNameSatisfies(o.name, required.name),
|
|
62
|
+
);
|
|
63
|
+
if (existingIndex >= 0) {
|
|
64
|
+
previousRequiredIndex = existingIndex;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const insertIndex = previousRequiredIndex + 1;
|
|
68
|
+
mergedOptions.splice(insertIndex, 0, { ...required, id: null });
|
|
69
|
+
previousRequiredIndex = insertIndex;
|
|
70
|
+
addedCount += 1;
|
|
71
|
+
}
|
|
72
|
+
if (addedCount === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await this.projectRepository.updateStoryList(project, mergedOptions);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
private optionNameSatisfies = (
|
|
79
|
+
currentName: string,
|
|
80
|
+
requiredName: string,
|
|
81
|
+
): boolean =>
|
|
82
|
+
normalizeProjectFieldName(currentName).startsWith(
|
|
83
|
+
normalizeProjectFieldName(requiredName),
|
|
84
|
+
);
|
|
32
85
|
}
|
|
@@ -94,24 +94,28 @@ describe('SetNoStoryIssueToStoryUseCase', () => {
|
|
|
94
94
|
expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it('should
|
|
98
|
-
|
|
97
|
+
it('should assign story to eligible issue even when cacheUsed is true', async () => {
|
|
98
|
+
const issue: Issue = {
|
|
99
|
+
...mock<Issue>(),
|
|
100
|
+
labels: [],
|
|
101
|
+
story: null,
|
|
102
|
+
state: 'OPEN',
|
|
103
|
+
nextActionDate: null,
|
|
104
|
+
nextActionHour: null,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const promise = useCase.run({
|
|
99
108
|
targetDates: [targetDate],
|
|
100
109
|
project: basicProject,
|
|
101
|
-
issues: [
|
|
102
|
-
{
|
|
103
|
-
...mock<Issue>(),
|
|
104
|
-
labels: [],
|
|
105
|
-
story: null,
|
|
106
|
-
state: 'OPEN',
|
|
107
|
-
nextActionDate: null,
|
|
108
|
-
nextActionHour: null,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
110
|
+
issues: [issue],
|
|
111
111
|
cacheUsed: true,
|
|
112
112
|
});
|
|
113
|
+
await jest.runAllTimersAsync();
|
|
114
|
+
await promise;
|
|
113
115
|
|
|
114
|
-
expect(mockIssueRepository.updateStory).
|
|
116
|
+
expect(mockIssueRepository.updateStory.mock.calls).toEqual([
|
|
117
|
+
[{ ...basicProject, story: basicProject.story }, issue, 'noStoryId'],
|
|
118
|
+
]);
|
|
115
119
|
});
|
|
116
120
|
|
|
117
121
|
it('should assign first story to eligible issue on non-minute-0 target date', async () => {
|
|
@@ -12,7 +12,7 @@ export class SetNoStoryIssueToStoryUseCase {
|
|
|
12
12
|
cacheUsed: boolean;
|
|
13
13
|
}): Promise<void> => {
|
|
14
14
|
const story = input.project.story;
|
|
15
|
-
if (!story
|
|
15
|
+
if (!story) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
const earliestTargetDate: Date | null = input.targetDates[0] ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequiredProjectField.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;AAE7E,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"RequiredProjectField.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;AAE7E,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;CACpC,CAAC;AA6BF,eAAO,MAAM,gBAAgB,UAAU,CAAC;AACxC,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,6BAA6B,0CACD,CAAC;AAE1C,eAAO,MAAM,uBAAuB,EAAE,8BAA8B,EAqBnE,CAAC"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
2
2
|
export declare class ProjectRequiredFieldCreateUseCase {
|
|
3
3
|
private readonly projectRepository;
|
|
4
|
-
constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'listFieldNames' | 'createField'>);
|
|
4
|
+
constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'>);
|
|
5
5
|
run: (params: {
|
|
6
6
|
projectUrl: string;
|
|
7
7
|
}) => Promise<void>;
|
|
8
8
|
private createMissingFields;
|
|
9
|
+
private reconcileStoryOptions;
|
|
10
|
+
private optionNameSatisfies;
|
|
9
11
|
}
|
|
10
12
|
//# sourceMappingURL=ProjectRequiredFieldCreateUseCase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectRequiredFieldCreateUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectRequiredFieldCreateUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAM3E,qBAAa,iCAAiC;IAE1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAAjB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,gBAAgB,GAAG,aAAa,GAAG,iBAAiB,CAClE;IAGH,GAAG,GAAU,QAAQ;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CAIzD;IAEF,OAAO,CAAC,mBAAmB,CAYzB;IAEF,OAAO,CAAC,qBAAqB,CAmC3B;IAEF,OAAO,CAAC,mBAAmB,CAMvB;CACL"}
|