@elisra-devops/docgen-data-provider 1.75.0 → 1.77.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/bin/models/mewp-reporting.d.ts +120 -0
- package/bin/models/mewp-reporting.js +3 -0
- package/bin/models/mewp-reporting.js.map +1 -0
- package/bin/modules/ResultDataProvider.d.ts +46 -6
- package/bin/modules/ResultDataProvider.js +1156 -119
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +1285 -33
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/bin/utils/mewpExternalIngestionUtils.d.ts +17 -0
- package/bin/utils/mewpExternalIngestionUtils.js +269 -0
- package/bin/utils/mewpExternalIngestionUtils.js.map +1 -0
- package/bin/utils/mewpExternalTableUtils.d.ts +36 -0
- package/bin/utils/mewpExternalTableUtils.js +320 -0
- package/bin/utils/mewpExternalTableUtils.js.map +1 -0
- package/package.json +10 -1
- package/src/models/mewp-reporting.ts +138 -0
- package/src/modules/ResultDataProvider.ts +1399 -166
- package/src/tests/modules/ResultDataProvider.test.ts +1471 -42
- package/src/utils/mewpExternalIngestionUtils.ts +349 -0
- package/src/utils/mewpExternalTableUtils.ts +461 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MewpBugLink,
|
|
3
|
+
MewpExternalFileRef,
|
|
4
|
+
MewpL3L4Link,
|
|
5
|
+
} from '../models/mewp-reporting';
|
|
6
|
+
import logger from './logger';
|
|
7
|
+
import MewpExternalTableUtils from './mewpExternalTableUtils';
|
|
8
|
+
|
|
9
|
+
export interface MewpExternalIngestionAdapters {
|
|
10
|
+
toComparableText: (value: any) => string;
|
|
11
|
+
toRequirementKey: (value: string) => string;
|
|
12
|
+
resolveBugResponsibility: (fields: Record<string, any>) => string;
|
|
13
|
+
isExternalStateInScope: (value: string, itemType: 'bug' | 'requirement') => boolean;
|
|
14
|
+
isExcludedL3L4BySapWbs: (value: string) => boolean;
|
|
15
|
+
resolveRequirementSapWbsByBaseKey?: (baseKey: string) => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default class MewpExternalIngestionUtils {
|
|
19
|
+
private externalTableUtils: MewpExternalTableUtils;
|
|
20
|
+
|
|
21
|
+
constructor(externalTableUtils: MewpExternalTableUtils) {
|
|
22
|
+
this.externalTableUtils = externalTableUtils;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async loadExternalBugsByTestCase(
|
|
26
|
+
externalBugsFile: MewpExternalFileRef | null | undefined,
|
|
27
|
+
adapters: MewpExternalIngestionAdapters
|
|
28
|
+
): Promise<Map<number, MewpBugLink[]>> {
|
|
29
|
+
const rows = await this.externalTableUtils.loadExternalTableRows(externalBugsFile, 'bugs');
|
|
30
|
+
const sourceName = String(
|
|
31
|
+
externalBugsFile?.name || externalBugsFile?.objectName || externalBugsFile?.text || externalBugsFile?.url || ''
|
|
32
|
+
).trim();
|
|
33
|
+
if (rows.length === 0) {
|
|
34
|
+
if (sourceName) {
|
|
35
|
+
logger.warn(`MEWP external bugs ingestion: source '${sourceName}' loaded with 0 data rows.`);
|
|
36
|
+
}
|
|
37
|
+
return new Map<number, MewpBugLink[]>();
|
|
38
|
+
}
|
|
39
|
+
logger.info(`MEWP external bugs ingestion: start source='${sourceName || 'unknown'}' rows=${rows.length}`);
|
|
40
|
+
|
|
41
|
+
let skippedMissingTestCaseId = 0;
|
|
42
|
+
let skippedMissingRequirement = 0;
|
|
43
|
+
let skippedMissingBugId = 0;
|
|
44
|
+
let skippedOutOfScopeState = 0;
|
|
45
|
+
|
|
46
|
+
const map = new Map<number, MewpBugLink[]>();
|
|
47
|
+
let parsedRows = 0;
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
const testCaseId = this.toPositiveNumber(
|
|
50
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
51
|
+
'Elisra_SortIndex',
|
|
52
|
+
'Elisra SortIndex',
|
|
53
|
+
'ElisraSortIndex',
|
|
54
|
+
])
|
|
55
|
+
);
|
|
56
|
+
if (!testCaseId) {
|
|
57
|
+
skippedMissingTestCaseId += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const requirementBaseKey = adapters.toRequirementKey(
|
|
61
|
+
adapters.toComparableText(this.externalTableUtils.readExternalCell(row, ['SR']))
|
|
62
|
+
);
|
|
63
|
+
if (!requirementBaseKey) {
|
|
64
|
+
skippedMissingRequirement += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const bugId =
|
|
69
|
+
this.toPositiveNumber(
|
|
70
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
71
|
+
'TargetWorkItemId',
|
|
72
|
+
'Bug ID',
|
|
73
|
+
'BugId',
|
|
74
|
+
'Links.TargetWorkItem.WorkItemId',
|
|
75
|
+
'Links TargetWorkItem WorkItemId',
|
|
76
|
+
])
|
|
77
|
+
) || 0;
|
|
78
|
+
if (!bugId) {
|
|
79
|
+
skippedMissingBugId += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const bugState = adapters.toComparableText(
|
|
84
|
+
this.externalTableUtils.readExternalCell(row, ['TargetState', 'State'])
|
|
85
|
+
);
|
|
86
|
+
if (!adapters.isExternalStateInScope(bugState, 'bug')) {
|
|
87
|
+
skippedOutOfScopeState += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const bugTitle = adapters.toComparableText(
|
|
92
|
+
this.externalTableUtils.readExternalCell(row, ['Bug Title', 'Title', 'Links.TargetWorkItem.Title'])
|
|
93
|
+
);
|
|
94
|
+
const bugResponsibilityRaw = adapters.toComparableText(
|
|
95
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
96
|
+
'Responsibility',
|
|
97
|
+
'Division',
|
|
98
|
+
'SAPWBS',
|
|
99
|
+
'TargetSapWbs',
|
|
100
|
+
])
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const bug: MewpBugLink = {
|
|
104
|
+
id: bugId,
|
|
105
|
+
title: bugTitle,
|
|
106
|
+
responsibility: adapters.resolveBugResponsibility({
|
|
107
|
+
'Custom.SAPWBS': bugResponsibilityRaw,
|
|
108
|
+
}),
|
|
109
|
+
requirementBaseKey,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (!map.has(testCaseId)) map.set(testCaseId, []);
|
|
113
|
+
map.get(testCaseId)!.push(bug);
|
|
114
|
+
parsedRows += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const deduped = new Map<number, MewpBugLink[]>();
|
|
118
|
+
let dedupedRows = 0;
|
|
119
|
+
for (const [testCaseId, bugs] of map.entries()) {
|
|
120
|
+
const byId = new Map<string, MewpBugLink>();
|
|
121
|
+
for (const bug of bugs || []) {
|
|
122
|
+
const bugId = Number(bug?.id || 0);
|
|
123
|
+
if (!Number.isFinite(bugId) || bugId <= 0) continue;
|
|
124
|
+
const baseKey = String(bug?.requirementBaseKey || '').trim();
|
|
125
|
+
const compositeKey = `${bugId}|${baseKey}`;
|
|
126
|
+
const existing = byId.get(compositeKey);
|
|
127
|
+
if (!existing) {
|
|
128
|
+
byId.set(compositeKey, bug);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
byId.set(compositeKey, {
|
|
133
|
+
id: bugId,
|
|
134
|
+
requirementBaseKey: baseKey,
|
|
135
|
+
title: String(existing?.title || bug?.title || '').trim(),
|
|
136
|
+
responsibility: String(existing?.responsibility || bug?.responsibility || '').trim(),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
deduped.set(
|
|
140
|
+
testCaseId,
|
|
141
|
+
[...byId.values()].sort((a, b) => {
|
|
142
|
+
const idCompare = Number(a.id) - Number(b.id);
|
|
143
|
+
if (idCompare !== 0) return idCompare;
|
|
144
|
+
return String(a.requirementBaseKey || '').localeCompare(String(b.requirementBaseKey || ''));
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
dedupedRows += deduped.get(testCaseId)?.length || 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (parsedRows === 0) {
|
|
151
|
+
logger.warn(
|
|
152
|
+
`External bugs source was loaded but no valid rows were parsed. ` +
|
|
153
|
+
`Expected columns include Elisra_SortIndex, SR and bug ID fields.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
logger.info(
|
|
157
|
+
`MEWP external bugs ingestion: done source='${sourceName || 'unknown'}' ` +
|
|
158
|
+
`rows=${rows.length} parsed=${parsedRows} deduped=${dedupedRows} ` +
|
|
159
|
+
`testCases=${deduped.size} ` +
|
|
160
|
+
`skippedMissingTestCaseId=${skippedMissingTestCaseId} ` +
|
|
161
|
+
`skippedMissingRequirement=${skippedMissingRequirement} ` +
|
|
162
|
+
`skippedMissingBugId=${skippedMissingBugId} ` +
|
|
163
|
+
`skippedOutOfScopeState=${skippedOutOfScopeState}`
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return deduped;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public async loadExternalL3L4ByBaseKey(
|
|
170
|
+
externalL3L4File: MewpExternalFileRef | null | undefined,
|
|
171
|
+
adapters: MewpExternalIngestionAdapters
|
|
172
|
+
): Promise<Map<string, MewpL3L4Link[]>> {
|
|
173
|
+
const rows = await this.externalTableUtils.loadExternalTableRows(externalL3L4File, 'l3l4');
|
|
174
|
+
const sourceName = String(
|
|
175
|
+
externalL3L4File?.name || externalL3L4File?.objectName || externalL3L4File?.text || externalL3L4File?.url || ''
|
|
176
|
+
).trim();
|
|
177
|
+
if (rows.length === 0) {
|
|
178
|
+
if (sourceName) {
|
|
179
|
+
logger.warn(`MEWP external L3/L4 ingestion: source '${sourceName}' loaded with 0 data rows.`);
|
|
180
|
+
}
|
|
181
|
+
return new Map<string, MewpL3L4Link[]>();
|
|
182
|
+
}
|
|
183
|
+
logger.info(`MEWP external L3/L4 ingestion: start source='${sourceName || 'unknown'}' rows=${rows.length}`);
|
|
184
|
+
|
|
185
|
+
const linksByBaseKey = new Map<string, Map<string, MewpL3L4Link>>();
|
|
186
|
+
const addLink = (baseKey: string, level: 'L3' | 'L4', id: number, title: string) => {
|
|
187
|
+
if (!baseKey || !id) return;
|
|
188
|
+
if (!linksByBaseKey.has(baseKey)) {
|
|
189
|
+
linksByBaseKey.set(baseKey, new Map<string, MewpL3L4Link>());
|
|
190
|
+
}
|
|
191
|
+
const idKey = String(id).trim();
|
|
192
|
+
const dedupeKey = `${level}:${idKey}`;
|
|
193
|
+
linksByBaseKey.get(baseKey)!.set(dedupeKey, {
|
|
194
|
+
id: idKey,
|
|
195
|
+
title: String(title || '').trim(),
|
|
196
|
+
level,
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let parsedRows = 0;
|
|
201
|
+
let skippedMissingRequirement = 0;
|
|
202
|
+
let acceptedL3 = 0;
|
|
203
|
+
let acceptedL4 = 0;
|
|
204
|
+
let filteredByState = 0;
|
|
205
|
+
let filteredBySapWbs = 0;
|
|
206
|
+
for (const row of rows) {
|
|
207
|
+
const srRaw = adapters.toComparableText(this.externalTableUtils.readExternalCell(row, ['SR']));
|
|
208
|
+
const baseKey = adapters.toRequirementKey(srRaw);
|
|
209
|
+
if (!baseKey) {
|
|
210
|
+
skippedMissingRequirement += 1;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const requirementSapWbsFallback = adapters.resolveRequirementSapWbsByBaseKey?.(baseKey) || '';
|
|
214
|
+
|
|
215
|
+
const area = adapters
|
|
216
|
+
.toComparableText(this.externalTableUtils.readExternalCell(row, ['AREA 34', 'AREA34']))
|
|
217
|
+
.toLowerCase();
|
|
218
|
+
const targetIdLevel3 = this.toPositiveNumber(
|
|
219
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
220
|
+
'TargetWorkItemId Level 3',
|
|
221
|
+
'TargetWorkItemIdLevel 3',
|
|
222
|
+
'TargetWorkItemIdLevel3',
|
|
223
|
+
])
|
|
224
|
+
);
|
|
225
|
+
const targetTitleLevel3 = adapters.toComparableText(
|
|
226
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
227
|
+
'TargetTitleLevel3',
|
|
228
|
+
'TargetTitleLevel 3',
|
|
229
|
+
'TargetTitle Level 3',
|
|
230
|
+
])
|
|
231
|
+
);
|
|
232
|
+
const targetStateLevel3 = adapters.toComparableText(
|
|
233
|
+
this.externalTableUtils.readExternalCell(row, ['TargetStateLevel 3', 'TargetStateLevel3'])
|
|
234
|
+
);
|
|
235
|
+
const targetSapWbsLevel3 = adapters.toComparableText(
|
|
236
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
237
|
+
'TargetSapWbsLevel 3',
|
|
238
|
+
'TargetSapWbsLevel3',
|
|
239
|
+
'TargetSapWbs Level 3',
|
|
240
|
+
])
|
|
241
|
+
);
|
|
242
|
+
const targetIdLevel4 = this.toPositiveNumber(
|
|
243
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
244
|
+
'TargetWorkItemIdLevel 4',
|
|
245
|
+
'TargetWorkItemId Level 4',
|
|
246
|
+
'TargetWorkItemIdLevel4',
|
|
247
|
+
])
|
|
248
|
+
);
|
|
249
|
+
const targetTitleLevel4 = adapters.toComparableText(
|
|
250
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
251
|
+
'TargetTitleLevel4',
|
|
252
|
+
'TargetTitleLevel 4',
|
|
253
|
+
'TargetTitle Level 4',
|
|
254
|
+
])
|
|
255
|
+
);
|
|
256
|
+
const targetStateLevel4 = adapters.toComparableText(
|
|
257
|
+
this.externalTableUtils.readExternalCell(row, ['TargetStateLevel 4', 'TargetStateLevel4'])
|
|
258
|
+
);
|
|
259
|
+
const targetSapWbsLevel4 = adapters.toComparableText(
|
|
260
|
+
this.externalTableUtils.readExternalCell(row, [
|
|
261
|
+
'TargetSapWbsLevel 4',
|
|
262
|
+
'TargetSapWbsLevel4',
|
|
263
|
+
'TargetSapWbs Level 4',
|
|
264
|
+
])
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (area.includes('level 4')) {
|
|
268
|
+
const effectiveSapWbsLevel3 = targetSapWbsLevel3 || requirementSapWbsFallback;
|
|
269
|
+
if (!targetIdLevel3) {
|
|
270
|
+
parsedRows += 1;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (!adapters.isExternalStateInScope(targetStateLevel3, 'requirement')) {
|
|
274
|
+
filteredByState += 1;
|
|
275
|
+
parsedRows += 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel3)) {
|
|
279
|
+
filteredBySapWbs += 1;
|
|
280
|
+
parsedRows += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
targetIdLevel3 &&
|
|
285
|
+
adapters.isExternalStateInScope(targetStateLevel3, 'requirement') &&
|
|
286
|
+
!adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel3)
|
|
287
|
+
) {
|
|
288
|
+
addLink(baseKey, 'L4', targetIdLevel3, targetTitleLevel3);
|
|
289
|
+
acceptedL4 += 1;
|
|
290
|
+
}
|
|
291
|
+
parsedRows += 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const effectiveSapWbsLevel3 = targetSapWbsLevel3 || requirementSapWbsFallback;
|
|
296
|
+
const allowLevel3State = adapters.isExternalStateInScope(targetStateLevel3, 'requirement');
|
|
297
|
+
const allowLevel3SapWbs = !adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel3);
|
|
298
|
+
if (!allowLevel3State) filteredByState += 1;
|
|
299
|
+
if (!allowLevel3SapWbs) filteredBySapWbs += 1;
|
|
300
|
+
if (targetIdLevel3 && allowLevel3State && allowLevel3SapWbs) {
|
|
301
|
+
addLink(baseKey, 'L3', targetIdLevel3, targetTitleLevel3);
|
|
302
|
+
acceptedL3 += 1;
|
|
303
|
+
}
|
|
304
|
+
const effectiveSapWbsLevel4 = targetSapWbsLevel4 || requirementSapWbsFallback;
|
|
305
|
+
const allowLevel4State = adapters.isExternalStateInScope(targetStateLevel4, 'requirement');
|
|
306
|
+
const allowLevel4SapWbs = !adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel4);
|
|
307
|
+
if (!allowLevel4State) filteredByState += 1;
|
|
308
|
+
if (!allowLevel4SapWbs) filteredBySapWbs += 1;
|
|
309
|
+
if (targetIdLevel4 && allowLevel4State && allowLevel4SapWbs) {
|
|
310
|
+
addLink(baseKey, 'L4', targetIdLevel4, targetTitleLevel4);
|
|
311
|
+
acceptedL4 += 1;
|
|
312
|
+
}
|
|
313
|
+
parsedRows += 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const out = new Map<string, MewpL3L4Link[]>();
|
|
317
|
+
for (const [baseKey, linksById] of linksByBaseKey.entries()) {
|
|
318
|
+
out.set(
|
|
319
|
+
baseKey,
|
|
320
|
+
[...linksById.values()].sort((a, b) => {
|
|
321
|
+
if (a.level !== b.level) return a.level === 'L3' ? -1 : 1;
|
|
322
|
+
return a.id.localeCompare(b.id);
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (parsedRows === 0) {
|
|
328
|
+
logger.warn(
|
|
329
|
+
`External L3/L4 source was loaded but no valid rows were parsed. ` +
|
|
330
|
+
`Expected columns include SR, AREA 34, target IDs/titles/states.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const totalLinks = [...out.values()].reduce((sum, items) => sum + (items?.length || 0), 0);
|
|
334
|
+
logger.info(
|
|
335
|
+
`MEWP external L3/L4 ingestion: done source='${sourceName || 'unknown'}' ` +
|
|
336
|
+
`rows=${rows.length} parsed=${parsedRows} baseKeys=${out.size} links=${totalLinks} ` +
|
|
337
|
+
`acceptedL3=${acceptedL3} acceptedL4=${acceptedL4} ` +
|
|
338
|
+
`skippedMissingRequirement=${skippedMissingRequirement} ` +
|
|
339
|
+
`filteredByState=${filteredByState} filteredBySapWbs=${filteredBySapWbs}`
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private toPositiveNumber(value: any): number {
|
|
346
|
+
const parsed = Number(String(value || '').replace(/[^\d]/g, ''));
|
|
347
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
348
|
+
}
|
|
349
|
+
}
|