devops-plugin-kit 0.1.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/@danieli-automation/create-devops-plugin/package.json +30 -0
- package/@danieli-automation/create-devops-plugin/src/cli.ts +136 -0
- package/@danieli-automation/create-devops-plugin/src/index.ts +37 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/apiIndexMock.ts +10 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/appHtml.ts +25 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/appStyles.ts +22 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/appTsx.ts +41 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/envExample.ts +11 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/gitignore.ts +13 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/index.ts +51 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/packageJson.ts +60 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/readme.ts +30 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/sdkMock.ts +11 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/testSetup.ts +16 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/tsconfigJson.ts +30 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/vitestConfig.ts +57 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/webpackAppConfig.ts +31 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/webpackCommonConfig.ts +116 -0
- package/@danieli-automation/create-devops-plugin/src/template/files/webpackConfig.ts +15 -0
- package/@danieli-automation/create-devops-plugin/src/template/files.ts +1 -0
- package/@danieli-automation/create-devops-plugin/src/template/fs.ts +85 -0
- package/@danieli-automation/create-devops-plugin/src/template/manifest.ts +40 -0
- package/@danieli-automation/create-devops-plugin/src/template/npm.ts +22 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/fonts/AzDevMDL2.woff +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/fonts/bowtie.woff2 +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fabric-icons.woff +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fluent-filled-v1.1.293.woff2 +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fluent-regular-v1.1.293.woff2 +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/images/DigiMetLogo.jpeg +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/images/danieliAutomation.png +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/images/danieliAutomationBlack.jpg +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/images/danieli_digi_met_logo.jpeg +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/static/images/logoSmallpng.png +0 -0
- package/@danieli-automation/create-devops-plugin/src/template/types.ts +14 -0
- package/@danieli-automation/create-devops-plugin/src/template/utils.ts +22 -0
- package/@danieli-automation/create-devops-plugin/tsconfig.json +8 -0
- package/@danieli-automation/devops-plugin-core/package.json +27 -0
- package/@danieli-automation/devops-plugin-core/src/core/azureClients.ts +18 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/createStore.ts +65 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useCrossTeamSprintInstance.ts +145 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useTaskOrder.ts +125 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useWorkItemOrder.ts +86 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/index.ts +13 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/keys.ts +31 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/instance.ts +184 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/taskOrder.ts +59 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/workItemOrder.ts +60 -0
- package/@danieli-automation/devops-plugin-core/src/core/storage/stores.ts +18 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/AdoWorkItemType.ts +18 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/KVStoreType.ts +1 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/ScopeType.ts +1 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/SelectedProjectType.ts +8 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/SortConfigType.ts +2 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/instance/CreateInstanceInputType.ts +10 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/instance/CrossSprintInstanceType.ts +20 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/instance/DefaultInstanceType.ts +12 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/instance/InstanceRowType.ts +18 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/instance/UpdateInstanceInputType.ts +10 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/taskOrder/TaskOrderMapType.ts +3 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/taskOrder/TaskOrderType.ts +1 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/workItemOrder/WorkItemOrderMapType.ts +3 -0
- package/@danieli-automation/devops-plugin-core/src/core/types/workItemOrder/WorkItemOrderType.ts +1 -0
- package/@danieli-automation/devops-plugin-core/src/index.ts +1 -0
- package/@danieli-automation/devops-plugin-core/src/pluginCore.ts +12 -0
- package/@danieli-automation/devops-plugin-core/tsconfig.json +16 -0
- package/@danieli-automation/devops-plugin-features/package.json +31 -0
- package/@danieli-automation/devops-plugin-features/src/app/stores/useUIStore.ts +12 -0
- package/@danieli-automation/devops-plugin-features/src/app/utils/date.ts +9 -0
- package/@danieli-automation/devops-plugin-features/src/app/utils/global.ts +9 -0
- package/@danieli-automation/devops-plugin-features/src/core/azureClients.ts +12 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/constants/InstanceConstant.ts +5 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/hooks/useInstancePermission.ts +127 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/stores/__tests__/useInstanceStore.test.ts +25 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/stores/types/InstanceStoreType.ts +7 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/stores/useInstanceStore.ts +9 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/types/CrossSprintInstanceType.ts +20 -0
- package/@danieli-automation/devops-plugin-features/src/features/instances/utils/instance.ts +55 -0
- package/@danieli-automation/devops-plugin-features/src/features/iterations/api/iterations.ts +298 -0
- package/@danieli-automation/devops-plugin-features/src/features/iterations/services/IterationService.ts +215 -0
- package/@danieli-automation/devops-plugin-features/src/features/iterations/types/IterationInfoType.ts +15 -0
- package/@danieli-automation/devops-plugin-features/src/features/iterations/utils/__tests__/iteration.test.ts +39 -0
- package/@danieli-automation/devops-plugin-features/src/features/iterations/utils/iteration.ts +132 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/api/projects.ts +79 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/api/teams.ts +29 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/api/users.ts +124 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/services/ProjectService.ts +80 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/types/SelectedProjectType.ts +8 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/types/TeamMemberType.ts +7 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/types/TeamRowSeedType.ts +6 -0
- package/@danieli-automation/devops-plugin-features/src/features/teams/types/UserSearchResultType.ts +8 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/api/states.ts +24 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/api/wiql.ts +146 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/api/workItems.ts +193 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/DefaultConsant.ts +43 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/FieldConstant.ts +27 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/StateConstant.ts +16 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/TypeConstant.ts +8 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/services/WIQLService.ts +101 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/services/WorkItemService.ts +54 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/types/AdoWorkItemType.ts +18 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/__tests__/filter.test.ts +87 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/__tests__/workItem.test.ts +116 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/filter.ts +189 -0
- package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/workItem.ts +245 -0
- package/@danieli-automation/devops-plugin-features/src/index.ts +0 -0
- package/@danieli-automation/devops-plugin-features/src/test/mocks/azure-devops-extension-api/Work.ts +5 -0
- package/@danieli-automation/devops-plugin-features/tsconfig.json +18 -0
- package/@danieli-automation/devops-plugin-features/vitest.config.ts +21 -0
- package/README.md +55 -0
- package/devops-plugin-kit-0.1.0.tgz +0 -0
- package/package.json +29 -0
- package/tsconfig.base.json +15 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { WorkItem, WorkItemErrorPolicy, WorkItemExpand } from "azure-devops-extension-api/WorkItemTracking";
|
|
2
|
+
import { witClient } from "core/azureClients";
|
|
3
|
+
import { AdoWorkItemType } from 'features/work-items/types/AdoWorkItemType';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetches work items by their IDs within a project.
|
|
7
|
+
*
|
|
8
|
+
* @param ids - Array of work item IDs to fetch
|
|
9
|
+
* @param projectId - Optional project ID to scope the query
|
|
10
|
+
* @param fields - Optional array of fields to retrieve for each work item
|
|
11
|
+
* @param expand - Optional expansion options for the work items
|
|
12
|
+
* @returns Promise resolving to an array of work items
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchWorkItemsByIds(ids: number[], projectId?: string, fields?: string[], expand: WorkItemExpand = WorkItemExpand.All): Promise<AdoWorkItemType[]> {
|
|
15
|
+
if (!ids || ids.length === 0) return [];
|
|
16
|
+
|
|
17
|
+
const client = witClient();
|
|
18
|
+
const uniqueIds = Array.from(new Set(ids));
|
|
19
|
+
const BATCH_SIZE = 150;
|
|
20
|
+
const result: AdoWorkItemType[] = [];
|
|
21
|
+
|
|
22
|
+
const fieldsArg = expand !== WorkItemExpand.None ? undefined : fields;
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < uniqueIds.length; i += BATCH_SIZE) {
|
|
25
|
+
const slice = uniqueIds.slice(i, i + BATCH_SIZE);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const batch = await client.getWorkItems(
|
|
29
|
+
slice,
|
|
30
|
+
projectId,
|
|
31
|
+
fieldsArg,
|
|
32
|
+
undefined,
|
|
33
|
+
expand,
|
|
34
|
+
WorkItemErrorPolicy.Omit
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(batch)) {
|
|
38
|
+
const cleaned = batch.filter((wi): wi is WorkItem => !!wi && typeof wi.id === "number" && !!wi.fields)
|
|
39
|
+
result.push(...cleaned);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("[fetchWorkItemsByIds] failed batch", { projectId, slice }, err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fetches work item details by given id and project id
|
|
51
|
+
*
|
|
52
|
+
* @param id - the id of the work item
|
|
53
|
+
* @param project - the id of the related project
|
|
54
|
+
* @param fields - the work items to select
|
|
55
|
+
* @returns Promise resolving to the object of work item
|
|
56
|
+
*/
|
|
57
|
+
export async function fetchWorkItemById(id: number, project?: string, fields?: string[], expand: WorkItemExpand = WorkItemExpand.Relations) {
|
|
58
|
+
const wit = witClient();
|
|
59
|
+
const fieldsArg = expand !== WorkItemExpand.None ? undefined : fields;
|
|
60
|
+
const workItem = await wit.getWorkItem(id, project, fieldsArg, undefined, expand);
|
|
61
|
+
return workItem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fetches child work items for a given parent work item within a project.
|
|
66
|
+
*
|
|
67
|
+
* @param projectId - The ID of the project
|
|
68
|
+
* @param parentId - The ID of the parent work item
|
|
69
|
+
* @param fields - Optional array of fields to retrieve for each child work item
|
|
70
|
+
* @param signal - Optional AbortSignal to cancel the request
|
|
71
|
+
* @returns Promise resolving to an array of child work items
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
export async function fetchChildren(projectId: string, parentId: number, fields?: string[], signal?: AbortSignal) {
|
|
75
|
+
|
|
76
|
+
if (signal?.aborted) return [];
|
|
77
|
+
|
|
78
|
+
const wit = witClient();
|
|
79
|
+
const parent = await wit.getWorkItem(parentId, projectId, undefined, undefined, WorkItemExpand.Relations);
|
|
80
|
+
const rels = parent.relations || [];
|
|
81
|
+
const childIdArray: number[] = [];
|
|
82
|
+
|
|
83
|
+
for (const r of rels) {
|
|
84
|
+
if (signal?.aborted) return [];
|
|
85
|
+
if (r.rel === "System.LinkTypes.Hierarchy-Forward") {
|
|
86
|
+
const m = r.url?.match(/(\d+)$/);
|
|
87
|
+
if (m) childIdArray.push(+m[1]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (childIdArray.length === 0) return [];
|
|
92
|
+
|
|
93
|
+
return fetchWorkItemsByIds(childIdArray, projectId, fields);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Updates a work item with the specified changes within a project.
|
|
98
|
+
*
|
|
99
|
+
* @param taskId - The ID of the work item to update
|
|
100
|
+
* @param changes - An array of change operations to apply to the work item
|
|
101
|
+
* @param project - The project ID where the work item resides
|
|
102
|
+
* @returns Promise resolving to the updated work item
|
|
103
|
+
*
|
|
104
|
+
*/
|
|
105
|
+
export async function updateWorkItem(taskId: number, changes: any[], project: string) {
|
|
106
|
+
|
|
107
|
+
const wit = witClient();
|
|
108
|
+
const wi = await wit.getWorkItem(taskId, project, undefined, undefined, WorkItemExpand.Fields);
|
|
109
|
+
const currentRev = wi.rev;
|
|
110
|
+
return wit.updateWorkItem(
|
|
111
|
+
[
|
|
112
|
+
{ op: "test", path: "/rev", value: currentRev },
|
|
113
|
+
...changes
|
|
114
|
+
],
|
|
115
|
+
taskId,
|
|
116
|
+
project,
|
|
117
|
+
false,
|
|
118
|
+
false
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Creates a new work item within a project, optionally linking it to a parent work item.
|
|
124
|
+
*
|
|
125
|
+
* @param project - The project ID where the work item will be created
|
|
126
|
+
* @param parentId - The ID of the parent work item, if any
|
|
127
|
+
* @param fields - A record of fields to set on the new work item
|
|
128
|
+
* @returns Promise resolving to the created work item
|
|
129
|
+
*
|
|
130
|
+
*/
|
|
131
|
+
export async function createWorkItem(project: string, parentId: number, fields: Record<any, any>) {
|
|
132
|
+
const wit = witClient();
|
|
133
|
+
const { title, state, iterationPath, areaPath, workItemType, parent } = fields;
|
|
134
|
+
|
|
135
|
+
const patchDocument = [
|
|
136
|
+
{ op: "add", path: "/fields/System.Title", value: title ?? "New Task" },
|
|
137
|
+
{ op: "add", path: "/fields/System.State", value: state ?? "To Do" },
|
|
138
|
+
{ op: "add", path: "/fields/System.AreaPath", value: areaPath },
|
|
139
|
+
{ op: "add", path: "/fields/System.IterationPath", value: normalizeAndEscapeIterationPath(iterationPath) },
|
|
140
|
+
{ op: "add", path: "/fields/System.Parent", value: parent },
|
|
141
|
+
//{ op: "add", path: "/fields/System.History", value: }
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (parentId) {
|
|
145
|
+
patchDocument.push({
|
|
146
|
+
op: "add",
|
|
147
|
+
path: "/relations/-",
|
|
148
|
+
value: {
|
|
149
|
+
rel: "System.LinkTypes.Hierarchy-Reverse",
|
|
150
|
+
url: `vstfs:///WorkItemTracking/WorkItem/${parentId}`,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = await wit.createWorkItem(patchDocument, project, workItemType);
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Deletes a work item within a project.
|
|
162
|
+
*
|
|
163
|
+
* @param taskId - The ID of the work item to delete
|
|
164
|
+
* @param project - The project ID where the work item resides
|
|
165
|
+
* @returns Promise resolving to the deletion result
|
|
166
|
+
*
|
|
167
|
+
*/
|
|
168
|
+
export async function deleteWorkItem(taskId: number, project: string) {
|
|
169
|
+
const wit = witClient();
|
|
170
|
+
return wit.deleteWorkItem(taskId, project);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#region Private Functions
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Normalize then escape backslashes so result is safe for Azure DevOps JSON payloads.
|
|
177
|
+
* This is idempotent: calling it multiple times yields the same result.
|
|
178
|
+
*
|
|
179
|
+
* Examples at runtime (string contents):
|
|
180
|
+
* "JOB_CLIENTE1\\Sprint 5" -> "JOB_CLIENTE1\\\\Sprint 5"
|
|
181
|
+
* "JOB_CLIENTE1\Sprint 5" -> "JOB_CLIENTE1\\\\Sprint 5"
|
|
182
|
+
*
|
|
183
|
+
* @param path Iteration path value.
|
|
184
|
+
* @returns Normalized and escaped iteration path.
|
|
185
|
+
*/
|
|
186
|
+
function normalizeAndEscapeIterationPath(path: string) {
|
|
187
|
+
if (path == null) return path; // keep null/undefined as-is
|
|
188
|
+
// Collapse any run of backslashes to a single backslash
|
|
189
|
+
const collapsed = path.replace(/\\+/g, '\\');
|
|
190
|
+
// Escape single backslashes for JSON payload (double them)
|
|
191
|
+
return collapsed.replace(/\\/g, '\\\\');
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import StateConstant from "./StateConstant.js";
|
|
2
|
+
import TypeConstant from "./TypeConstant.js";
|
|
3
|
+
|
|
4
|
+
export const WORK_ITEM_TYPES = [TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE, TypeConstant.BUG_TYPE, TypeConstant.TASK_TYPE];
|
|
5
|
+
|
|
6
|
+
//@TODO: States should be fetched from the api
|
|
7
|
+
export const WORK_ITEM_STATES = [
|
|
8
|
+
StateConstant.NEW_STATE,
|
|
9
|
+
StateConstant.TO_DO_STATE,
|
|
10
|
+
StateConstant.IN_PROGRESS_STATE,
|
|
11
|
+
StateConstant.DONE_STATE,
|
|
12
|
+
StateConstant.EXECUTING_STATE,
|
|
13
|
+
StateConstant.TERMINATED_STATE,
|
|
14
|
+
StateConstant.APPROVED_STATE,
|
|
15
|
+
StateConstant.REMOVED_STATE,
|
|
16
|
+
StateConstant.COMMITTED_STATE,
|
|
17
|
+
StateConstant.DEFAULT_STATE
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
//@TODO: Colors should be fetched from the api
|
|
21
|
+
export const WORK_ITEM_STATE_COLORS: { [state: string]: string } = {
|
|
22
|
+
[StateConstant.NEW_STATE]: "rgb(178, 178, 178)",
|
|
23
|
+
[StateConstant.COMMITTED_STATE]: "#007acc",
|
|
24
|
+
[StateConstant.IN_PROGRESS_STATE]: "#007acc",
|
|
25
|
+
[StateConstant.EXECUTING_STATE]: "#A020F0",
|
|
26
|
+
[StateConstant.TERMINATED_STATE]: "#6BB700",
|
|
27
|
+
[StateConstant.APPROVED_STATE]: "rgb(178, 178, 178)",
|
|
28
|
+
[StateConstant.REMOVED_STATE]: "rgba(218, 213, 208, 0.5)",
|
|
29
|
+
[StateConstant.TO_DO_STATE]: "rgb(178, 178, 178)",
|
|
30
|
+
[StateConstant.DONE_STATE]: "#339933",
|
|
31
|
+
[StateConstant.DEFAULT_STATE]: "rgb(178, 178, 178)",
|
|
32
|
+
[StateConstant.BLOCKED_STATE]: "rgb(230, 0, 23);"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//@TODO: Icons should be fetched from the api
|
|
36
|
+
export const WORK_ITEM_TYPE_ICON: { [type: string]: { iconName: string; color: string } } = {
|
|
37
|
+
[TypeConstant.EPIC_TYPE]: { iconName: "CrownSolid", color: "rgb(224,108,0)" },
|
|
38
|
+
[TypeConstant.FEATURE_TYPE]: { iconName: "Trophy", color: "rgb(119,59,147)" },
|
|
39
|
+
[TypeConstant.BUG_TYPE]: { iconName: "LadybugSolid", color: "rgb(204, 41, 6)" },
|
|
40
|
+
[TypeConstant.TASK_TYPE]: { iconName: "TaskSolid", color: "rgb(164, 136, 10)" },
|
|
41
|
+
[TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE]: { iconName: "PageListSolid", color: "rgb(0, 152, 199)" },
|
|
42
|
+
"Default": { iconName: "WorkItem", color: "#605E5C" }
|
|
43
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//Work Item Field Constants
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
WORK_ITEM_FIELD_ID: "System.Id",
|
|
5
|
+
WORK_ITEM_FIELD_ASSIGNED_TO: "System.AssignedTo",
|
|
6
|
+
WORK_ITEM_FIELD_STATE: "System.State",
|
|
7
|
+
WORK_ITEM_FIELD_WORK_ITEM_TYPE: "System.WorkItemType",
|
|
8
|
+
WORK_ITEM_FIELD_PARENT: "System.Parent",
|
|
9
|
+
WORK_ITEM_FIELD_TITLE: "System.Title",
|
|
10
|
+
WORK_ITEM_FIELD_CREATED_DATE: "System.CreatedDate",
|
|
11
|
+
WORK_ITEM_FIELD_CHANGED_DATE: "System.ChangedDate",
|
|
12
|
+
WORK_ITEM_FIELD_AREA_PATH: "System.AreaPath",
|
|
13
|
+
WORK_ITEM_FIELD_ITERATION_LEVEL_1: "System.IterationLevel1",
|
|
14
|
+
WORK_ITEM_FIELD_ITERATION_LEVEL_2: "System.IterationLevel2",
|
|
15
|
+
WORK_ITEM_FIELD_ITERATION_PATH: "System.IterationPath",
|
|
16
|
+
WORK_ITEM_FIELD_ITERATION_ID: "System.IterationId",
|
|
17
|
+
WORK_ITEM_FIELD_TEAM_PROJECT: "System.TeamProject",
|
|
18
|
+
WORK_ITEM_FIELD_TAGS: "System.Tags",
|
|
19
|
+
WORK_ITEM_FIELD_CHANGED_BY: "System.ChangedBy",
|
|
20
|
+
WORK_ITEM_FIELD_EFFORT: "Microsoft.VSTS.Scheduling.Effort",
|
|
21
|
+
WORK_ITEM_FIELD_REMAINING_WORK: "Microsoft.VSTS.Scheduling.RemainingWork",
|
|
22
|
+
WORK_ITEM_FIELD_CLOSED_DATE: "Microsoft.VSTS.Common.ClosedDate",
|
|
23
|
+
WORK_ITEM_FIELD_PRIORITY: "Microsoft.VSTS.Common.Priority",
|
|
24
|
+
WORK_ITEM_FIELD_TIME_SPENT: "Custom.Timespent",
|
|
25
|
+
WORK_ITEM_FIELD_ITERATION_INFO: "Custom.IterationInfo",
|
|
26
|
+
WORK_ITEM_FIELD_ORDER: "order"
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
NEW_STATE: "New",
|
|
3
|
+
COMMITTED_STATE: "Committed",
|
|
4
|
+
IN_PROGRESS_STATE: "In Progress",
|
|
5
|
+
EXECUTING_STATE: "Executing",
|
|
6
|
+
TERMINATED_STATE: "Terminated",
|
|
7
|
+
APPROVED_STATE: "Approved",
|
|
8
|
+
REMOVED_STATE: "Removed",
|
|
9
|
+
TO_DO_STATE: "To Do",
|
|
10
|
+
DONE_STATE: "Done",
|
|
11
|
+
DEFAULT_STATE: "Default",
|
|
12
|
+
COMPLETED_STATE: "Completed",
|
|
13
|
+
CLOSED_STATE: "Closed",
|
|
14
|
+
RESOLVED_STATE: "Resolved",
|
|
15
|
+
BLOCKED_STATE: "Blocked"
|
|
16
|
+
}
|
package/@danieli-automation/devops-plugin-features/src/features/work-items/services/WIQLService.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { witClient } from "core/azureClients";
|
|
2
|
+
import { AreaGroup } from "features/teams/services/ProjectService";
|
|
3
|
+
import { wiqlForMultipleIterations, wiqlForParentWorkItems, wiqlForSingleIteration } from "features/work-items/api/wiql";
|
|
4
|
+
import { fetchWorkItemsByIds } from "features/work-items/api/workItems";
|
|
5
|
+
import { AdoWorkItemType } from "features/work-items/types/AdoWorkItemType";
|
|
6
|
+
|
|
7
|
+
export const WIQLService = {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetches parent work items (PBIs and Bugs) for the given child work item IDs across projects.
|
|
11
|
+
*
|
|
12
|
+
* @param workItemIds
|
|
13
|
+
* @param projectId
|
|
14
|
+
* @returns Promise resolving to an array of parent work items
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
getParentWorkItemsForPbisAndBugs: async (workItemIds: number[], projectId?: string): Promise<AdoWorkItemType[]> => {
|
|
18
|
+
|
|
19
|
+
const wiql = wiqlForParentWorkItems(workItemIds);
|
|
20
|
+
if (!wiql) return [];
|
|
21
|
+
|
|
22
|
+
const wit = witClient();
|
|
23
|
+
const res = await wit.queryByWiql({ query: wiql }, undefined, projectId ?? "");
|
|
24
|
+
|
|
25
|
+
const parentIds = new Set<number>();
|
|
26
|
+
const relations = res.workItemRelations || [];
|
|
27
|
+
|
|
28
|
+
for (const rel of relations) {
|
|
29
|
+
if (rel.source?.id) parentIds.add(rel.source.id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (parentIds.size === 0) return [];
|
|
33
|
+
const parentIdArray = Array.from(parentIds);
|
|
34
|
+
return fetchWorkItemsByIds(parentIdArray, projectId ?? "");
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @description: Fetches Task IDs that belong to the selected iteration, per project area groups.
|
|
39
|
+
* @param areaGroups Project+area scoping.
|
|
40
|
+
* @param iterationPath Selected iteration path.
|
|
41
|
+
* @returns Promise resolving to a unique list of task IDs.
|
|
42
|
+
*/
|
|
43
|
+
getTaskIdsForSelectedIteration: async (areaGroups: AreaGroup[], iterationPaths: string[]): Promise<number[]> => {
|
|
44
|
+
const out = new Set<number>();
|
|
45
|
+
const wit = witClient();
|
|
46
|
+
|
|
47
|
+
const results = await Promise.all(
|
|
48
|
+
areaGroups.map(async (g) => {
|
|
49
|
+
if (!g.areaPaths?.length) return [];
|
|
50
|
+
|
|
51
|
+
const { tasksQuery } = iterationPaths.length > 1
|
|
52
|
+
? wiqlForMultipleIterations(iterationPaths, g.areaPaths)
|
|
53
|
+
: wiqlForSingleIteration(iterationPaths[0], g.areaPaths);
|
|
54
|
+
const res = await wit.queryByWiql({ query: tasksQuery }, undefined, g.projectId ?? "");
|
|
55
|
+
return (res.workItems ?? []).map(w => w.id).filter(Boolean) as number[];
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
for (const ids of results) {
|
|
60
|
+
for (const id of ids) out.add(id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Array.from(out);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @description: Fetches parent work item IDs (PBIs/Bugs) that belong to the selected iteration,
|
|
68
|
+
* scoped by project area groups.
|
|
69
|
+
* @param areaGroups Project+area scoping.
|
|
70
|
+
* @param iterationPath Selected iteration path.
|
|
71
|
+
* @returns Promise resolving to a unique list of parent work item IDs.
|
|
72
|
+
*/
|
|
73
|
+
getParentIdsForSelectedIteration: async (areaGroups: AreaGroup[], iterationPaths: string[]): Promise<number[]> => {
|
|
74
|
+
const out = new Set<number>();
|
|
75
|
+
const wit = witClient();
|
|
76
|
+
|
|
77
|
+
const results = await Promise.all(
|
|
78
|
+
areaGroups.map(async (g) => {
|
|
79
|
+
if (!g.areaPaths?.length) return [];
|
|
80
|
+
|
|
81
|
+
const { parentsQuery } = iterationPaths.length > 1
|
|
82
|
+
? wiqlForMultipleIterations(iterationPaths, g.areaPaths)
|
|
83
|
+
: wiqlForSingleIteration(iterationPaths[0], g.areaPaths);
|
|
84
|
+
const res = await wit.queryByWiql({ query: parentsQuery }, undefined, g.projectId ?? "");
|
|
85
|
+
return (res.workItems ?? []).map(w => w.id).filter(Boolean) as number[];
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
for (const ids of results) {
|
|
90
|
+
for (const id of ids) out.add(id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Array.from(out);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { WorkItemExpand } from "azure-devops-extension-api/WorkItemTracking";
|
|
2
|
+
import { fetchWorkItemById } from "features/work-items/api/workItems";
|
|
3
|
+
import FieldConstant from "features/work-items/constants/FieldConstant";
|
|
4
|
+
|
|
5
|
+
export const WorkItemService = {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fetches a single work item including its relations (parent/child links, etc.).
|
|
9
|
+
* Used for traversing work item hierarchy via "System.LinkTypes.Hierarchy-Forward".
|
|
10
|
+
*
|
|
11
|
+
* @param id - Work item ID
|
|
12
|
+
* @param projectId - Project name or ID used by the client
|
|
13
|
+
* @returns Promise resolving to the work item including `relations`
|
|
14
|
+
*/
|
|
15
|
+
collectDescendantWorkItemIds: async (rootId: number, project: string): Promise<number[]> => {
|
|
16
|
+
const visited = new Set<number>();
|
|
17
|
+
const result: number[] = [];
|
|
18
|
+
|
|
19
|
+
const stack: number[] = [rootId];
|
|
20
|
+
|
|
21
|
+
while (stack.length > 0) {
|
|
22
|
+
const id = stack.pop()!;
|
|
23
|
+
if (visited.has(id)) continue;
|
|
24
|
+
visited.add(id);
|
|
25
|
+
|
|
26
|
+
const wi = await fetchWorkItemById(id, project, [FieldConstant.WORK_ITEM_FIELD_ID]);
|
|
27
|
+
const relations = wi?.relations ?? [];
|
|
28
|
+
|
|
29
|
+
for (const r of relations) {
|
|
30
|
+
if (r.rel !== "System.LinkTypes.Hierarchy-Forward") continue;
|
|
31
|
+
const match = r.url?.match(/(\d+)$/);
|
|
32
|
+
if (!match) continue;
|
|
33
|
+
const childId = Number(match[1]);
|
|
34
|
+
if (!Number.isFinite(childId) || visited.has(childId)) continue;
|
|
35
|
+
|
|
36
|
+
result.push(childId);
|
|
37
|
+
stack.push(childId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetches a single work item including its relations (parent/child links, etc.).
|
|
45
|
+
*
|
|
46
|
+
* @param id - Work item ID
|
|
47
|
+
* @param projectId - Project name or ID used by the client
|
|
48
|
+
* @returns Promise resolving to the work item including `relations`
|
|
49
|
+
*/
|
|
50
|
+
getWorkItemById: async (workItemId: number, project?: string, fields?: string[], expand?: WorkItemExpand) => {
|
|
51
|
+
const workItem = await fetchWorkItemById(workItemId, project, fields, expand);
|
|
52
|
+
return workItem;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/@danieli-automation/devops-plugin-features/src/features/work-items/types/AdoWorkItemType.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { WorkItemRelation } from "azure-devops-extension-api/WorkItemTracking";
|
|
2
|
+
|
|
3
|
+
export type AdoWorkItemType = {
|
|
4
|
+
id: any,
|
|
5
|
+
tempId?: string
|
|
6
|
+
fields: { [key: string]: any; }
|
|
7
|
+
_links?: any;
|
|
8
|
+
url?: string;
|
|
9
|
+
order?: number;
|
|
10
|
+
displayOrder?: string;
|
|
11
|
+
rev?: number;
|
|
12
|
+
multiLineFields?: { [k: string]: string[] };
|
|
13
|
+
relations?: WorkItemRelation[];
|
|
14
|
+
isNewTask?: boolean;
|
|
15
|
+
parentWorkItem?: AdoWorkItemType;
|
|
16
|
+
isFaded?: boolean | undefined;
|
|
17
|
+
_children?: AdoWorkItemType[];
|
|
18
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import FieldConstant from "features/work-items/constants/FieldConstant";
|
|
2
|
+
import StateConstant from "features/work-items/constants/StateConstant";
|
|
3
|
+
import TypeConstant from "features/work-items/constants/TypeConstant";
|
|
4
|
+
import { AdoWorkItemType } from "features/work-items/types/AdoWorkItemType";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { filterEpics, filterFeatures, filterPbisAndBugs, filterTasks } from '../filter.js';
|
|
7
|
+
|
|
8
|
+
describe("Utils: Filter Helper Tests", () => {
|
|
9
|
+
|
|
10
|
+
const epicTypeWorkItem: AdoWorkItemType = {
|
|
11
|
+
id: 1,
|
|
12
|
+
fields: {
|
|
13
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "1",
|
|
14
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 1",
|
|
15
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.EPIC_TYPE
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const featureTypeWorkItem: AdoWorkItemType = {
|
|
20
|
+
id: 2,
|
|
21
|
+
fields: {
|
|
22
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "2",
|
|
23
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 2",
|
|
24
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.FEATURE_TYPE
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const pbiTypeWorkItem: AdoWorkItemType = {
|
|
29
|
+
id: 3,
|
|
30
|
+
fields: {
|
|
31
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "3",
|
|
32
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 3",
|
|
33
|
+
[FieldConstant.WORK_ITEM_FIELD_STATE]: StateConstant.EXECUTING_STATE,
|
|
34
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const bugTypeWorkItem: AdoWorkItemType = {
|
|
39
|
+
id: 4,
|
|
40
|
+
fields: {
|
|
41
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "4",
|
|
42
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 4",
|
|
43
|
+
[FieldConstant.WORK_ITEM_FIELD_STATE]: StateConstant.EXECUTING_STATE,
|
|
44
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.BUG_TYPE
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const taskTypeWorkItem: AdoWorkItemType = {
|
|
49
|
+
id: 5,
|
|
50
|
+
fields: {
|
|
51
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "5",
|
|
52
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 5",
|
|
53
|
+
[FieldConstant.WORK_ITEM_FIELD_STATE]: StateConstant.IN_PROGRESS_STATE,
|
|
54
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.TASK_TYPE
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const workItems: AdoWorkItemType[] = [epicTypeWorkItem, featureTypeWorkItem, pbiTypeWorkItem, bugTypeWorkItem, taskTypeWorkItem];
|
|
59
|
+
|
|
60
|
+
//#region filterEpics
|
|
61
|
+
it("filterEpics: returns filtered epic type work items from given array of data", () => {
|
|
62
|
+
expect(filterEpics(workItems)).toEqual([{ ...epicTypeWorkItem }]);
|
|
63
|
+
});
|
|
64
|
+
//#endregion
|
|
65
|
+
|
|
66
|
+
//#region filterFeatures
|
|
67
|
+
it("filterFeatures: returns filtered feature type work items from given array of data", () => {
|
|
68
|
+
expect(filterFeatures(workItems)).toEqual([{ ...featureTypeWorkItem }]);
|
|
69
|
+
});
|
|
70
|
+
//#endregion
|
|
71
|
+
|
|
72
|
+
//#region filterPbisAndBugs
|
|
73
|
+
it("filterPbisAndBugs: returns filtered pbis and bug type work items from given array of data", () => {
|
|
74
|
+
const result = filterPbisAndBugs(workItems);
|
|
75
|
+
|
|
76
|
+
expect(result).toHaveLength(2);
|
|
77
|
+
expect(result).toEqual(expect.arrayContaining([bugTypeWorkItem, pbiTypeWorkItem]));
|
|
78
|
+
});
|
|
79
|
+
//#endregions
|
|
80
|
+
|
|
81
|
+
//#region filterTasks
|
|
82
|
+
it("filterTasks: returns filtered task type work items from given array of data", () => {
|
|
83
|
+
expect(filterTasks(workItems)).toEqual([{ ...taskTypeWorkItem }]);
|
|
84
|
+
});
|
|
85
|
+
//#endregions
|
|
86
|
+
|
|
87
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import FieldConstant from "features/work-items/constants/FieldConstant";
|
|
2
|
+
import TypeConstant from "features/work-items/constants/TypeConstant";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { AdoWorkItemType } from "../../types/AdoWorkItemType.js";
|
|
5
|
+
import { calculateTotalRemainingWorkByWorkItems, getEpicByWorkItem, getFeatureByWorkItem } from "../workItem.js";
|
|
6
|
+
|
|
7
|
+
describe("Utils: WorkItem Helper Tests", () => {
|
|
8
|
+
|
|
9
|
+
const epic7420: AdoWorkItemType = {
|
|
10
|
+
id: 7420,
|
|
11
|
+
fields: {
|
|
12
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "7420",
|
|
13
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 7420",
|
|
14
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.EPIC_TYPE,
|
|
15
|
+
},
|
|
16
|
+
relations: [
|
|
17
|
+
{
|
|
18
|
+
rel: "System.LinkTypes.Hierarchy-Forward",
|
|
19
|
+
url: "https://dev.azure.com/org/project/_apis/wit/workItems/7428", // child = feature 7428
|
|
20
|
+
attributes: {}
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const epic7421: AdoWorkItemType = {
|
|
26
|
+
id: 7421,
|
|
27
|
+
fields: {
|
|
28
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "7421",
|
|
29
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 7421",
|
|
30
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.EPIC_TYPE,
|
|
31
|
+
},
|
|
32
|
+
relations: [],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const feature7428: AdoWorkItemType = {
|
|
36
|
+
id: 7428,
|
|
37
|
+
fields: {
|
|
38
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "7428",
|
|
39
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 7428",
|
|
40
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.FEATURE_TYPE,
|
|
41
|
+
},
|
|
42
|
+
relations: [
|
|
43
|
+
{
|
|
44
|
+
rel: "System.LinkTypes.Hierarchy-Forward",
|
|
45
|
+
url: "https://dev.azure.com/org/project/_apis/wit/workItems/2", // child = PBI id 2
|
|
46
|
+
attributes: {}
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const pbi2: AdoWorkItemType = {
|
|
52
|
+
id: 2,
|
|
53
|
+
fields: {
|
|
54
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "2",
|
|
55
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 2",
|
|
56
|
+
[FieldConstant.WORK_ITEM_FIELD_REMAINING_WORK]: 3,
|
|
57
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const pbi3: AdoWorkItemType = {
|
|
62
|
+
id: 3,
|
|
63
|
+
fields: {
|
|
64
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "3",
|
|
65
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 3",
|
|
66
|
+
[FieldConstant.WORK_ITEM_FIELD_REMAINING_WORK]: 3,
|
|
67
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const pbi4: AdoWorkItemType = {
|
|
72
|
+
id: 4,
|
|
73
|
+
fields: {
|
|
74
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "4",
|
|
75
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 4",
|
|
76
|
+
[FieldConstant.WORK_ITEM_FIELD_REMAINING_WORK]: 3,
|
|
77
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const pbi5: AdoWorkItemType = {
|
|
82
|
+
id: 5,
|
|
83
|
+
fields: {
|
|
84
|
+
[FieldConstant.WORK_ITEM_FIELD_ID]: "5",
|
|
85
|
+
[FieldConstant.WORK_ITEM_FIELD_TITLE]: "This is test work item 5",
|
|
86
|
+
[FieldConstant.WORK_ITEM_FIELD_REMAINING_WORK]: 3,
|
|
87
|
+
[FieldConstant.WORK_ITEM_FIELD_WORK_ITEM_TYPE]: TypeConstant.PRODUCT_BACKLOG_ITEM_TYPE,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const workItems: AdoWorkItemType[] = [epic7420, epic7421, feature7428, pbi2, pbi3, pbi4, pbi5];
|
|
92
|
+
|
|
93
|
+
//#region getEpicById
|
|
94
|
+
it("getEpicById: returns epic type work item by the id of child work item among the provided array of work items", () => {
|
|
95
|
+
const result = getEpicByWorkItem(pbi2, workItems);
|
|
96
|
+
|
|
97
|
+
expect(result).toEqual(epic7420);
|
|
98
|
+
});
|
|
99
|
+
//#endregion
|
|
100
|
+
|
|
101
|
+
//#region getFeatureById
|
|
102
|
+
it("getFeatureById: returns feature type work item by the id of child work item among the provided array of work items", () => {
|
|
103
|
+
const result = getFeatureByWorkItem(pbi2, workItems);
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual(feature7428);
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
|
|
109
|
+
//#region calculateTotalRemainingWorkByWorkItems
|
|
110
|
+
it("calculateTotalRemainingWorkByWorkItems: returns total remaining work hours of the work items given", () => {
|
|
111
|
+
const result = calculateTotalRemainingWorkByWorkItems(workItems);
|
|
112
|
+
|
|
113
|
+
expect(result).toEqual(12);
|
|
114
|
+
});
|
|
115
|
+
//#endregion
|
|
116
|
+
});
|