@elisra-devops/docgen-data-provider 1.75.0 → 1.76.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.
@@ -0,0 +1,270 @@
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
+ if (rows.length === 0) return new Map<number, MewpBugLink[]>();
31
+
32
+ const map = new Map<number, MewpBugLink[]>();
33
+ let parsedRows = 0;
34
+ for (const row of rows) {
35
+ const testCaseId = this.toPositiveNumber(
36
+ this.externalTableUtils.readExternalCell(row, [
37
+ 'Elisra_SortIndex',
38
+ 'Elisra SortIndex',
39
+ 'ElisraSortIndex',
40
+ ])
41
+ );
42
+ if (!testCaseId) continue;
43
+ const requirementBaseKey = adapters.toRequirementKey(
44
+ adapters.toComparableText(this.externalTableUtils.readExternalCell(row, ['SR']))
45
+ );
46
+ if (!requirementBaseKey) continue;
47
+
48
+ const bugId =
49
+ this.toPositiveNumber(
50
+ this.externalTableUtils.readExternalCell(row, [
51
+ 'TargetWorkItemId',
52
+ 'Bug ID',
53
+ 'BugId',
54
+ 'Links.TargetWorkItem.WorkItemId',
55
+ 'Links TargetWorkItem WorkItemId',
56
+ ])
57
+ ) || 0;
58
+ if (!bugId) continue;
59
+
60
+ const bugState = adapters.toComparableText(
61
+ this.externalTableUtils.readExternalCell(row, ['TargetState', 'State'])
62
+ );
63
+ if (!adapters.isExternalStateInScope(bugState, 'bug')) continue;
64
+
65
+ const bugTitle = adapters.toComparableText(
66
+ this.externalTableUtils.readExternalCell(row, ['Bug Title', 'Title', 'Links.TargetWorkItem.Title'])
67
+ );
68
+ const bugResponsibilityRaw = adapters.toComparableText(
69
+ this.externalTableUtils.readExternalCell(row, [
70
+ 'Responsibility',
71
+ 'Division',
72
+ 'SAPWBS',
73
+ 'TargetSapWbs',
74
+ ])
75
+ );
76
+
77
+ const bug: MewpBugLink = {
78
+ id: bugId,
79
+ title: bugTitle,
80
+ responsibility: adapters.resolveBugResponsibility({
81
+ 'Custom.SAPWBS': bugResponsibilityRaw,
82
+ }),
83
+ requirementBaseKey,
84
+ };
85
+
86
+ if (!map.has(testCaseId)) map.set(testCaseId, []);
87
+ map.get(testCaseId)!.push(bug);
88
+ parsedRows += 1;
89
+ }
90
+
91
+ const deduped = new Map<number, MewpBugLink[]>();
92
+ for (const [testCaseId, bugs] of map.entries()) {
93
+ const byId = new Map<string, MewpBugLink>();
94
+ for (const bug of bugs || []) {
95
+ const bugId = Number(bug?.id || 0);
96
+ if (!Number.isFinite(bugId) || bugId <= 0) continue;
97
+ const baseKey = String(bug?.requirementBaseKey || '').trim();
98
+ const compositeKey = `${bugId}|${baseKey}`;
99
+ const existing = byId.get(compositeKey);
100
+ if (!existing) {
101
+ byId.set(compositeKey, bug);
102
+ continue;
103
+ }
104
+
105
+ byId.set(compositeKey, {
106
+ id: bugId,
107
+ requirementBaseKey: baseKey,
108
+ title: String(existing?.title || bug?.title || '').trim(),
109
+ responsibility: String(existing?.responsibility || bug?.responsibility || '').trim(),
110
+ });
111
+ }
112
+ deduped.set(
113
+ testCaseId,
114
+ [...byId.values()].sort((a, b) => {
115
+ const idCompare = Number(a.id) - Number(b.id);
116
+ if (idCompare !== 0) return idCompare;
117
+ return String(a.requirementBaseKey || '').localeCompare(String(b.requirementBaseKey || ''));
118
+ })
119
+ );
120
+ }
121
+
122
+ if (parsedRows === 0) {
123
+ logger.warn(
124
+ `External bugs source was loaded but no valid rows were parsed. ` +
125
+ `Expected columns include Elisra_SortIndex, SR and bug ID fields.`
126
+ );
127
+ }
128
+
129
+ return deduped;
130
+ }
131
+
132
+ public async loadExternalL3L4ByBaseKey(
133
+ externalL3L4File: MewpExternalFileRef | null | undefined,
134
+ adapters: MewpExternalIngestionAdapters
135
+ ): Promise<Map<string, MewpL3L4Link[]>> {
136
+ const rows = await this.externalTableUtils.loadExternalTableRows(externalL3L4File, 'l3l4');
137
+ if (rows.length === 0) return new Map<string, MewpL3L4Link[]>();
138
+
139
+ const linksByBaseKey = new Map<string, Map<string, MewpL3L4Link>>();
140
+ const addLink = (baseKey: string, level: 'L3' | 'L4', id: number, title: string) => {
141
+ if (!baseKey || !id) return;
142
+ if (!linksByBaseKey.has(baseKey)) {
143
+ linksByBaseKey.set(baseKey, new Map<string, MewpL3L4Link>());
144
+ }
145
+ const idKey = String(id).trim();
146
+ const dedupeKey = `${level}:${idKey}`;
147
+ linksByBaseKey.get(baseKey)!.set(dedupeKey, {
148
+ id: idKey,
149
+ title: String(title || '').trim(),
150
+ level,
151
+ });
152
+ };
153
+
154
+ let parsedRows = 0;
155
+ for (const row of rows) {
156
+ const srRaw = adapters.toComparableText(this.externalTableUtils.readExternalCell(row, ['SR']));
157
+ const baseKey = adapters.toRequirementKey(srRaw);
158
+ if (!baseKey) continue;
159
+ const requirementSapWbsFallback = adapters.resolveRequirementSapWbsByBaseKey?.(baseKey) || '';
160
+
161
+ const area = adapters
162
+ .toComparableText(this.externalTableUtils.readExternalCell(row, ['AREA 34', 'AREA34']))
163
+ .toLowerCase();
164
+ const targetIdLevel3 = this.toPositiveNumber(
165
+ this.externalTableUtils.readExternalCell(row, [
166
+ 'TargetWorkItemId Level 3',
167
+ 'TargetWorkItemIdLevel 3',
168
+ 'TargetWorkItemIdLevel3',
169
+ ])
170
+ );
171
+ const targetTitleLevel3 = adapters.toComparableText(
172
+ this.externalTableUtils.readExternalCell(row, [
173
+ 'TargetTitleLevel3',
174
+ 'TargetTitleLevel 3',
175
+ 'TargetTitle Level 3',
176
+ ])
177
+ );
178
+ const targetStateLevel3 = adapters.toComparableText(
179
+ this.externalTableUtils.readExternalCell(row, ['TargetStateLevel 3', 'TargetStateLevel3'])
180
+ );
181
+ const targetSapWbsLevel3 = adapters.toComparableText(
182
+ this.externalTableUtils.readExternalCell(row, [
183
+ 'TargetSapWbsLevel 3',
184
+ 'TargetSapWbsLevel3',
185
+ 'TargetSapWbs Level 3',
186
+ ])
187
+ );
188
+ const targetIdLevel4 = this.toPositiveNumber(
189
+ this.externalTableUtils.readExternalCell(row, [
190
+ 'TargetWorkItemIdLevel 4',
191
+ 'TargetWorkItemId Level 4',
192
+ 'TargetWorkItemIdLevel4',
193
+ ])
194
+ );
195
+ const targetTitleLevel4 = adapters.toComparableText(
196
+ this.externalTableUtils.readExternalCell(row, [
197
+ 'TargetTitleLevel4',
198
+ 'TargetTitleLevel 4',
199
+ 'TargetTitle Level 4',
200
+ ])
201
+ );
202
+ const targetStateLevel4 = adapters.toComparableText(
203
+ this.externalTableUtils.readExternalCell(row, ['TargetStateLevel 4', 'TargetStateLevel4'])
204
+ );
205
+ const targetSapWbsLevel4 = adapters.toComparableText(
206
+ this.externalTableUtils.readExternalCell(row, [
207
+ 'TargetSapWbsLevel 4',
208
+ 'TargetSapWbsLevel4',
209
+ 'TargetSapWbs Level 4',
210
+ ])
211
+ );
212
+
213
+ if (area.includes('level 4')) {
214
+ const effectiveSapWbsLevel3 = targetSapWbsLevel3 || requirementSapWbsFallback;
215
+ if (
216
+ targetIdLevel3 &&
217
+ adapters.isExternalStateInScope(targetStateLevel3, 'requirement') &&
218
+ !adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel3)
219
+ ) {
220
+ addLink(baseKey, 'L4', targetIdLevel3, targetTitleLevel3);
221
+ }
222
+ parsedRows += 1;
223
+ continue;
224
+ }
225
+
226
+ const effectiveSapWbsLevel3 = targetSapWbsLevel3 || requirementSapWbsFallback;
227
+ if (
228
+ targetIdLevel3 &&
229
+ adapters.isExternalStateInScope(targetStateLevel3, 'requirement') &&
230
+ !adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel3)
231
+ ) {
232
+ addLink(baseKey, 'L3', targetIdLevel3, targetTitleLevel3);
233
+ }
234
+ const effectiveSapWbsLevel4 = targetSapWbsLevel4 || requirementSapWbsFallback;
235
+ if (
236
+ targetIdLevel4 &&
237
+ adapters.isExternalStateInScope(targetStateLevel4, 'requirement') &&
238
+ !adapters.isExcludedL3L4BySapWbs(effectiveSapWbsLevel4)
239
+ ) {
240
+ addLink(baseKey, 'L4', targetIdLevel4, targetTitleLevel4);
241
+ }
242
+ parsedRows += 1;
243
+ }
244
+
245
+ const out = new Map<string, MewpL3L4Link[]>();
246
+ for (const [baseKey, linksById] of linksByBaseKey.entries()) {
247
+ out.set(
248
+ baseKey,
249
+ [...linksById.values()].sort((a, b) => {
250
+ if (a.level !== b.level) return a.level === 'L3' ? -1 : 1;
251
+ return a.id.localeCompare(b.id);
252
+ })
253
+ );
254
+ }
255
+
256
+ if (parsedRows === 0) {
257
+ logger.warn(
258
+ `External L3/L4 source was loaded but no valid rows were parsed. ` +
259
+ `Expected columns include SR, AREA 34, target IDs/titles/states.`
260
+ );
261
+ }
262
+
263
+ return out;
264
+ }
265
+
266
+ private toPositiveNumber(value: any): number {
267
+ const parsed = Number(String(value || '').replace(/[^\d]/g, ''));
268
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
269
+ }
270
+ }