@elisra-devops/docgen-data-provider 1.84.0 → 1.86.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.
@@ -88,6 +88,7 @@ export default class ResultDataProvider {
88
88
  private buildMewpCoverageL3L4Rows;
89
89
  private buildMewpCoverageRows;
90
90
  private resolveCoverageBugResponsibility;
91
+ private buildMewpTestCaseResponsibilityMap;
91
92
  private resolveMewpL2RunStatus;
92
93
  private fetchMewpScopedTestData;
93
94
  private extractRelNumberFromSuite;
@@ -134,6 +135,7 @@ export default class ResultDataProvider {
134
135
  private extractLinkedWorkItemIdsFromRelations;
135
136
  private extractMewpRequirementIdentifier;
136
137
  private deriveMewpResponsibility;
138
+ private deriveMewpTestCaseResponsibility;
137
139
  private deriveMewpSubSystem;
138
140
  private resolveBugResponsibility;
139
141
  private resolveMewpResponsibility;
@@ -306,6 +306,7 @@ class ResultDataProvider {
306
306
  const requirements = this.collapseMewpRequirementFamilies(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
307
307
  const l2ToLinkedL1BaseKeys = await this.buildMewpL2ToLinkedL1BaseKeys(allRequirements, projectName, testData);
308
308
  const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
309
+ const testCaseResponsibilityById = await this.buildMewpTestCaseResponsibilityMap(testData, projectName);
309
310
  const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options === null || options === void 0 ? void 0 : options.externalBugsFile);
310
311
  const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(options === null || options === void 0 ? void 0 : options.externalL3L4File, requirementSapWbsByBaseKey);
311
312
  const hasExternalBugsFile = !!String(((_a = options === null || options === void 0 ? void 0 : options.externalBugsFile) === null || _a === void 0 ? void 0 : _a.name) ||
@@ -442,7 +443,7 @@ class ResultDataProvider {
442
443
  logger_1.default.warn(`MEWP coverage join diagnostics: no overlap between external bug test cases and failed test cases. ` +
443
444
  `Bug rows remain empty because bugs are shown only for failed L2s.`);
444
445
  }
445
- const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2);
446
+ const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2, testCaseResponsibilityById);
446
447
  const coverageRowStats = rows.reduce((acc, row) => {
447
448
  const hasBug = Number((row === null || row === void 0 ? void 0 : row['Bug ID']) || 0) > 0;
448
449
  const hasL3 = String((row === null || row === void 0 ? void 0 : row['L3 REQ ID']) || '').trim() !== '';
@@ -557,34 +558,56 @@ class ResultDataProvider {
557
558
  diagnostics.testCasesWithoutMentionedCustomerIds += 1;
558
559
  }
559
560
  const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
560
- const expectedFamilyCodes = new Set();
561
- for (const baseKey of mentionedBaseKeys) {
562
- const familyCodes = requirementFamilies.get(baseKey);
563
- if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
564
- familyCodes.forEach((code) => expectedFamilyCodes.add(code));
565
- }
566
- else {
567
- for (const code of mentionedL2Only) {
568
- if (this.toRequirementKey(code) === baseKey)
569
- expectedFamilyCodes.add(code);
570
- }
571
- }
572
- }
573
561
  const linkedFullCodesRaw = ((_c = linkedRequirementsByTestCase.get(testCaseId)) === null || _c === void 0 ? void 0 : _c.fullCodes) || new Set();
574
562
  const linkedFullCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && linkedFullCodesRaw.size > 0
575
563
  ? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
576
564
  : linkedFullCodesRaw;
577
565
  const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
578
- const missingMentioned = [...mentionedL2Only].filter((code) => {
566
+ const mentionedCodesByBase = new Map();
567
+ for (const code of mentionedL2Only) {
579
568
  const baseKey = this.toRequirementKey(code);
580
569
  if (!baseKey)
581
- return false;
582
- const hasSpecificSuffix = /-\d+$/.test(code);
583
- if (hasSpecificSuffix)
584
- return !linkedFullCodes.has(code);
585
- return !linkedBaseKeys.has(baseKey);
586
- });
587
- const missingFamily = [...expectedFamilyCodes].filter((code) => !linkedFullCodes.has(code));
570
+ continue;
571
+ if (!mentionedCodesByBase.has(baseKey))
572
+ mentionedCodesByBase.set(baseKey, new Set());
573
+ mentionedCodesByBase.get(baseKey).add(code);
574
+ }
575
+ // Context-based direction A logic:
576
+ // 1) If no member of family is linked -> report only base SR (Step X: SR0054).
577
+ // 2) If family is partially linked -> report only specific missing members.
578
+ // 3) If family fully linked -> report nothing for that family.
579
+ const missingBaseWhenFamilyUncovered = new Set();
580
+ const missingSpecificMentionedNoFamily = new Set();
581
+ const missingFamilyMembers = new Set();
582
+ for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
583
+ const familyCodes = requirementFamilies.get(baseKey);
584
+ if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
585
+ const missingInFamily = [...familyCodes].filter((code) => !linkedFullCodes.has(code));
586
+ if (missingInFamily.length === 0)
587
+ continue;
588
+ const linkedInFamilyCount = familyCodes.size - missingInFamily.length;
589
+ if (linkedInFamilyCount === 0) {
590
+ missingBaseWhenFamilyUncovered.add(baseKey);
591
+ }
592
+ else {
593
+ for (const code of missingInFamily) {
594
+ missingFamilyMembers.add(code);
595
+ }
596
+ }
597
+ continue;
598
+ }
599
+ // Fallback path when family data is unavailable for this base key.
600
+ for (const code of mentionedCodes) {
601
+ const hasSpecificSuffix = /-\d+$/.test(code);
602
+ if (hasSpecificSuffix) {
603
+ if (!linkedFullCodes.has(code))
604
+ missingSpecificMentionedNoFamily.add(code);
605
+ }
606
+ else if (!linkedBaseKeys.has(baseKey)) {
607
+ missingBaseWhenFamilyUncovered.add(baseKey);
608
+ }
609
+ }
610
+ }
588
611
  // Direction B is family-based: if any member of a family is mentioned in Expected Result,
589
612
  // linked members of that same family are not considered "linked but not mentioned".
590
613
  const extraLinked = [...linkedFullCodes].filter((code) => {
@@ -604,13 +627,18 @@ class ResultDataProvider {
604
627
  }
605
628
  mentionedButNotLinkedByStep.get(normalizedStepRef).add(normalizedRequirementId);
606
629
  };
607
- const sortedMissingMentioned = [...new Set(missingMentioned)].sort((a, b) => a.localeCompare(b));
608
- const sortedMissingFamily = [...new Set(missingFamily)].sort((a, b) => a.localeCompare(b));
609
- for (const code of sortedMissingMentioned) {
630
+ const sortedMissingSpecificMentionedNoFamily = [...missingSpecificMentionedNoFamily].sort((a, b) => a.localeCompare(b));
631
+ const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) => a.localeCompare(b));
632
+ const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
633
+ for (const code of sortedMissingSpecificMentionedNoFamily) {
610
634
  const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
611
635
  appendMentionedButNotLinked(code, stepRef);
612
636
  }
613
- for (const code of sortedMissingFamily) {
637
+ for (const baseKey of sortedMissingBaseWhenFamilyUncovered) {
638
+ const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
639
+ appendMentionedButNotLinked(baseKey, stepRef);
640
+ }
641
+ for (const code of sortedMissingFamilyMembers) {
614
642
  const baseKey = this.toRequirementKey(code);
615
643
  const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
616
644
  appendMentionedButNotLinked(code, stepRef);
@@ -644,7 +672,9 @@ class ResultDataProvider {
644
672
  logger_1.default.debug(`MEWP internal validation parse diagnostics: ` +
645
673
  `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
646
674
  `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
647
- `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingMentioned.length + sortedMissingFamily.length} ` +
675
+ `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingSpecificMentionedNoFamily.length +
676
+ sortedMissingBaseWhenFamilyUncovered.length +
677
+ sortedMissingFamilyMembers.length} ` +
648
678
  `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
649
679
  `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
650
680
  rows.push({
@@ -813,7 +843,7 @@ class ResultDataProvider {
813
843
  return String((a === null || a === void 0 ? void 0 : a.l4Id) || '').localeCompare(String((b === null || b === void 0 ? void 0 : b.l4Id) || ''));
814
844
  });
815
845
  }
816
- buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2) {
846
+ buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2, testCaseResponsibilityById) {
817
847
  var _a;
818
848
  const rows = [];
819
849
  const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
@@ -847,7 +877,7 @@ class ResultDataProvider {
847
877
  const bugId = Number((bug === null || bug === void 0 ? void 0 : bug.id) || 0);
848
878
  if (!Number.isFinite(bugId) || bugId <= 0)
849
879
  continue;
850
- aggregatedBugs.set(bugId, Object.assign(Object.assign({}, bug), { responsibility: this.resolveCoverageBugResponsibility(String((bug === null || bug === void 0 ? void 0 : bug.responsibility) || ''), requirement) }));
880
+ aggregatedBugs.set(bugId, Object.assign(Object.assign({}, bug), { responsibility: this.resolveCoverageBugResponsibility(String((bug === null || bug === void 0 ? void 0 : bug.responsibility) || ''), requirement, String((testCaseResponsibilityById === null || testCaseResponsibilityById === void 0 ? void 0 : testCaseResponsibilityById.get(testCaseId)) || '')) }));
851
881
  }
852
882
  }
853
883
  }
@@ -874,18 +904,114 @@ class ResultDataProvider {
874
904
  }
875
905
  return rows;
876
906
  }
877
- resolveCoverageBugResponsibility(rawResponsibility, requirement) {
878
- const direct = String(rawResponsibility || '').trim();
907
+ resolveCoverageBugResponsibility(rawResponsibility, requirement, testCaseResponsibility = '') {
908
+ const normalizeDisplay = (value) => {
909
+ const direct = String(value || '').trim();
910
+ if (!direct)
911
+ return '';
912
+ const resolved = this.resolveMewpResponsibility(this.toMewpComparableText(direct));
913
+ if (resolved === 'ESUK')
914
+ return 'ESUK';
915
+ if (resolved === 'IL')
916
+ return 'Elisra';
917
+ return direct;
918
+ };
919
+ const direct = normalizeDisplay(rawResponsibility);
879
920
  if (direct && direct.toLowerCase() !== 'unknown')
880
921
  return direct;
881
- const requirementResponsibility = String((requirement === null || requirement === void 0 ? void 0 : requirement.responsibility) || '')
882
- .trim()
883
- .toUpperCase();
884
- if (requirementResponsibility === 'ESUK')
885
- return 'ESUK';
886
- if (requirementResponsibility === 'IL' || requirementResponsibility === 'ELISRA')
887
- return 'Elisra';
888
- return direct || 'Unknown';
922
+ const fromTestCase = normalizeDisplay(testCaseResponsibility);
923
+ if (fromTestCase && fromTestCase.toLowerCase() !== 'unknown')
924
+ return fromTestCase;
925
+ const fromRequirement = normalizeDisplay(String((requirement === null || requirement === void 0 ? void 0 : requirement.responsibility) || ''));
926
+ if (fromRequirement && fromRequirement.toLowerCase() !== 'unknown')
927
+ return fromRequirement;
928
+ return direct || fromTestCase || fromRequirement || 'Unknown';
929
+ }
930
+ async buildMewpTestCaseResponsibilityMap(testData, projectName) {
931
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
932
+ const out = new Map();
933
+ const unresolved = new Set();
934
+ const extractFromWorkItemFields = (workItemFields) => {
935
+ const fields = {};
936
+ if (!Array.isArray(workItemFields))
937
+ return fields;
938
+ for (const field of workItemFields) {
939
+ const keyCandidates = [field === null || field === void 0 ? void 0 : field.key, field === null || field === void 0 ? void 0 : field.name, field === null || field === void 0 ? void 0 : field.referenceName]
940
+ .map((item) => String(item || '').trim())
941
+ .filter((item) => !!item);
942
+ for (const key of keyCandidates) {
943
+ fields[key] = field === null || field === void 0 ? void 0 : field.value;
944
+ }
945
+ }
946
+ return fields;
947
+ };
948
+ for (const suite of testData || []) {
949
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
950
+ for (const testCase of testCasesItems) {
951
+ const testCaseId = Number(((_a = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCase === null || testCase === void 0 ? void 0 : testCase.testCaseId) || (testCase === null || testCase === void 0 ? void 0 : testCase.id) || 0);
952
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId))
953
+ continue;
954
+ const workItemFieldsMap = ((_b = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _b === void 0 ? void 0 : _b.fields) || {};
955
+ const workItemFieldsList = extractFromWorkItemFields((_c = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _c === void 0 ? void 0 : _c.workItemFields);
956
+ const directFieldAliases = Object.fromEntries(Object.entries({
957
+ 'System.AreaPath': ((_d = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _d === void 0 ? void 0 : _d.areaPath) ||
958
+ ((_e = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _e === void 0 ? void 0 : _e.AreaPath) ||
959
+ (testCase === null || testCase === void 0 ? void 0 : testCase.areaPath) ||
960
+ (testCase === null || testCase === void 0 ? void 0 : testCase.AreaPath) ||
961
+ ((_f = testCase === null || testCase === void 0 ? void 0 : testCase.testCase) === null || _f === void 0 ? void 0 : _f.areaPath) ||
962
+ ((_g = testCase === null || testCase === void 0 ? void 0 : testCase.testCase) === null || _g === void 0 ? void 0 : _g.AreaPath),
963
+ 'Area Path': ((_h = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _h === void 0 ? void 0 : _h['Area Path']) ||
964
+ (testCase === null || testCase === void 0 ? void 0 : testCase['Area Path']) ||
965
+ ((_j = testCase === null || testCase === void 0 ? void 0 : testCase.testCase) === null || _j === void 0 ? void 0 : _j['Area Path']),
966
+ AreaPath: ((_k = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _k === void 0 ? void 0 : _k.AreaPath) ||
967
+ (testCase === null || testCase === void 0 ? void 0 : testCase.AreaPath) ||
968
+ ((_l = testCase === null || testCase === void 0 ? void 0 : testCase.testCase) === null || _l === void 0 ? void 0 : _l.AreaPath),
969
+ }).filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== ''));
970
+ const fromWorkItem = this.deriveMewpTestCaseResponsibility(Object.assign(Object.assign(Object.assign({}, directFieldAliases), workItemFieldsList), workItemFieldsMap));
971
+ if (fromWorkItem) {
972
+ out.set(testCaseId, fromWorkItem);
973
+ }
974
+ else {
975
+ unresolved.add(testCaseId);
976
+ }
977
+ }
978
+ const testPointsItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testPointsItems) ? suite.testPointsItems : [];
979
+ for (const testPoint of testPointsItems) {
980
+ const testCaseId = Number((testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCaseId) || ((_m = testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCase) === null || _m === void 0 ? void 0 : _m.id) || 0);
981
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId))
982
+ continue;
983
+ const fromPoint = this.deriveMewpTestCaseResponsibility({
984
+ 'System.AreaPath': (testPoint === null || testPoint === void 0 ? void 0 : testPoint.areaPath) || (testPoint === null || testPoint === void 0 ? void 0 : testPoint.AreaPath),
985
+ 'Area Path': testPoint === null || testPoint === void 0 ? void 0 : testPoint['Area Path'],
986
+ AreaPath: testPoint === null || testPoint === void 0 ? void 0 : testPoint.AreaPath,
987
+ });
988
+ if (fromPoint) {
989
+ out.set(testCaseId, fromPoint);
990
+ unresolved.delete(testCaseId);
991
+ }
992
+ else {
993
+ unresolved.add(testCaseId);
994
+ }
995
+ }
996
+ }
997
+ if (unresolved.size > 0) {
998
+ try {
999
+ const workItems = await this.fetchWorkItemsByIds(projectName, [...unresolved], false);
1000
+ for (const workItem of workItems || []) {
1001
+ const testCaseId = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1002
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId))
1003
+ continue;
1004
+ const resolved = this.deriveMewpTestCaseResponsibility((workItem === null || workItem === void 0 ? void 0 : workItem.fields) || {});
1005
+ if (!resolved)
1006
+ continue;
1007
+ out.set(testCaseId, resolved);
1008
+ }
1009
+ }
1010
+ catch (error) {
1011
+ logger_1.default.warn(`MEWP coverage: failed to enrich test-case responsibility fallback: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
1012
+ }
1013
+ }
1014
+ return out;
889
1015
  }
890
1016
  resolveMewpL2RunStatus(input) {
891
1017
  if (((input === null || input === void 0 ? void 0 : input.failed) || 0) > 0)
@@ -1998,11 +2124,42 @@ class ResultDataProvider {
1998
2124
  return fromExplicitLabel;
1999
2125
  if (explicitSapWbsByLabel)
2000
2126
  return explicitSapWbsByLabel;
2001
- const areaPath = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.AreaPath']);
2002
- const fromAreaPath = this.resolveMewpResponsibility(areaPath);
2003
- if (fromAreaPath)
2004
- return fromAreaPath;
2005
- const keyHints = ['sapwbs', 'responsibility', 'owner'];
2127
+ const areaPathCandidates = [
2128
+ fields === null || fields === void 0 ? void 0 : fields['System.AreaPath'],
2129
+ fields === null || fields === void 0 ? void 0 : fields['Area Path'],
2130
+ fields === null || fields === void 0 ? void 0 : fields['AreaPath'],
2131
+ ];
2132
+ for (const candidate of areaPathCandidates) {
2133
+ const normalized = this.toMewpComparableText(candidate);
2134
+ const resolved = this.resolveMewpResponsibility(normalized);
2135
+ if (resolved)
2136
+ return resolved;
2137
+ }
2138
+ const keyHints = ['sapwbs', 'responsibility', 'owner', 'areapath', 'area path'];
2139
+ for (const [key, value] of Object.entries(fields || {})) {
2140
+ const normalizedKey = String(key || '').toLowerCase();
2141
+ if (!keyHints.some((hint) => normalizedKey.includes(hint)))
2142
+ continue;
2143
+ const resolved = this.resolveMewpResponsibility(this.toMewpComparableText(value));
2144
+ if (resolved)
2145
+ return resolved;
2146
+ }
2147
+ return '';
2148
+ }
2149
+ // Test-case responsibility must come from test-case path context (not SAPWBS).
2150
+ deriveMewpTestCaseResponsibility(fields) {
2151
+ const areaPathCandidates = [
2152
+ fields === null || fields === void 0 ? void 0 : fields['System.AreaPath'],
2153
+ fields === null || fields === void 0 ? void 0 : fields['Area Path'],
2154
+ fields === null || fields === void 0 ? void 0 : fields['AreaPath'],
2155
+ ];
2156
+ for (const candidate of areaPathCandidates) {
2157
+ const normalized = this.toMewpComparableText(candidate);
2158
+ const resolved = this.resolveMewpResponsibility(normalized);
2159
+ if (resolved)
2160
+ return resolved;
2161
+ }
2162
+ const keyHints = ['areapath', 'area path', 'responsibility', 'owner'];
2006
2163
  for (const [key, value] of Object.entries(fields || {})) {
2007
2164
  const normalizedKey = String(key || '').toLowerCase();
2008
2165
  if (!keyHints.some((hint) => normalizedKey.includes(hint)))
@@ -2404,7 +2561,6 @@ class ResultDataProvider {
2404
2561
  logger_1.default.warn(`Invalid run result ${runId} or result ${resultId}`);
2405
2562
  return null;
2406
2563
  }
2407
- logger_1.default.warn(`Current Test point for Test case ${point.testCaseId} is in Active state`);
2408
2564
  const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${point.testCaseId}?$expand=all`;
2409
2565
  const testCaseData = await tfs_1.TFSServices.getItemContent(url, this.token);
2410
2566
  const newResultData = {