@elisra-devops/docgen-data-provider 1.79.0 → 1.80.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.
@@ -118,6 +118,8 @@ export default class ResultDataProvider {
118
118
  private fetchMewpL2Requirements;
119
119
  private isMewpL2AreaPath;
120
120
  private collapseMewpRequirementFamilies;
121
+ private buildMewpL2ToLinkedL1BaseKeys;
122
+ private buildMewpExternalJoinKeysByL2Requirement;
121
123
  private buildRequirementFamilyMap;
122
124
  private buildLinkedRequirementsByTestCase;
123
125
  private resolveMewpRequirementScopeKeysFromQuery;
@@ -304,6 +304,7 @@ class ResultDataProvider {
304
304
  const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
305
305
  const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
306
306
  const requirements = this.collapseMewpRequirementFamilies(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
307
+ const l2ToLinkedL1BaseKeys = await this.buildMewpL2ToLinkedL1BaseKeys(allRequirements, projectName, testData);
307
308
  const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
308
309
  const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options === null || options === void 0 ? void 0 : options.externalBugsFile);
309
310
  const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(options === null || options === void 0 ? void 0 : options.externalL3L4File, requirementSapWbsByBaseKey);
@@ -333,6 +334,7 @@ class ResultDataProvider {
333
334
  if (requirements.length === 0) {
334
335
  return Object.assign(Object.assign({}, defaultPayload), { sheetName: this.buildMewpCoverageSheetName(planName, testPlanId) });
335
336
  }
337
+ const externalJoinKeysByL2 = this.buildMewpExternalJoinKeysByL2Requirement(requirements, l2ToLinkedL1BaseKeys);
336
338
  const requirementIndex = new Map();
337
339
  const observedTestCaseIdsByRequirement = new Map();
338
340
  const requirementKeys = new Set();
@@ -382,8 +384,15 @@ class ResultDataProvider {
382
384
  this.accumulateRequirementCountsFromActionResults(fallbackActionResults, testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
383
385
  }
384
386
  const requirementBaseKeys = new Set(requirements.map((item) => String((item === null || item === void 0 ? void 0 : item.baseKey) || '').trim()).filter((item) => !!item));
387
+ const externalJoinKeyUniverse = new Set();
388
+ for (const keySet of externalJoinKeysByL2.values()) {
389
+ for (const key of keySet) {
390
+ if (key)
391
+ externalJoinKeyUniverse.add(key);
392
+ }
393
+ }
385
394
  const externalL3L4BaseKeys = new Set([...externalL3L4ByBaseKey.keys()]);
386
- const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) => requirementBaseKeys.has(key));
395
+ const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
387
396
  const failedRequirementBaseKeys = new Set();
388
397
  const failedTestCaseIds = new Set();
389
398
  for (const [requirementBaseKey, byTestCase] of requirementIndex.entries()) {
@@ -404,9 +413,10 @@ class ResultDataProvider {
404
413
  externalBugBaseKeys.add(key);
405
414
  }
406
415
  }
407
- const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) => requirementBaseKeys.has(key));
408
- const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) => failedRequirementBaseKeys.has(key));
416
+ const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
417
+ const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
409
418
  logger_1.default.info(`MEWP coverage join diagnostics: requirementBaseKeys=${requirementBaseKeys.size} ` +
419
+ `externalJoinKeys=${externalJoinKeyUniverse.size} ` +
410
420
  `failedRequirementBaseKeys=${failedRequirementBaseKeys.size} failedTestCases=${failedTestCaseIds.size}; ` +
411
421
  `externalL3L4BaseKeys=${externalL3L4BaseKeys.size} externalL3L4Overlap=${externalL3L4OverlapKeys.length}; ` +
412
422
  `externalBugTestCases=${externalBugTestCaseIds.size} externalBugFailedTestCaseOverlap=${externalBugFailedTestCaseOverlap.length}; ` +
@@ -414,21 +424,25 @@ class ResultDataProvider {
414
424
  `externalBugFailedRequirementOverlap=${externalBugFailedRequirementOverlap.length}`);
415
425
  if (externalL3L4BaseKeys.size > 0 && externalL3L4OverlapKeys.length === 0) {
416
426
  const sampleReq = [...requirementBaseKeys].slice(0, 5);
427
+ const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
417
428
  const sampleExt = [...externalL3L4BaseKeys].slice(0, 5);
418
429
  logger_1.default.warn(`MEWP coverage join diagnostics: no L3/L4 key overlap found. ` +
419
- `sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalL3L4Keys=${sampleExt.join(', ')}`);
430
+ `sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
431
+ `sampleExternalL3L4Keys=${sampleExt.join(', ')}`);
420
432
  }
421
433
  if (externalBugBaseKeys.size > 0 && externalBugRequirementOverlap.length === 0) {
422
434
  const sampleReq = [...requirementBaseKeys].slice(0, 5);
435
+ const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
423
436
  const sampleExt = [...externalBugBaseKeys].slice(0, 5);
424
437
  logger_1.default.warn(`MEWP coverage join diagnostics: no bug requirement-key overlap found. ` +
425
- `sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalBugKeys=${sampleExt.join(', ')}`);
438
+ `sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
439
+ `sampleExternalBugKeys=${sampleExt.join(', ')}`);
426
440
  }
427
441
  if (externalBugTestCaseIds.size > 0 && externalBugFailedTestCaseOverlap.length === 0) {
428
442
  logger_1.default.warn(`MEWP coverage join diagnostics: no overlap between external bug test cases and failed test cases. ` +
429
443
  `Bug rows remain empty because bugs are shown only for failed L2s.`);
430
444
  }
431
- const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase);
445
+ const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2);
432
446
  const coverageRowStats = rows.reduce((acc, row) => {
433
447
  const hasBug = Number((row === null || row === void 0 ? void 0 : row['Bug ID']) || 0) > 0;
434
448
  const hasL3 = String((row === null || row === void 0 ? void 0 : row['L3 REQ ID']) || '').trim() !== '';
@@ -493,12 +507,24 @@ class ResultDataProvider {
493
507
  }
494
508
  }
495
509
  const validL2BaseKeys = new Set([...requirementFamilies.keys()]);
510
+ const diagnostics = {
511
+ totalTestCases: 0,
512
+ totalParsedSteps: 0,
513
+ totalStepsWithMentions: 0,
514
+ totalMentionedCustomerIds: 0,
515
+ testCasesWithoutMentionedCustomerIds: 0,
516
+ failingRows: 0,
517
+ };
496
518
  for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
519
+ diagnostics.totalTestCases += 1;
497
520
  const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
498
521
  const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
499
522
  ? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
500
523
  : [];
524
+ const executableSteps = parsedSteps.filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle));
525
+ diagnostics.totalParsedSteps += executableSteps.length;
501
526
  const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
527
+ diagnostics.totalStepsWithMentions += mentionEntries.length;
502
528
  const mentionedL2Only = new Set();
503
529
  const mentionedCodeFirstStep = new Map();
504
530
  const mentionedBaseFirstStep = new Map();
@@ -521,6 +547,10 @@ class ResultDataProvider {
521
547
  }
522
548
  }
523
549
  }
550
+ diagnostics.totalMentionedCustomerIds += mentionedL2Only.size;
551
+ if (mentionedL2Only.size === 0) {
552
+ diagnostics.testCasesWithoutMentionedCustomerIds += 1;
553
+ }
524
554
  const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
525
555
  const expectedFamilyCodes = new Set();
526
556
  for (const baseKey of mentionedBaseKeys) {
@@ -597,6 +627,14 @@ class ResultDataProvider {
597
627
  .join('; ');
598
628
  const linkedButNotMentioned = sortedExtraLinked.join('; ');
599
629
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
630
+ if (validationStatus === 'Fail')
631
+ diagnostics.failingRows += 1;
632
+ logger_1.default.debug(`MEWP internal validation parse diagnostics: ` +
633
+ `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
634
+ `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
635
+ `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingMentioned.length + sortedMissingFamily.length} ` +
636
+ `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
637
+ `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
600
638
  rows.push({
601
639
  'Test Case ID': testCaseId,
602
640
  'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
@@ -605,6 +643,11 @@ class ResultDataProvider {
605
643
  'Validation Status': validationStatus,
606
644
  });
607
645
  }
646
+ logger_1.default.info(`MEWP internal validation summary: testCases=${diagnostics.totalTestCases} ` +
647
+ `parsedSteps=${diagnostics.totalParsedSteps} stepsWithMentions=${diagnostics.totalStepsWithMentions} ` +
648
+ `totalCustomerIdsFound=${diagnostics.totalMentionedCustomerIds} ` +
649
+ `testCasesWithoutCustomerIds=${diagnostics.testCasesWithoutMentionedCustomerIds} ` +
650
+ `failingRows=${diagnostics.failingRows}`);
608
651
  return {
609
652
  sheetName: this.buildInternalValidationSheetName(planName, testPlanId),
610
653
  columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
@@ -719,7 +762,22 @@ class ResultDataProvider {
719
762
  return { l3Id: '', l3Title: '', l4Id: '', l4Title: '' };
720
763
  }
721
764
  buildMewpCoverageL3L4Rows(links) {
722
- const sorted = [...(links || [])].sort((a, b) => {
765
+ const deduped = new Map();
766
+ for (const item of links || []) {
767
+ const level = (item === null || item === void 0 ? void 0 : item.level) === 'L4' ? 'L4' : 'L3';
768
+ const id = String((item === null || item === void 0 ? void 0 : item.id) || '').trim();
769
+ if (!id)
770
+ continue;
771
+ const key = `${level}:${id}`;
772
+ if (!deduped.has(key)) {
773
+ deduped.set(key, {
774
+ id,
775
+ level,
776
+ title: String((item === null || item === void 0 ? void 0 : item.title) || '').trim(),
777
+ });
778
+ }
779
+ }
780
+ const sorted = [...deduped.values()].sort((a, b) => {
723
781
  if (a.level !== b.level)
724
782
  return a.level === 'L3' ? -1 : 1;
725
783
  return String(a.id || '').localeCompare(String(b.id || ''));
@@ -736,12 +794,14 @@ class ResultDataProvider {
736
794
  }
737
795
  return rows;
738
796
  }
739
- buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase) {
797
+ buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2) {
740
798
  var _a;
741
799
  const rows = [];
742
800
  const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
801
+ const joinKeysByRequirement = externalJoinKeysByL2 || new Map();
743
802
  for (const requirement of requirements) {
744
803
  const key = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || this.toRequirementKey(requirement.requirementId) || '').trim();
804
+ const externalJoinKeys = joinKeysByRequirement.get(key) || new Set([key]);
745
805
  const linkedTestCaseIds = ((requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []).filter((id) => Number.isFinite(id) && Number(id) > 0);
746
806
  const linkedByTestCase = key ? Array.from(linkedByRequirement.get(key) || []) : [];
747
807
  const observedTestCaseIds = key
@@ -763,7 +823,7 @@ class ResultDataProvider {
763
823
  const externalBugs = externalBugsByTestCase.get(testCaseId) || [];
764
824
  for (const bug of externalBugs) {
765
825
  const bugBaseKey = String((bug === null || bug === void 0 ? void 0 : bug.requirementBaseKey) || '').trim();
766
- if (bugBaseKey && bugBaseKey !== key)
826
+ if (bugBaseKey && !externalJoinKeys.has(bugBaseKey))
767
827
  continue;
768
828
  const bugId = Number((bug === null || bug === void 0 ? void 0 : bug.id) || 0);
769
829
  if (!Number.isFinite(bugId) || bugId <= 0)
@@ -781,7 +841,7 @@ class ResultDataProvider {
781
841
  const bugsForRows = runStatus === 'Fail'
782
842
  ? Array.from(aggregatedBugs.values()).sort((a, b) => a.id - b.id)
783
843
  : [];
784
- const l3l4ForRows = [...(l3l4ByBaseKey.get(key) || [])];
844
+ const l3l4ForRows = [...externalJoinKeys].flatMap((joinKey) => l3l4ByBaseKey.get(joinKey) || []);
785
845
  const bugRows = bugsForRows.length > 0
786
846
  ? bugsForRows
787
847
  : [];
@@ -1456,6 +1516,119 @@ class ResultDataProvider {
1456
1516
  })
1457
1517
  .sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
1458
1518
  }
1519
+ async buildMewpL2ToLinkedL1BaseKeys(requirements, projectName, testData) {
1520
+ const out = new Map();
1521
+ const relatedIds = new Set();
1522
+ const linkedTestCaseIdsByL2 = new Map();
1523
+ const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
1524
+ for (const requirement of requirements || []) {
1525
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1526
+ if (!l2BaseKey)
1527
+ continue;
1528
+ if (!out.has(l2BaseKey))
1529
+ out.set(l2BaseKey, new Set());
1530
+ if (!linkedTestCaseIdsByL2.has(l2BaseKey))
1531
+ linkedTestCaseIdsByL2.set(l2BaseKey, new Set());
1532
+ for (const testCaseId of (requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []) {
1533
+ const numeric = Number(testCaseId);
1534
+ if (Number.isFinite(numeric) && numeric > 0) {
1535
+ linkedTestCaseIdsByL2.get(l2BaseKey).add(numeric);
1536
+ }
1537
+ }
1538
+ for (const relatedId of (requirement === null || requirement === void 0 ? void 0 : requirement.relatedWorkItemIds) || []) {
1539
+ const id = Number(relatedId);
1540
+ if (Number.isFinite(id) && id > 0)
1541
+ relatedIds.add(id);
1542
+ }
1543
+ }
1544
+ let titleDerivedCount = 0;
1545
+ for (const [l2BaseKey, testCaseIds] of linkedTestCaseIdsByL2.entries()) {
1546
+ const targetSet = out.get(l2BaseKey) || new Set();
1547
+ for (const testCaseId of testCaseIds) {
1548
+ const title = String(testCaseTitleMap.get(testCaseId) || '').trim();
1549
+ if (!title)
1550
+ continue;
1551
+ const fromTitleCodes = this.extractRequirementCodesFromExpectedText(title, false);
1552
+ if (fromTitleCodes.size > 0) {
1553
+ for (const code of fromTitleCodes) {
1554
+ const normalized = this.toRequirementKey(code);
1555
+ if (normalized)
1556
+ targetSet.add(normalized);
1557
+ }
1558
+ }
1559
+ else {
1560
+ const normalized = this.toRequirementKey(title);
1561
+ if (normalized)
1562
+ targetSet.add(normalized);
1563
+ }
1564
+ }
1565
+ if (targetSet.size > 0) {
1566
+ titleDerivedCount += 1;
1567
+ }
1568
+ out.set(l2BaseKey, targetSet);
1569
+ }
1570
+ if (relatedIds.size === 0) {
1571
+ const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
1572
+ logger_1.default.info(`MEWP L2->L1 mapping summary: l2Families=${out.size} ` +
1573
+ `fromTitle=${titleDerivedCount} fallbackFromLinkedL1=0 linkedL1Keys=${linkedL1Count}`);
1574
+ return out;
1575
+ }
1576
+ const relatedWorkItems = await this.fetchWorkItemsByIds(projectName, [...relatedIds], false);
1577
+ const l1BaseByWorkItemId = new Map();
1578
+ for (const workItem of relatedWorkItems || []) {
1579
+ const workItemId = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1580
+ if (!Number.isFinite(workItemId) || workItemId <= 0)
1581
+ continue;
1582
+ const fields = (workItem === null || workItem === void 0 ? void 0 : workItem.fields) || {};
1583
+ const customerId = this.extractMewpRequirementIdentifier(fields);
1584
+ const baseKey = this.toRequirementKey(customerId);
1585
+ if (!baseKey)
1586
+ continue;
1587
+ l1BaseByWorkItemId.set(workItemId, baseKey);
1588
+ }
1589
+ let linkedFallbackCount = 0;
1590
+ for (const requirement of requirements || []) {
1591
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1592
+ if (!l2BaseKey)
1593
+ continue;
1594
+ if (!out.has(l2BaseKey))
1595
+ out.set(l2BaseKey, new Set());
1596
+ const targetSet = out.get(l2BaseKey);
1597
+ if (targetSet.size > 0) {
1598
+ continue;
1599
+ }
1600
+ for (const relatedId of (requirement === null || requirement === void 0 ? void 0 : requirement.relatedWorkItemIds) || []) {
1601
+ const baseKey = l1BaseByWorkItemId.get(Number(relatedId));
1602
+ if (baseKey)
1603
+ targetSet.add(baseKey);
1604
+ }
1605
+ if (targetSet.size > 0) {
1606
+ linkedFallbackCount += 1;
1607
+ }
1608
+ }
1609
+ const l2Families = out.size;
1610
+ const withLinkedL1 = [...out.values()].filter((set) => set.size > 0).length;
1611
+ const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
1612
+ logger_1.default.info(`MEWP L2->L1 mapping summary: l2Families=${l2Families} withLinkedL1=${withLinkedL1} ` +
1613
+ `fromTitle=${titleDerivedCount} fallbackFromLinkedL1=${linkedFallbackCount} linkedL1Keys=${linkedL1Count}`);
1614
+ return out;
1615
+ }
1616
+ buildMewpExternalJoinKeysByL2Requirement(requirements, l2ToLinkedL1BaseKeys) {
1617
+ const out = new Map();
1618
+ for (const requirement of requirements || []) {
1619
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1620
+ if (!l2BaseKey)
1621
+ continue;
1622
+ const joinKeys = new Set([l2BaseKey]);
1623
+ for (const l1Key of l2ToLinkedL1BaseKeys.get(l2BaseKey) || []) {
1624
+ const normalized = String(l1Key || '').trim();
1625
+ if (normalized)
1626
+ joinKeys.add(normalized);
1627
+ }
1628
+ out.set(l2BaseKey, joinKeys);
1629
+ }
1630
+ return out;
1631
+ }
1459
1632
  buildRequirementFamilyMap(requirements, scopedRequirementKeys) {
1460
1633
  const familyMap = new Map();
1461
1634
  for (const requirement of requirements || []) {