@elisra-devops/docgen-data-provider 1.77.0 → 1.79.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 +1 -0
- package/bin/modules/ResultDataProvider.d.ts +0 -1
- package/bin/modules/ResultDataProvider.js +72 -50
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +6 -6
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mewp-reporting.ts +1 -0
- package/src/modules/ResultDataProvider.ts +89 -44
- package/src/tests/modules/ResultDataProvider.test.ts +6 -7
|
@@ -598,6 +598,69 @@ export default class ResultDataProvider {
|
|
|
598
598
|
);
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
const requirementBaseKeys = new Set<string>(
|
|
602
|
+
requirements.map((item) => String(item?.baseKey || '').trim()).filter((item) => !!item)
|
|
603
|
+
);
|
|
604
|
+
const externalL3L4BaseKeys = new Set<string>([...externalL3L4ByBaseKey.keys()]);
|
|
605
|
+
const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) => requirementBaseKeys.has(key));
|
|
606
|
+
const failedRequirementBaseKeys = new Set<string>();
|
|
607
|
+
const failedTestCaseIds = new Set<number>();
|
|
608
|
+
for (const [requirementBaseKey, byTestCase] of requirementIndex.entries()) {
|
|
609
|
+
for (const [testCaseId, counts] of byTestCase.entries()) {
|
|
610
|
+
if (Number(counts?.failed || 0) > 0) {
|
|
611
|
+
failedRequirementBaseKeys.add(requirementBaseKey);
|
|
612
|
+
failedTestCaseIds.add(testCaseId);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const externalBugTestCaseIds = new Set<number>([...externalBugsByTestCase.keys()]);
|
|
617
|
+
const externalBugFailedTestCaseOverlap = [...externalBugTestCaseIds].filter((id) =>
|
|
618
|
+
failedTestCaseIds.has(id)
|
|
619
|
+
);
|
|
620
|
+
const externalBugBaseKeys = new Set<string>();
|
|
621
|
+
for (const bugs of externalBugsByTestCase.values()) {
|
|
622
|
+
for (const bug of bugs || []) {
|
|
623
|
+
const key = String(bug?.requirementBaseKey || '').trim();
|
|
624
|
+
if (key) externalBugBaseKeys.add(key);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) =>
|
|
628
|
+
requirementBaseKeys.has(key)
|
|
629
|
+
);
|
|
630
|
+
const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) =>
|
|
631
|
+
failedRequirementBaseKeys.has(key)
|
|
632
|
+
);
|
|
633
|
+
logger.info(
|
|
634
|
+
`MEWP coverage join diagnostics: requirementBaseKeys=${requirementBaseKeys.size} ` +
|
|
635
|
+
`failedRequirementBaseKeys=${failedRequirementBaseKeys.size} failedTestCases=${failedTestCaseIds.size}; ` +
|
|
636
|
+
`externalL3L4BaseKeys=${externalL3L4BaseKeys.size} externalL3L4Overlap=${externalL3L4OverlapKeys.length}; ` +
|
|
637
|
+
`externalBugTestCases=${externalBugTestCaseIds.size} externalBugFailedTestCaseOverlap=${externalBugFailedTestCaseOverlap.length}; ` +
|
|
638
|
+
`externalBugBaseKeys=${externalBugBaseKeys.size} externalBugRequirementOverlap=${externalBugRequirementOverlap.length} ` +
|
|
639
|
+
`externalBugFailedRequirementOverlap=${externalBugFailedRequirementOverlap.length}`
|
|
640
|
+
);
|
|
641
|
+
if (externalL3L4BaseKeys.size > 0 && externalL3L4OverlapKeys.length === 0) {
|
|
642
|
+
const sampleReq = [...requirementBaseKeys].slice(0, 5);
|
|
643
|
+
const sampleExt = [...externalL3L4BaseKeys].slice(0, 5);
|
|
644
|
+
logger.warn(
|
|
645
|
+
`MEWP coverage join diagnostics: no L3/L4 key overlap found. ` +
|
|
646
|
+
`sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalL3L4Keys=${sampleExt.join(', ')}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (externalBugBaseKeys.size > 0 && externalBugRequirementOverlap.length === 0) {
|
|
650
|
+
const sampleReq = [...requirementBaseKeys].slice(0, 5);
|
|
651
|
+
const sampleExt = [...externalBugBaseKeys].slice(0, 5);
|
|
652
|
+
logger.warn(
|
|
653
|
+
`MEWP coverage join diagnostics: no bug requirement-key overlap found. ` +
|
|
654
|
+
`sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalBugKeys=${sampleExt.join(', ')}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (externalBugTestCaseIds.size > 0 && externalBugFailedTestCaseOverlap.length === 0) {
|
|
658
|
+
logger.warn(
|
|
659
|
+
`MEWP coverage join diagnostics: no overlap between external bug test cases and failed test cases. ` +
|
|
660
|
+
`Bug rows remain empty because bugs are shown only for failed L2s.`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
601
664
|
const rows = this.buildMewpCoverageRows(
|
|
602
665
|
requirements,
|
|
603
666
|
requirementIndex,
|
|
@@ -936,12 +999,16 @@ export default class ResultDataProvider {
|
|
|
936
999
|
}
|
|
937
1000
|
|
|
938
1001
|
private createMewpCoverageRow(
|
|
939
|
-
requirement: Pick<
|
|
1002
|
+
requirement: Pick<
|
|
1003
|
+
MewpL2RequirementFamily,
|
|
1004
|
+
'workItemId' | 'requirementId' | 'title' | 'subSystem' | 'responsibility'
|
|
1005
|
+
>,
|
|
940
1006
|
runStatus: MewpRunStatus,
|
|
941
1007
|
bug: MewpCoverageBugCell,
|
|
942
1008
|
linkedL3L4: MewpCoverageL3L4Cell
|
|
943
1009
|
): MewpCoverageRow {
|
|
944
|
-
const
|
|
1010
|
+
const l2ReqIdNumeric = Number(requirement?.workItemId || 0);
|
|
1011
|
+
const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
|
|
945
1012
|
const l2ReqTitle = this.toMewpComparableText(requirement.title);
|
|
946
1013
|
const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
|
|
947
1014
|
|
|
@@ -987,15 +1054,6 @@ export default class ResultDataProvider {
|
|
|
987
1054
|
return rows;
|
|
988
1055
|
}
|
|
989
1056
|
|
|
990
|
-
private formatMewpCustomerId(rawValue: string): string {
|
|
991
|
-
const normalized = this.normalizeMewpRequirementCode(this.toMewpComparableText(rawValue));
|
|
992
|
-
if (normalized) return normalized;
|
|
993
|
-
|
|
994
|
-
const onlyDigits = String(rawValue || '').replace(/\D/g, '');
|
|
995
|
-
if (onlyDigits) return `SR${onlyDigits}`;
|
|
996
|
-
return '';
|
|
997
|
-
}
|
|
998
|
-
|
|
999
1057
|
private buildMewpCoverageRows(
|
|
1000
1058
|
requirements: MewpL2RequirementFamily[],
|
|
1001
1059
|
requirementIndex: MewpRequirementIndex,
|
|
@@ -1727,7 +1785,7 @@ export default class ResultDataProvider {
|
|
|
1727
1785
|
const workItems = await this.fetchWorkItemsByIds(projectName, requirementIds, true);
|
|
1728
1786
|
const requirements = workItems.map((wi: any) => {
|
|
1729
1787
|
const fields = wi?.fields || {};
|
|
1730
|
-
const requirementId = this.extractMewpRequirementIdentifier(fields
|
|
1788
|
+
const requirementId = this.extractMewpRequirementIdentifier(fields);
|
|
1731
1789
|
const areaPath = this.toMewpComparableText(fields?.['System.AreaPath']);
|
|
1732
1790
|
return {
|
|
1733
1791
|
workItemId: Number(wi?.id || 0),
|
|
@@ -1815,6 +1873,7 @@ export default class ResultDataProvider {
|
|
|
1815
1873
|
|
|
1816
1874
|
return [...families.entries()]
|
|
1817
1875
|
.map(([baseKey, family]) => ({
|
|
1876
|
+
workItemId: Number(family?.representative?.workItemId || 0),
|
|
1818
1877
|
requirementId: String(family?.representative?.requirementId || baseKey),
|
|
1819
1878
|
baseKey,
|
|
1820
1879
|
title: String(family?.representative?.title || ''),
|
|
@@ -2090,47 +2149,33 @@ export default class ResultDataProvider {
|
|
|
2090
2149
|
return [...out].sort((a, b) => a - b);
|
|
2091
2150
|
}
|
|
2092
2151
|
|
|
2093
|
-
private extractMewpRequirementIdentifier(fields: Record<string, any
|
|
2152
|
+
private extractMewpRequirementIdentifier(fields: Record<string, any>): string {
|
|
2094
2153
|
const entries = Object.entries(fields || {});
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2154
|
+
const normalizeFieldKey = (value: string): string =>
|
|
2155
|
+
String(value || '')
|
|
2156
|
+
.toLowerCase()
|
|
2157
|
+
.replace(/[^a-z0-9]/g, '');
|
|
2158
|
+
|
|
2159
|
+
// Strict MEWP mode: only explicit MEWP customer-id fields are accepted.
|
|
2160
|
+
// API display name: "Customer ID"
|
|
2161
|
+
// API reference name: "Custom.CustomerID"
|
|
2162
|
+
const customerIdFieldKeys = new Set<string>([
|
|
2098
2163
|
'customerid',
|
|
2099
|
-
'
|
|
2100
|
-
|
|
2101
|
-
'requirementid',
|
|
2102
|
-
'externalid',
|
|
2103
|
-
'srid',
|
|
2104
|
-
'sapwbsid',
|
|
2105
|
-
];
|
|
2106
|
-
for (const [key, value] of entries) {
|
|
2107
|
-
const normalizedKey = String(key || '').toLowerCase();
|
|
2108
|
-
if (!strictHints.some((hint) => normalizedKey.includes(hint))) continue;
|
|
2109
|
-
|
|
2110
|
-
const valueAsString = this.toMewpComparableText(value);
|
|
2111
|
-
if (!valueAsString) continue;
|
|
2112
|
-
const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
|
|
2113
|
-
if (normalized) return normalized;
|
|
2114
|
-
}
|
|
2164
|
+
'customcustomerid',
|
|
2165
|
+
]);
|
|
2115
2166
|
|
|
2116
|
-
// Second pass: weaker hints, but still key-based only.
|
|
2117
|
-
const looseHints = ['customer', 'requirement', 'external', 'sapwbs', 'sr'];
|
|
2118
2167
|
for (const [key, value] of entries) {
|
|
2119
|
-
const normalizedKey =
|
|
2120
|
-
if (!
|
|
2168
|
+
const normalizedKey = normalizeFieldKey(key);
|
|
2169
|
+
if (!customerIdFieldKeys.has(normalizedKey)) continue;
|
|
2121
2170
|
|
|
2122
2171
|
const valueAsString = this.toMewpComparableText(value);
|
|
2123
2172
|
if (!valueAsString) continue;
|
|
2124
|
-
const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
|
|
2125
|
-
if (normalized) return normalized;
|
|
2126
|
-
}
|
|
2127
2173
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
if (titleCode) return titleCode;
|
|
2174
|
+
const normalizedRequirementId = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
|
|
2175
|
+
if (normalizedRequirementId) return normalizedRequirementId;
|
|
2176
|
+
}
|
|
2132
2177
|
|
|
2133
|
-
return
|
|
2178
|
+
return '';
|
|
2134
2179
|
}
|
|
2135
2180
|
|
|
2136
2181
|
private deriveMewpResponsibility(fields: Record<string, any>): string {
|
|
@@ -1178,9 +1178,9 @@ describe('ResultDataProvider', () => {
|
|
|
1178
1178
|
})
|
|
1179
1179
|
);
|
|
1180
1180
|
|
|
1181
|
-
const covered = result.rows.find((row: any) => row['L2 REQ ID'] === '
|
|
1182
|
-
const inferredByStepText = result.rows.find((row: any) => row['L2 REQ ID'] === '
|
|
1183
|
-
const uncovered = result.rows.find((row: any) => row['L2 REQ ID'] === '
|
|
1181
|
+
const covered = result.rows.find((row: any) => row['L2 REQ ID'] === '5001');
|
|
1182
|
+
const inferredByStepText = result.rows.find((row: any) => row['L2 REQ ID'] === '5002');
|
|
1183
|
+
const uncovered = result.rows.find((row: any) => row['L2 REQ ID'] === '5003');
|
|
1184
1184
|
|
|
1185
1185
|
expect(covered).toEqual(
|
|
1186
1186
|
expect.objectContaining({
|
|
@@ -1297,7 +1297,7 @@ describe('ResultDataProvider', () => {
|
|
|
1297
1297
|
[1]
|
|
1298
1298
|
);
|
|
1299
1299
|
|
|
1300
|
-
const row = result.rows.find((item: any) => item['L2 REQ ID'] === '
|
|
1300
|
+
const row = result.rows.find((item: any) => item['L2 REQ ID'] === '7001');
|
|
1301
1301
|
expect(parseSpy).not.toHaveBeenCalled();
|
|
1302
1302
|
expect(row).toEqual(
|
|
1303
1303
|
expect.objectContaining({
|
|
@@ -1312,11 +1312,10 @@ describe('ResultDataProvider', () => {
|
|
|
1312
1312
|
'System.Description': 'random text with SR9999 that is unrelated',
|
|
1313
1313
|
'Custom.CustomerId': 'customer id unknown',
|
|
1314
1314
|
'System.Title': 'Requirement without explicit SR code',
|
|
1315
|
-
}
|
|
1316
|
-
4321
|
|
1315
|
+
}
|
|
1317
1316
|
);
|
|
1318
1317
|
|
|
1319
|
-
expect(requirementId).toBe('
|
|
1318
|
+
expect(requirementId).toBe('');
|
|
1320
1319
|
});
|
|
1321
1320
|
|
|
1322
1321
|
it('should derive responsibility from Custom.SAPWBS when present', () => {
|