@elisra-devops/docgen-data-provider 1.79.0 → 1.81.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 +7 -6
- package/bin/modules/ResultDataProvider.d.ts +2 -0
- package/bin/modules/ResultDataProvider.js +201 -36
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +34 -14
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/bin/utils/mewpExternalIngestionUtils.d.ts +2 -2
- package/bin/utils/mewpExternalIngestionUtils.js +96 -41
- package/bin/utils/mewpExternalIngestionUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mewp-reporting.ts +8 -6
- package/src/modules/ResultDataProvider.ts +222 -57
- package/src/tests/modules/ResultDataProvider.test.ts +36 -14
- package/src/utils/mewpExternalIngestionUtils.ts +95 -47
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
MewpInternalValidationRow,
|
|
18
18
|
MewpL2RequirementFamily,
|
|
19
19
|
MewpL2RequirementWorkItem,
|
|
20
|
-
|
|
20
|
+
MewpL3L4Pair,
|
|
21
21
|
MewpLinkedRequirementsByTestCase,
|
|
22
22
|
MewpRequirementIndex,
|
|
23
23
|
MewpRunStatus,
|
|
@@ -472,6 +472,11 @@ export default class ResultDataProvider {
|
|
|
472
472
|
allRequirements,
|
|
473
473
|
scopedRequirementKeys?.size ? scopedRequirementKeys : undefined
|
|
474
474
|
);
|
|
475
|
+
const l2ToLinkedL1BaseKeys = await this.buildMewpL2ToLinkedL1BaseKeys(
|
|
476
|
+
allRequirements,
|
|
477
|
+
projectName,
|
|
478
|
+
testData
|
|
479
|
+
);
|
|
475
480
|
const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
|
|
476
481
|
const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options?.externalBugsFile);
|
|
477
482
|
const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(
|
|
@@ -523,6 +528,10 @@ export default class ResultDataProvider {
|
|
|
523
528
|
sheetName: this.buildMewpCoverageSheetName(planName, testPlanId),
|
|
524
529
|
};
|
|
525
530
|
}
|
|
531
|
+
const externalJoinKeysByL2 = this.buildMewpExternalJoinKeysByL2Requirement(
|
|
532
|
+
requirements,
|
|
533
|
+
l2ToLinkedL1BaseKeys
|
|
534
|
+
);
|
|
526
535
|
|
|
527
536
|
const requirementIndex: MewpRequirementIndex = new Map();
|
|
528
537
|
const observedTestCaseIdsByRequirement = new Map<string, Set<number>>();
|
|
@@ -601,8 +610,16 @@ export default class ResultDataProvider {
|
|
|
601
610
|
const requirementBaseKeys = new Set<string>(
|
|
602
611
|
requirements.map((item) => String(item?.baseKey || '').trim()).filter((item) => !!item)
|
|
603
612
|
);
|
|
613
|
+
const externalJoinKeyUniverse = new Set<string>();
|
|
614
|
+
for (const keySet of externalJoinKeysByL2.values()) {
|
|
615
|
+
for (const key of keySet) {
|
|
616
|
+
if (key) externalJoinKeyUniverse.add(key);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
604
619
|
const externalL3L4BaseKeys = new Set<string>([...externalL3L4ByBaseKey.keys()]);
|
|
605
|
-
const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) =>
|
|
620
|
+
const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) =>
|
|
621
|
+
externalJoinKeyUniverse.has(key)
|
|
622
|
+
);
|
|
606
623
|
const failedRequirementBaseKeys = new Set<string>();
|
|
607
624
|
const failedTestCaseIds = new Set<number>();
|
|
608
625
|
for (const [requirementBaseKey, byTestCase] of requirementIndex.entries()) {
|
|
@@ -625,13 +642,14 @@ export default class ResultDataProvider {
|
|
|
625
642
|
}
|
|
626
643
|
}
|
|
627
644
|
const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) =>
|
|
628
|
-
|
|
645
|
+
externalJoinKeyUniverse.has(key)
|
|
629
646
|
);
|
|
630
647
|
const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) =>
|
|
631
|
-
|
|
648
|
+
externalJoinKeyUniverse.has(key)
|
|
632
649
|
);
|
|
633
650
|
logger.info(
|
|
634
651
|
`MEWP coverage join diagnostics: requirementBaseKeys=${requirementBaseKeys.size} ` +
|
|
652
|
+
`externalJoinKeys=${externalJoinKeyUniverse.size} ` +
|
|
635
653
|
`failedRequirementBaseKeys=${failedRequirementBaseKeys.size} failedTestCases=${failedTestCaseIds.size}; ` +
|
|
636
654
|
`externalL3L4BaseKeys=${externalL3L4BaseKeys.size} externalL3L4Overlap=${externalL3L4OverlapKeys.length}; ` +
|
|
637
655
|
`externalBugTestCases=${externalBugTestCaseIds.size} externalBugFailedTestCaseOverlap=${externalBugFailedTestCaseOverlap.length}; ` +
|
|
@@ -640,18 +658,22 @@ export default class ResultDataProvider {
|
|
|
640
658
|
);
|
|
641
659
|
if (externalL3L4BaseKeys.size > 0 && externalL3L4OverlapKeys.length === 0) {
|
|
642
660
|
const sampleReq = [...requirementBaseKeys].slice(0, 5);
|
|
661
|
+
const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
|
|
643
662
|
const sampleExt = [...externalL3L4BaseKeys].slice(0, 5);
|
|
644
663
|
logger.warn(
|
|
645
664
|
`MEWP coverage join diagnostics: no L3/L4 key overlap found. ` +
|
|
646
|
-
`sampleRequirementKeys=${sampleReq.join(', ')}
|
|
665
|
+
`sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
|
|
666
|
+
`sampleExternalL3L4Keys=${sampleExt.join(', ')}`
|
|
647
667
|
);
|
|
648
668
|
}
|
|
649
669
|
if (externalBugBaseKeys.size > 0 && externalBugRequirementOverlap.length === 0) {
|
|
650
670
|
const sampleReq = [...requirementBaseKeys].slice(0, 5);
|
|
671
|
+
const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
|
|
651
672
|
const sampleExt = [...externalBugBaseKeys].slice(0, 5);
|
|
652
673
|
logger.warn(
|
|
653
674
|
`MEWP coverage join diagnostics: no bug requirement-key overlap found. ` +
|
|
654
|
-
`sampleRequirementKeys=${sampleReq.join(', ')}
|
|
675
|
+
`sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
|
|
676
|
+
`sampleExternalBugKeys=${sampleExt.join(', ')}`
|
|
655
677
|
);
|
|
656
678
|
}
|
|
657
679
|
if (externalBugTestCaseIds.size > 0 && externalBugFailedTestCaseOverlap.length === 0) {
|
|
@@ -667,7 +689,8 @@ export default class ResultDataProvider {
|
|
|
667
689
|
observedTestCaseIdsByRequirement,
|
|
668
690
|
linkedRequirementsByTestCase,
|
|
669
691
|
externalL3L4ByBaseKey,
|
|
670
|
-
externalBugsByTestCase
|
|
692
|
+
externalBugsByTestCase,
|
|
693
|
+
externalJoinKeysByL2
|
|
671
694
|
);
|
|
672
695
|
const coverageRowStats = rows.reduce(
|
|
673
696
|
(acc, row) => {
|
|
@@ -757,14 +780,26 @@ export default class ResultDataProvider {
|
|
|
757
780
|
}
|
|
758
781
|
|
|
759
782
|
const validL2BaseKeys = new Set<string>([...requirementFamilies.keys()]);
|
|
783
|
+
const diagnostics = {
|
|
784
|
+
totalTestCases: 0,
|
|
785
|
+
totalParsedSteps: 0,
|
|
786
|
+
totalStepsWithMentions: 0,
|
|
787
|
+
totalMentionedCustomerIds: 0,
|
|
788
|
+
testCasesWithoutMentionedCustomerIds: 0,
|
|
789
|
+
failingRows: 0,
|
|
790
|
+
};
|
|
760
791
|
|
|
761
792
|
for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
|
|
793
|
+
diagnostics.totalTestCases += 1;
|
|
762
794
|
const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
|
|
763
795
|
const parsedSteps =
|
|
764
796
|
stepsXml && String(stepsXml).trim() !== ''
|
|
765
797
|
? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map<number, number>())
|
|
766
798
|
: [];
|
|
799
|
+
const executableSteps = parsedSteps.filter((step) => !step?.isSharedStepTitle);
|
|
800
|
+
diagnostics.totalParsedSteps += executableSteps.length;
|
|
767
801
|
const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
|
|
802
|
+
diagnostics.totalStepsWithMentions += mentionEntries.length;
|
|
768
803
|
const mentionedL2Only = new Set<string>();
|
|
769
804
|
const mentionedCodeFirstStep = new Map<string, string>();
|
|
770
805
|
const mentionedBaseFirstStep = new Map<string, string>();
|
|
@@ -787,6 +822,10 @@ export default class ResultDataProvider {
|
|
|
787
822
|
}
|
|
788
823
|
}
|
|
789
824
|
}
|
|
825
|
+
diagnostics.totalMentionedCustomerIds += mentionedL2Only.size;
|
|
826
|
+
if (mentionedL2Only.size === 0) {
|
|
827
|
+
diagnostics.testCasesWithoutMentionedCustomerIds += 1;
|
|
828
|
+
}
|
|
790
829
|
|
|
791
830
|
const mentionedBaseKeys = new Set<string>(
|
|
792
831
|
[...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
@@ -874,6 +913,15 @@ export default class ResultDataProvider {
|
|
|
874
913
|
const linkedButNotMentioned = sortedExtraLinked.join('; ');
|
|
875
914
|
const validationStatus: 'Pass' | 'Fail' =
|
|
876
915
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
916
|
+
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
917
|
+
logger.debug(
|
|
918
|
+
`MEWP internal validation parse diagnostics: ` +
|
|
919
|
+
`testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
|
|
920
|
+
`stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
|
|
921
|
+
`linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingMentioned.length + sortedMissingFamily.length} ` +
|
|
922
|
+
`linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
|
|
923
|
+
`customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`
|
|
924
|
+
);
|
|
877
925
|
|
|
878
926
|
rows.push({
|
|
879
927
|
'Test Case ID': testCaseId,
|
|
@@ -883,6 +931,13 @@ export default class ResultDataProvider {
|
|
|
883
931
|
'Validation Status': validationStatus,
|
|
884
932
|
});
|
|
885
933
|
}
|
|
934
|
+
logger.info(
|
|
935
|
+
`MEWP internal validation summary: testCases=${diagnostics.totalTestCases} ` +
|
|
936
|
+
`parsedSteps=${diagnostics.totalParsedSteps} stepsWithMentions=${diagnostics.totalStepsWithMentions} ` +
|
|
937
|
+
`totalCustomerIdsFound=${diagnostics.totalMentionedCustomerIds} ` +
|
|
938
|
+
`testCasesWithoutCustomerIds=${diagnostics.testCasesWithoutMentionedCustomerIds} ` +
|
|
939
|
+
`failingRows=${diagnostics.failingRows}`
|
|
940
|
+
);
|
|
886
941
|
|
|
887
942
|
return {
|
|
888
943
|
sheetName: this.buildInternalValidationSheetName(planName, testPlanId),
|
|
@@ -1035,23 +1090,35 @@ export default class ResultDataProvider {
|
|
|
1035
1090
|
return { l3Id: '', l3Title: '', l4Id: '', l4Title: '' };
|
|
1036
1091
|
}
|
|
1037
1092
|
|
|
1038
|
-
private buildMewpCoverageL3L4Rows(
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1093
|
+
private buildMewpCoverageL3L4Rows(pairs: MewpL3L4Pair[]): MewpCoverageL3L4Cell[] {
|
|
1094
|
+
const deduped = new Map<string, MewpCoverageL3L4Cell>();
|
|
1095
|
+
for (const pair of pairs || []) {
|
|
1096
|
+
const l3Id = String(pair?.l3Id || '').trim();
|
|
1097
|
+
const l3Title = String(pair?.l3Title || '').trim();
|
|
1098
|
+
const l4Id = String(pair?.l4Id || '').trim();
|
|
1099
|
+
const l4Title = String(pair?.l4Title || '').trim();
|
|
1100
|
+
if (!l3Id && !l4Id) continue;
|
|
1101
|
+
|
|
1102
|
+
const key = `${l3Id}|${l4Id}`;
|
|
1103
|
+
const existing = deduped.get(key);
|
|
1104
|
+
if (!existing) {
|
|
1105
|
+
deduped.set(key, { l3Id, l3Title, l4Id, l4Title });
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1043
1108
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
l3Title: isL3 ? String(item?.title || '').trim() : '',
|
|
1050
|
-
l4Id: isL3 ? '' : String(item?.id || '').trim(),
|
|
1051
|
-
l4Title: isL3 ? '' : String(item?.title || '').trim(),
|
|
1109
|
+
deduped.set(key, {
|
|
1110
|
+
l3Id: existing.l3Id || l3Id,
|
|
1111
|
+
l3Title: existing.l3Title || l3Title,
|
|
1112
|
+
l4Id: existing.l4Id || l4Id,
|
|
1113
|
+
l4Title: existing.l4Title || l4Title,
|
|
1052
1114
|
});
|
|
1053
1115
|
}
|
|
1054
|
-
|
|
1116
|
+
|
|
1117
|
+
return [...deduped.values()].sort((a, b) => {
|
|
1118
|
+
const l3Compare = String(a?.l3Id || '').localeCompare(String(b?.l3Id || ''));
|
|
1119
|
+
if (l3Compare !== 0) return l3Compare;
|
|
1120
|
+
return String(a?.l4Id || '').localeCompare(String(b?.l4Id || ''));
|
|
1121
|
+
});
|
|
1055
1122
|
}
|
|
1056
1123
|
|
|
1057
1124
|
private buildMewpCoverageRows(
|
|
@@ -1059,13 +1126,16 @@ export default class ResultDataProvider {
|
|
|
1059
1126
|
requirementIndex: MewpRequirementIndex,
|
|
1060
1127
|
observedTestCaseIdsByRequirement: Map<string, Set<number>>,
|
|
1061
1128
|
linkedRequirementsByTestCase: MewpLinkedRequirementsByTestCase,
|
|
1062
|
-
l3l4ByBaseKey: Map<string,
|
|
1063
|
-
externalBugsByTestCase: Map<number, MewpBugLink[]
|
|
1129
|
+
l3l4ByBaseKey: Map<string, MewpL3L4Pair[]>,
|
|
1130
|
+
externalBugsByTestCase: Map<number, MewpBugLink[]>,
|
|
1131
|
+
externalJoinKeysByL2?: Map<string, Set<string>>
|
|
1064
1132
|
): MewpCoverageRow[] {
|
|
1065
1133
|
const rows: MewpCoverageRow[] = [];
|
|
1066
1134
|
const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
|
|
1135
|
+
const joinKeysByRequirement = externalJoinKeysByL2 || new Map<string, Set<string>>();
|
|
1067
1136
|
for (const requirement of requirements) {
|
|
1068
1137
|
const key = String(requirement?.baseKey || this.toRequirementKey(requirement.requirementId) || '').trim();
|
|
1138
|
+
const externalJoinKeys = joinKeysByRequirement.get(key) || new Set<string>([key]);
|
|
1069
1139
|
const linkedTestCaseIds = (requirement?.linkedTestCaseIds || []).filter(
|
|
1070
1140
|
(id) => Number.isFinite(id) && Number(id) > 0
|
|
1071
1141
|
);
|
|
@@ -1095,7 +1165,7 @@ export default class ResultDataProvider {
|
|
|
1095
1165
|
const externalBugs = externalBugsByTestCase.get(testCaseId) || [];
|
|
1096
1166
|
for (const bug of externalBugs) {
|
|
1097
1167
|
const bugBaseKey = String(bug?.requirementBaseKey || '').trim();
|
|
1098
|
-
if (bugBaseKey && bugBaseKey
|
|
1168
|
+
if (bugBaseKey && !externalJoinKeys.has(bugBaseKey)) continue;
|
|
1099
1169
|
const bugId = Number(bug?.id || 0);
|
|
1100
1170
|
if (!Number.isFinite(bugId) || bugId <= 0) continue;
|
|
1101
1171
|
aggregatedBugs.set(bugId, {
|
|
@@ -1120,44 +1190,22 @@ export default class ResultDataProvider {
|
|
|
1120
1190
|
runStatus === 'Fail'
|
|
1121
1191
|
? Array.from(aggregatedBugs.values()).sort((a, b) => a.id - b.id)
|
|
1122
1192
|
: [];
|
|
1123
|
-
const l3l4ForRows = [...(l3l4ByBaseKey.get(
|
|
1193
|
+
const l3l4ForRows = [...externalJoinKeys].flatMap((joinKey) => l3l4ByBaseKey.get(joinKey) || []);
|
|
1124
1194
|
|
|
1125
|
-
const bugRows: MewpCoverageBugCell[] =
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1195
|
+
const bugRows: MewpCoverageBugCell[] = bugsForRows.map((bug) => ({
|
|
1196
|
+
id: Number.isFinite(Number(bug?.id)) && Number(bug?.id) > 0 ? Number(bug?.id) : '',
|
|
1197
|
+
title: String(bug?.title || '').trim(),
|
|
1198
|
+
responsibility: String(bug?.responsibility || '').trim(),
|
|
1199
|
+
}));
|
|
1129
1200
|
const l3l4Rows: MewpCoverageL3L4Cell[] = this.buildMewpCoverageL3L4Rows(l3l4ForRows);
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
rows.push(
|
|
1133
|
-
this.createMewpCoverageRow(
|
|
1134
|
-
requirement,
|
|
1135
|
-
runStatus,
|
|
1136
|
-
this.createEmptyMewpCoverageBugCell(),
|
|
1137
|
-
this.createEmptyMewpCoverageL3L4Cell()
|
|
1138
|
-
)
|
|
1139
|
-
);
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
for (const bug of bugRows) {
|
|
1201
|
+
const rowCount = Math.max(bugRows.length, l3l4Rows.length, 1);
|
|
1202
|
+
for (let index = 0; index < rowCount; index += 1) {
|
|
1144
1203
|
rows.push(
|
|
1145
1204
|
this.createMewpCoverageRow(
|
|
1146
1205
|
requirement,
|
|
1147
1206
|
runStatus,
|
|
1148
|
-
|
|
1149
|
-
this.createEmptyMewpCoverageL3L4Cell()
|
|
1150
|
-
)
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
for (const linkedL3L4 of l3l4Rows) {
|
|
1155
|
-
rows.push(
|
|
1156
|
-
this.createMewpCoverageRow(
|
|
1157
|
-
requirement,
|
|
1158
|
-
runStatus,
|
|
1159
|
-
this.createEmptyMewpCoverageBugCell(),
|
|
1160
|
-
linkedL3L4
|
|
1207
|
+
bugRows[index] || this.createEmptyMewpCoverageBugCell(),
|
|
1208
|
+
l3l4Rows[index] || this.createEmptyMewpCoverageL3L4Cell()
|
|
1161
1209
|
)
|
|
1162
1210
|
);
|
|
1163
1211
|
}
|
|
@@ -1348,7 +1396,7 @@ export default class ResultDataProvider {
|
|
|
1348
1396
|
private async loadExternalL3L4ByBaseKey(
|
|
1349
1397
|
externalL3L4File: MewpExternalFileRef | null | undefined,
|
|
1350
1398
|
requirementSapWbsByBaseKey: Map<string, string> = new Map<string, string>()
|
|
1351
|
-
): Promise<Map<string,
|
|
1399
|
+
): Promise<Map<string, MewpL3L4Pair[]>> {
|
|
1352
1400
|
return this.mewpExternalIngestionUtils.loadExternalL3L4ByBaseKey(externalL3L4File, {
|
|
1353
1401
|
toComparableText: (value) => this.toMewpComparableText(value),
|
|
1354
1402
|
toRequirementKey: (value) => this.toRequirementKey(value),
|
|
@@ -1884,6 +1932,123 @@ export default class ResultDataProvider {
|
|
|
1884
1932
|
.sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
|
|
1885
1933
|
}
|
|
1886
1934
|
|
|
1935
|
+
private async buildMewpL2ToLinkedL1BaseKeys(
|
|
1936
|
+
requirements: MewpL2RequirementWorkItem[],
|
|
1937
|
+
projectName: string,
|
|
1938
|
+
testData: any[]
|
|
1939
|
+
): Promise<Map<string, Set<string>>> {
|
|
1940
|
+
const out = new Map<string, Set<string>>();
|
|
1941
|
+
const relatedIds = new Set<number>();
|
|
1942
|
+
const linkedTestCaseIdsByL2 = new Map<string, Set<number>>();
|
|
1943
|
+
const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
|
|
1944
|
+
|
|
1945
|
+
for (const requirement of requirements || []) {
|
|
1946
|
+
const l2BaseKey = String(requirement?.baseKey || '').trim();
|
|
1947
|
+
if (!l2BaseKey) continue;
|
|
1948
|
+
if (!out.has(l2BaseKey)) out.set(l2BaseKey, new Set<string>());
|
|
1949
|
+
if (!linkedTestCaseIdsByL2.has(l2BaseKey)) linkedTestCaseIdsByL2.set(l2BaseKey, new Set<number>());
|
|
1950
|
+
for (const testCaseId of requirement?.linkedTestCaseIds || []) {
|
|
1951
|
+
const numeric = Number(testCaseId);
|
|
1952
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
1953
|
+
linkedTestCaseIdsByL2.get(l2BaseKey)!.add(numeric);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
for (const relatedId of requirement?.relatedWorkItemIds || []) {
|
|
1957
|
+
const id = Number(relatedId);
|
|
1958
|
+
if (Number.isFinite(id) && id > 0) relatedIds.add(id);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
let titleDerivedCount = 0;
|
|
1963
|
+
for (const [l2BaseKey, testCaseIds] of linkedTestCaseIdsByL2.entries()) {
|
|
1964
|
+
const targetSet = out.get(l2BaseKey) || new Set<string>();
|
|
1965
|
+
for (const testCaseId of testCaseIds) {
|
|
1966
|
+
const title = String(testCaseTitleMap.get(testCaseId) || '').trim();
|
|
1967
|
+
if (!title) continue;
|
|
1968
|
+
const fromTitleCodes = this.extractRequirementCodesFromExpectedText(title, false);
|
|
1969
|
+
if (fromTitleCodes.size > 0) {
|
|
1970
|
+
for (const code of fromTitleCodes) {
|
|
1971
|
+
const normalized = this.toRequirementKey(code);
|
|
1972
|
+
if (normalized) targetSet.add(normalized);
|
|
1973
|
+
}
|
|
1974
|
+
} else {
|
|
1975
|
+
const normalized = this.toRequirementKey(title);
|
|
1976
|
+
if (normalized) targetSet.add(normalized);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (targetSet.size > 0) {
|
|
1980
|
+
titleDerivedCount += 1;
|
|
1981
|
+
}
|
|
1982
|
+
out.set(l2BaseKey, targetSet);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
if (relatedIds.size === 0) {
|
|
1986
|
+
const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
|
|
1987
|
+
logger.info(
|
|
1988
|
+
`MEWP L2->L1 mapping summary: l2Families=${out.size} ` +
|
|
1989
|
+
`fromTitle=${titleDerivedCount} fallbackFromLinkedL1=0 linkedL1Keys=${linkedL1Count}`
|
|
1990
|
+
);
|
|
1991
|
+
return out;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
const relatedWorkItems = await this.fetchWorkItemsByIds(projectName, [...relatedIds], false);
|
|
1995
|
+
const l1BaseByWorkItemId = new Map<number, string>();
|
|
1996
|
+
for (const workItem of relatedWorkItems || []) {
|
|
1997
|
+
const workItemId = Number(workItem?.id || 0);
|
|
1998
|
+
if (!Number.isFinite(workItemId) || workItemId <= 0) continue;
|
|
1999
|
+
const fields = workItem?.fields || {};
|
|
2000
|
+
const customerId = this.extractMewpRequirementIdentifier(fields);
|
|
2001
|
+
const baseKey = this.toRequirementKey(customerId);
|
|
2002
|
+
if (!baseKey) continue;
|
|
2003
|
+
l1BaseByWorkItemId.set(workItemId, baseKey);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
let linkedFallbackCount = 0;
|
|
2007
|
+
for (const requirement of requirements || []) {
|
|
2008
|
+
const l2BaseKey = String(requirement?.baseKey || '').trim();
|
|
2009
|
+
if (!l2BaseKey) continue;
|
|
2010
|
+
if (!out.has(l2BaseKey)) out.set(l2BaseKey, new Set<string>());
|
|
2011
|
+
const targetSet = out.get(l2BaseKey)!;
|
|
2012
|
+
if (targetSet.size > 0) {
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
for (const relatedId of requirement?.relatedWorkItemIds || []) {
|
|
2016
|
+
const baseKey = l1BaseByWorkItemId.get(Number(relatedId));
|
|
2017
|
+
if (baseKey) targetSet.add(baseKey);
|
|
2018
|
+
}
|
|
2019
|
+
if (targetSet.size > 0) {
|
|
2020
|
+
linkedFallbackCount += 1;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
const l2Families = out.size;
|
|
2025
|
+
const withLinkedL1 = [...out.values()].filter((set) => set.size > 0).length;
|
|
2026
|
+
const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
|
|
2027
|
+
logger.info(
|
|
2028
|
+
`MEWP L2->L1 mapping summary: l2Families=${l2Families} withLinkedL1=${withLinkedL1} ` +
|
|
2029
|
+
`fromTitle=${titleDerivedCount} fallbackFromLinkedL1=${linkedFallbackCount} linkedL1Keys=${linkedL1Count}`
|
|
2030
|
+
);
|
|
2031
|
+
return out;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
private buildMewpExternalJoinKeysByL2Requirement(
|
|
2035
|
+
requirements: MewpL2RequirementFamily[],
|
|
2036
|
+
l2ToLinkedL1BaseKeys: Map<string, Set<string>>
|
|
2037
|
+
): Map<string, Set<string>> {
|
|
2038
|
+
const out = new Map<string, Set<string>>();
|
|
2039
|
+
for (const requirement of requirements || []) {
|
|
2040
|
+
const l2BaseKey = String(requirement?.baseKey || '').trim();
|
|
2041
|
+
if (!l2BaseKey) continue;
|
|
2042
|
+
const joinKeys = new Set<string>([l2BaseKey]);
|
|
2043
|
+
for (const l1Key of l2ToLinkedL1BaseKeys.get(l2BaseKey) || []) {
|
|
2044
|
+
const normalized = String(l1Key || '').trim();
|
|
2045
|
+
if (normalized) joinKeys.add(normalized);
|
|
2046
|
+
}
|
|
2047
|
+
out.set(l2BaseKey, joinKeys);
|
|
2048
|
+
}
|
|
2049
|
+
return out;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1887
2052
|
private buildRequirementFamilyMap(
|
|
1888
2053
|
requirements: Array<Pick<MewpL2RequirementWorkItem, 'requirementId' | 'baseKey'>>,
|
|
1889
2054
|
scopedRequirementKeys?: Set<string>
|
|
@@ -1341,7 +1341,7 @@ describe('ResultDataProvider', () => {
|
|
|
1341
1341
|
expect(il).toBe('IL');
|
|
1342
1342
|
});
|
|
1343
1343
|
|
|
1344
|
-
it('should
|
|
1344
|
+
it('should zip bug rows with L3/L4 pairs and avoid cross-product duplication', () => {
|
|
1345
1345
|
const requirements = [
|
|
1346
1346
|
{
|
|
1347
1347
|
requirementId: 'SR5303',
|
|
@@ -1398,8 +1398,7 @@ describe('ResultDataProvider', () => {
|
|
|
1398
1398
|
[
|
|
1399
1399
|
'SR5303',
|
|
1400
1400
|
[
|
|
1401
|
-
{
|
|
1402
|
-
{ id: '9103', title: 'L4 9103', level: 'L4' as const },
|
|
1401
|
+
{ l3Id: '9003', l3Title: 'L3 9003', l4Id: '9103', l4Title: 'L4 9103' },
|
|
1403
1402
|
],
|
|
1404
1403
|
],
|
|
1405
1404
|
]);
|
|
@@ -1413,18 +1412,18 @@ describe('ResultDataProvider', () => {
|
|
|
1413
1412
|
externalBugsByTestCase
|
|
1414
1413
|
);
|
|
1415
1414
|
|
|
1416
|
-
expect(rows).toHaveLength(
|
|
1417
|
-
expect(rows.map((row: any) => row['Bug ID'])).toEqual([10003, 20003
|
|
1418
|
-
expect(rows[
|
|
1415
|
+
expect(rows).toHaveLength(2);
|
|
1416
|
+
expect(rows.map((row: any) => row['Bug ID'])).toEqual([10003, 20003]);
|
|
1417
|
+
expect(rows[0]).toEqual(
|
|
1419
1418
|
expect.objectContaining({
|
|
1420
1419
|
'L3 REQ ID': '9003',
|
|
1421
|
-
'L4 REQ ID': '',
|
|
1420
|
+
'L4 REQ ID': '9103',
|
|
1422
1421
|
})
|
|
1423
1422
|
);
|
|
1424
|
-
expect(rows[
|
|
1423
|
+
expect(rows[1]).toEqual(
|
|
1425
1424
|
expect.objectContaining({
|
|
1426
1425
|
'L3 REQ ID': '',
|
|
1427
|
-
'L4 REQ ID': '
|
|
1426
|
+
'L4 REQ ID': '',
|
|
1428
1427
|
})
|
|
1429
1428
|
);
|
|
1430
1429
|
});
|
|
@@ -2561,8 +2560,29 @@ describe('ResultDataProvider', () => {
|
|
|
2561
2560
|
|
|
2562
2561
|
const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(validL3L4Source);
|
|
2563
2562
|
expect(map.get('SR0001')).toEqual([
|
|
2564
|
-
{
|
|
2565
|
-
{
|
|
2563
|
+
{ l3Id: '', l3Title: '', l4Id: '7001', l4Title: 'L4 From Level3 Column' },
|
|
2564
|
+
{ l3Id: '7002', l3Title: 'L3 Requirement', l4Id: '', l4Title: '' },
|
|
2565
|
+
]);
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2568
|
+
it('should emit paired L3+L4 when AREA 34 is Level 4 and both level columns are present', async () => {
|
|
2569
|
+
const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
|
|
2570
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2571
|
+
{
|
|
2572
|
+
SR: 'SR0001',
|
|
2573
|
+
'AREA 34': 'Level 4',
|
|
2574
|
+
'TargetWorkItemId Level 3': '7401',
|
|
2575
|
+
TargetTitleLevel3: 'L3 In Level4 Row',
|
|
2576
|
+
'TargetStateLevel 3': 'Active',
|
|
2577
|
+
'TargetWorkItemIdLevel 4': '8401',
|
|
2578
|
+
TargetTitleLevel4: 'L4 In Level4 Row',
|
|
2579
|
+
'TargetStateLevel 4': 'Active',
|
|
2580
|
+
},
|
|
2581
|
+
]);
|
|
2582
|
+
|
|
2583
|
+
const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(validL3L4Source);
|
|
2584
|
+
expect(map.get('SR0001')).toEqual([
|
|
2585
|
+
{ l3Id: '7401', l3Title: 'L3 In Level4 Row', l4Id: '8401', l4Title: 'L4 In Level4 Row' },
|
|
2566
2586
|
]);
|
|
2567
2587
|
});
|
|
2568
2588
|
|
|
@@ -2593,8 +2613,8 @@ describe('ResultDataProvider', () => {
|
|
|
2593
2613
|
|
|
2594
2614
|
const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(validL3L4Source);
|
|
2595
2615
|
expect(map.get('SR0001')).toEqual([
|
|
2596
|
-
{
|
|
2597
|
-
{
|
|
2616
|
+
{ l3Id: '', l3Title: '', l4Id: '7201', l4Title: 'L4 IL' },
|
|
2617
|
+
{ l3Id: '7102', l3Title: 'L3 IL', l4Id: '', l4Title: '' },
|
|
2598
2618
|
]);
|
|
2599
2619
|
});
|
|
2600
2620
|
|
|
@@ -2628,7 +2648,9 @@ describe('ResultDataProvider', () => {
|
|
|
2628
2648
|
);
|
|
2629
2649
|
|
|
2630
2650
|
expect(map.has('SR0001')).toBe(false);
|
|
2631
|
-
expect(map.get('SR0002')).toEqual([
|
|
2651
|
+
expect(map.get('SR0002')).toEqual([
|
|
2652
|
+
{ l3Id: '7302', l3Title: 'L3 From IL Requirement', l4Id: '', l4Title: '' },
|
|
2653
|
+
]);
|
|
2632
2654
|
});
|
|
2633
2655
|
|
|
2634
2656
|
it('should resolve bug responsibility from AreaPath when SAPWBS is empty', () => {
|