@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.
@@ -42,6 +42,7 @@ export interface MewpL2RequirementWorkItem {
42
42
  }
43
43
 
44
44
  export interface MewpL2RequirementFamily {
45
+ workItemId?: number;
45
46
  requirementId: string;
46
47
  baseKey: string;
47
48
  title: string;
@@ -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<MewpL2RequirementFamily, 'requirementId' | 'title' | 'subSystem' | 'responsibility'>,
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 l2ReqId = this.formatMewpCustomerId(requirement.requirementId);
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, Number(wi?.id || 0));
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>, fallbackWorkItemId: number): string {
2152
+ private extractMewpRequirementIdentifier(fields: Record<string, any>): string {
2094
2153
  const entries = Object.entries(fields || {});
2095
-
2096
- // First pass: only trusted identifier-like fields.
2097
- const strictHints = [
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
- 'customer id',
2100
- 'customerrequirementid',
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 = String(key || '').toLowerCase();
2120
- if (!looseHints.some((hint) => normalizedKey.includes(hint))) continue;
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
- // Optional fallback from title only (avoid scanning all fields and accidental SR matches).
2129
- const title = this.toMewpComparableText(fields?.['System.Title']);
2130
- const titleCode = this.normalizeMewpRequirementCodeWithSuffix(title);
2131
- if (titleCode) return titleCode;
2174
+ const normalizedRequirementId = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
2175
+ if (normalizedRequirementId) return normalizedRequirementId;
2176
+ }
2132
2177
 
2133
- return fallbackWorkItemId ? `SR${fallbackWorkItemId}` : '';
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'] === 'SR1001');
1182
- const inferredByStepText = result.rows.find((row: any) => row['L2 REQ ID'] === 'SR1002');
1183
- const uncovered = result.rows.find((row: any) => row['L2 REQ ID'] === 'SR1003');
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'] === 'SR2001');
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('SR4321');
1318
+ expect(requirementId).toBe('');
1320
1319
  });
1321
1320
 
1322
1321
  it('should derive responsibility from Custom.SAPWBS when present', () => {