@elisra-devops/docgen-data-provider 1.73.0 → 1.74.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/modules/ResultDataProvider.d.ts +11 -1
- package/bin/modules/ResultDataProvider.js +278 -37
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +113 -9
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +390 -38
- package/src/tests/modules/ResultDataProvider.test.ts +140 -9
|
@@ -30,14 +30,14 @@ const pLimit = require('p-limit');
|
|
|
30
30
|
*/
|
|
31
31
|
export default class ResultDataProvider {
|
|
32
32
|
private static readonly MEWP_L2_COVERAGE_COLUMNS = [
|
|
33
|
-
'Customer
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
33
|
+
'Customer ID',
|
|
34
|
+
'Title (Customer name)',
|
|
35
|
+
'Responsibility - SAPWBS (ESUK/IL)',
|
|
36
|
+
'Test case id',
|
|
37
|
+
'Test case title',
|
|
38
38
|
'Number of passed steps',
|
|
39
39
|
'Number of failed steps',
|
|
40
|
-
'Number of
|
|
40
|
+
'Number of not run tests',
|
|
41
41
|
];
|
|
42
42
|
|
|
43
43
|
orgUrl: string = '';
|
|
@@ -393,7 +393,8 @@ export default class ResultDataProvider {
|
|
|
393
393
|
public async getMewpL2CoverageFlatResults(
|
|
394
394
|
testPlanId: string,
|
|
395
395
|
projectName: string,
|
|
396
|
-
selectedSuiteIds: number[] | undefined
|
|
396
|
+
selectedSuiteIds: number[] | undefined,
|
|
397
|
+
linkedQueryRequest?: any
|
|
397
398
|
) {
|
|
398
399
|
const defaultPayload = {
|
|
399
400
|
sheetName: `MEWP L2 Coverage - Plan ${testPlanId}`,
|
|
@@ -406,7 +407,7 @@ export default class ResultDataProvider {
|
|
|
406
407
|
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
407
408
|
const testData = await this.fetchTestData(suites, projectName, testPlanId, false);
|
|
408
409
|
|
|
409
|
-
const requirements = await this.fetchMewpL2Requirements(projectName);
|
|
410
|
+
const requirements = await this.fetchMewpL2Requirements(projectName, linkedQueryRequest);
|
|
410
411
|
if (requirements.length === 0) {
|
|
411
412
|
return {
|
|
412
413
|
...defaultPayload,
|
|
@@ -414,26 +415,28 @@ export default class ResultDataProvider {
|
|
|
414
415
|
};
|
|
415
416
|
}
|
|
416
417
|
|
|
417
|
-
const requirementIndex = new Map<
|
|
418
|
-
|
|
419
|
-
{ passed: number; failed: number; notRun: number }
|
|
420
|
-
>();
|
|
418
|
+
const requirementIndex = new Map<string, Map<number, { passed: number; failed: number; notRun: number }>>();
|
|
419
|
+
const observedTestCaseIdsByRequirement = new Map<string, Set<number>>();
|
|
421
420
|
const requirementKeys = new Set<string>();
|
|
422
421
|
requirements.forEach((requirement) => {
|
|
423
422
|
const key = this.toRequirementKey(requirement.requirementId);
|
|
424
423
|
if (!key) return;
|
|
425
424
|
requirementKeys.add(key);
|
|
426
|
-
if (!requirementIndex.has(key)) {
|
|
427
|
-
requirementIndex.set(key, { passed: 0, failed: 0, notRun: 0 });
|
|
428
|
-
}
|
|
429
425
|
});
|
|
430
426
|
|
|
431
427
|
const parsedDefinitionStepsByTestCase = new Map<number, TestSteps[]>();
|
|
432
428
|
const testCaseStepsXmlMap = this.buildTestCaseStepsXmlMap(testData);
|
|
429
|
+
const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
|
|
433
430
|
|
|
434
431
|
const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, [], false, false);
|
|
435
432
|
for (const runResult of runResults) {
|
|
436
|
-
const testCaseId =
|
|
433
|
+
const testCaseId = this.extractMewpTestCaseId(runResult);
|
|
434
|
+
const runTestCaseTitle = this.toMewpComparableText(
|
|
435
|
+
runResult?.testCase?.name || runResult?.testCaseName || runResult?.testCaseTitle
|
|
436
|
+
);
|
|
437
|
+
if (Number.isFinite(testCaseId) && testCaseId > 0 && runTestCaseTitle && !testCaseTitleMap.has(testCaseId)) {
|
|
438
|
+
testCaseTitleMap.set(testCaseId, runTestCaseTitle);
|
|
439
|
+
}
|
|
437
440
|
const actionResults = Array.isArray(runResult?.iteration?.actionResults)
|
|
438
441
|
? runResult.iteration.actionResults
|
|
439
442
|
: [];
|
|
@@ -447,8 +450,10 @@ export default class ResultDataProvider {
|
|
|
447
450
|
this.accumulateRequirementCountsFromStepText(
|
|
448
451
|
`${String(actionResult?.action || '')} ${String(actionResult?.expected || '')}`,
|
|
449
452
|
stepStatus,
|
|
453
|
+
testCaseId,
|
|
450
454
|
requirementKeys,
|
|
451
|
-
requirementIndex
|
|
455
|
+
requirementIndex,
|
|
456
|
+
observedTestCaseIdsByRequirement
|
|
452
457
|
);
|
|
453
458
|
}
|
|
454
459
|
continue;
|
|
@@ -476,19 +481,20 @@ export default class ResultDataProvider {
|
|
|
476
481
|
this.accumulateRequirementCountsFromStepText(
|
|
477
482
|
`${String(step?.action || '')} ${String(step?.expected || '')}`,
|
|
478
483
|
'notRun',
|
|
484
|
+
testCaseId,
|
|
479
485
|
requirementKeys,
|
|
480
|
-
requirementIndex
|
|
486
|
+
requirementIndex,
|
|
487
|
+
observedTestCaseIdsByRequirement
|
|
481
488
|
);
|
|
482
489
|
}
|
|
483
490
|
}
|
|
484
491
|
|
|
485
|
-
const rows
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
});
|
|
492
|
+
const rows = this.buildMewpCoverageRows(
|
|
493
|
+
requirements,
|
|
494
|
+
requirementIndex,
|
|
495
|
+
observedTestCaseIdsByRequirement,
|
|
496
|
+
testCaseTitleMap
|
|
497
|
+
);
|
|
492
498
|
|
|
493
499
|
return {
|
|
494
500
|
sheetName: this.buildMewpCoverageSheetName(planName, testPlanId),
|
|
@@ -550,23 +556,131 @@ export default class ResultDataProvider {
|
|
|
550
556
|
title: string;
|
|
551
557
|
responsibility: string;
|
|
552
558
|
},
|
|
559
|
+
testCaseId: number | undefined,
|
|
560
|
+
testCaseTitle: string,
|
|
553
561
|
stepSummary: { passed: number; failed: number; notRun: number }
|
|
554
562
|
) {
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
const customerRequirement = [requirementId, requirementTitle].filter(Boolean).join(' - ');
|
|
563
|
+
const customerId = String(requirement.requirementId || '').trim();
|
|
564
|
+
const customerTitle = String(requirement.title || '').trim();
|
|
558
565
|
const responsibility = String(requirement.responsibility || '').trim();
|
|
566
|
+
const safeTestCaseId = Number.isFinite(testCaseId) && Number(testCaseId) > 0 ? Number(testCaseId) : '';
|
|
559
567
|
|
|
560
568
|
return {
|
|
561
|
-
'Customer
|
|
562
|
-
'
|
|
563
|
-
'
|
|
564
|
-
|
|
565
|
-
'
|
|
569
|
+
'Customer ID': customerId,
|
|
570
|
+
'Title (Customer name)': customerTitle,
|
|
571
|
+
'Responsibility - SAPWBS (ESUK/IL)': responsibility,
|
|
572
|
+
'Test case id': safeTestCaseId,
|
|
573
|
+
'Test case title': String(testCaseTitle || '').trim(),
|
|
566
574
|
'Number of passed steps': Number.isFinite(stepSummary?.passed) ? stepSummary.passed : 0,
|
|
567
575
|
'Number of failed steps': Number.isFinite(stepSummary?.failed) ? stepSummary.failed : 0,
|
|
568
|
-
'Number of
|
|
576
|
+
'Number of not run tests': Number.isFinite(stepSummary?.notRun) ? stepSummary.notRun : 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private buildMewpCoverageRows(
|
|
581
|
+
requirements: Array<{
|
|
582
|
+
requirementId: string;
|
|
583
|
+
title: string;
|
|
584
|
+
responsibility: string;
|
|
585
|
+
linkedTestCaseIds: number[];
|
|
586
|
+
}>,
|
|
587
|
+
requirementIndex: Map<string, Map<number, { passed: number; failed: number; notRun: number }>>,
|
|
588
|
+
observedTestCaseIdsByRequirement: Map<string, Set<number>>,
|
|
589
|
+
testCaseTitleMap: Map<number, string>
|
|
590
|
+
): any[] {
|
|
591
|
+
const rows: any[] = [];
|
|
592
|
+
for (const requirement of requirements) {
|
|
593
|
+
const key = this.toRequirementKey(requirement.requirementId);
|
|
594
|
+
const linkedTestCaseIds = (requirement?.linkedTestCaseIds || []).filter(
|
|
595
|
+
(id) => Number.isFinite(id) && Number(id) > 0
|
|
596
|
+
);
|
|
597
|
+
const observedTestCaseIds = key
|
|
598
|
+
? Array.from(observedTestCaseIdsByRequirement.get(key) || [])
|
|
599
|
+
: [];
|
|
600
|
+
|
|
601
|
+
const testCaseIds = Array.from(new Set<number>([...linkedTestCaseIds, ...observedTestCaseIds])).sort(
|
|
602
|
+
(a, b) => a - b
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
if (testCaseIds.length === 0) {
|
|
606
|
+
rows.push(
|
|
607
|
+
this.createMewpCoverageRow(requirement, undefined, '', {
|
|
608
|
+
passed: 0,
|
|
609
|
+
failed: 0,
|
|
610
|
+
notRun: 0,
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
for (const testCaseId of testCaseIds) {
|
|
617
|
+
const summary = key
|
|
618
|
+
? requirementIndex.get(key)?.get(testCaseId) || { passed: 0, failed: 0, notRun: 0 }
|
|
619
|
+
: { passed: 0, failed: 0, notRun: 0 };
|
|
620
|
+
rows.push(
|
|
621
|
+
this.createMewpCoverageRow(
|
|
622
|
+
requirement,
|
|
623
|
+
testCaseId,
|
|
624
|
+
String(testCaseTitleMap.get(testCaseId) || ''),
|
|
625
|
+
summary
|
|
626
|
+
)
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return rows;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private buildMewpTestCaseTitleMap(testData: any[]): Map<number, string> {
|
|
635
|
+
const map = new Map<number, string>();
|
|
636
|
+
|
|
637
|
+
const readTitleFromWorkItemFields = (workItemFields: any): string => {
|
|
638
|
+
if (!Array.isArray(workItemFields)) return '';
|
|
639
|
+
for (const field of workItemFields) {
|
|
640
|
+
const keyCandidates = [field?.key, field?.name, field?.referenceName, field?.id]
|
|
641
|
+
.map((item) => String(item || '').toLowerCase().trim());
|
|
642
|
+
const isTitleField =
|
|
643
|
+
keyCandidates.includes('system.title') || keyCandidates.includes('title');
|
|
644
|
+
if (!isTitleField) continue;
|
|
645
|
+
const value = this.toMewpComparableText(field?.value);
|
|
646
|
+
if (value) return value;
|
|
647
|
+
}
|
|
648
|
+
return '';
|
|
569
649
|
};
|
|
650
|
+
|
|
651
|
+
for (const suite of testData || []) {
|
|
652
|
+
const testPointsItems = Array.isArray(suite?.testPointsItems) ? suite.testPointsItems : [];
|
|
653
|
+
for (const point of testPointsItems) {
|
|
654
|
+
const pointTestCaseId = Number(point?.testCaseId || point?.testCase?.id);
|
|
655
|
+
if (!Number.isFinite(pointTestCaseId) || pointTestCaseId <= 0 || map.has(pointTestCaseId)) continue;
|
|
656
|
+
const pointTitle = this.toMewpComparableText(point?.testCaseName || point?.testCase?.name);
|
|
657
|
+
if (pointTitle) map.set(pointTestCaseId, pointTitle);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const testCasesItems = Array.isArray(suite?.testCasesItems) ? suite.testCasesItems : [];
|
|
661
|
+
for (const testCase of testCasesItems) {
|
|
662
|
+
const id = Number(testCase?.workItem?.id || testCase?.testCaseId || testCase?.id);
|
|
663
|
+
if (!Number.isFinite(id) || id <= 0 || map.has(id)) continue;
|
|
664
|
+
const fromDirectFields = this.toMewpComparableText(
|
|
665
|
+
testCase?.testCaseName || testCase?.name || testCase?.workItem?.name
|
|
666
|
+
);
|
|
667
|
+
if (fromDirectFields) {
|
|
668
|
+
map.set(id, fromDirectFields);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const fromWorkItemField = readTitleFromWorkItemFields(testCase?.workItem?.workItemFields);
|
|
672
|
+
if (fromWorkItemField) {
|
|
673
|
+
map.set(id, fromWorkItemField);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return map;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private extractMewpTestCaseId(runResult: any): number {
|
|
682
|
+
const testCaseId = Number(runResult?.testCaseId || runResult?.testCase?.id || 0);
|
|
683
|
+
return Number.isFinite(testCaseId) ? testCaseId : 0;
|
|
570
684
|
}
|
|
571
685
|
|
|
572
686
|
private buildTestCaseStepsXmlMap(testData: any[]): Map<number, string> {
|
|
@@ -617,16 +731,30 @@ export default class ResultDataProvider {
|
|
|
617
731
|
private accumulateRequirementCountsFromStepText(
|
|
618
732
|
stepText: string,
|
|
619
733
|
status: 'passed' | 'failed' | 'notRun',
|
|
734
|
+
testCaseId: number,
|
|
620
735
|
requirementKeys: Set<string>,
|
|
621
|
-
counters: Map<string, { passed: number; failed: number; notRun: number }
|
|
736
|
+
counters: Map<string, Map<number, { passed: number; failed: number; notRun: number }>>,
|
|
737
|
+
observedTestCaseIdsByRequirement: Map<string, Set<number>>
|
|
622
738
|
) {
|
|
739
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0) return;
|
|
740
|
+
|
|
623
741
|
const codes = this.extractRequirementCodesFromText(stepText);
|
|
624
742
|
for (const code of codes) {
|
|
625
743
|
if (requirementKeys.size > 0 && !requirementKeys.has(code)) continue;
|
|
626
744
|
if (!counters.has(code)) {
|
|
627
|
-
counters.set(code, { passed:
|
|
745
|
+
counters.set(code, new Map<number, { passed: number; failed: number; notRun: number }>());
|
|
746
|
+
}
|
|
747
|
+
const perTestCaseCounters = counters.get(code)!;
|
|
748
|
+
if (!perTestCaseCounters.has(testCaseId)) {
|
|
749
|
+
perTestCaseCounters.set(testCaseId, { passed: 0, failed: 0, notRun: 0 });
|
|
628
750
|
}
|
|
629
|
-
|
|
751
|
+
|
|
752
|
+
if (!observedTestCaseIdsByRequirement.has(code)) {
|
|
753
|
+
observedTestCaseIdsByRequirement.set(code, new Set<number>());
|
|
754
|
+
}
|
|
755
|
+
observedTestCaseIdsByRequirement.get(code)!.add(testCaseId);
|
|
756
|
+
|
|
757
|
+
const counter = perTestCaseCounters.get(testCaseId)!;
|
|
630
758
|
if (status === 'passed') counter.passed += 1;
|
|
631
759
|
else if (status === 'failed') counter.failed += 1;
|
|
632
760
|
else counter.notRun += 1;
|
|
@@ -674,7 +802,7 @@ export default class ResultDataProvider {
|
|
|
674
802
|
return `SR${digits}`;
|
|
675
803
|
}
|
|
676
804
|
|
|
677
|
-
private async fetchMewpL2Requirements(projectName: string): Promise<
|
|
805
|
+
private async fetchMewpL2Requirements(projectName: string, linkedQueryRequest?: any): Promise<
|
|
678
806
|
Array<{
|
|
679
807
|
workItemId: number;
|
|
680
808
|
requirementId: string;
|
|
@@ -683,6 +811,11 @@ export default class ResultDataProvider {
|
|
|
683
811
|
linkedTestCaseIds: number[];
|
|
684
812
|
}>
|
|
685
813
|
> {
|
|
814
|
+
const queryHref = this.extractMewpQueryHref(linkedQueryRequest);
|
|
815
|
+
if (queryHref) {
|
|
816
|
+
return this.fetchMewpL2RequirementsFromQuery(projectName, queryHref);
|
|
817
|
+
}
|
|
818
|
+
|
|
686
819
|
const workItemTypeNames = await this.fetchMewpRequirementTypeNames(projectName);
|
|
687
820
|
if (workItemTypeNames.length === 0) {
|
|
688
821
|
return [];
|
|
@@ -722,6 +855,225 @@ ORDER BY [System.Id]`;
|
|
|
722
855
|
return requirements.sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
|
|
723
856
|
}
|
|
724
857
|
|
|
858
|
+
private extractMewpQueryHref(linkedQueryRequest?: any): string {
|
|
859
|
+
const mode = String(linkedQueryRequest?.linkedQueryMode || '')
|
|
860
|
+
.trim()
|
|
861
|
+
.toLowerCase();
|
|
862
|
+
if (mode !== 'query') return '';
|
|
863
|
+
|
|
864
|
+
return String(linkedQueryRequest?.testAssociatedQuery?.wiql?.href || '').trim();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
private async fetchMewpL2RequirementsFromQuery(
|
|
868
|
+
projectName: string,
|
|
869
|
+
queryHref: string
|
|
870
|
+
): Promise<
|
|
871
|
+
Array<{
|
|
872
|
+
workItemId: number;
|
|
873
|
+
requirementId: string;
|
|
874
|
+
title: string;
|
|
875
|
+
responsibility: string;
|
|
876
|
+
linkedTestCaseIds: number[];
|
|
877
|
+
}>
|
|
878
|
+
> {
|
|
879
|
+
try {
|
|
880
|
+
const ticketsDataProvider = new TicketsDataProvider(this.orgUrl, this.token);
|
|
881
|
+
const queryResult = await ticketsDataProvider.GetQueryResultsFromWiql(
|
|
882
|
+
queryHref,
|
|
883
|
+
true,
|
|
884
|
+
new Map<number, Set<any>>()
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
const requirementTypeNames = await this.fetchMewpRequirementTypeNames(projectName);
|
|
888
|
+
const requirementTypeSet = new Set(
|
|
889
|
+
requirementTypeNames.map((name) => String(name || '').trim().toLowerCase())
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const requirementsById = new Map<
|
|
893
|
+
number,
|
|
894
|
+
{
|
|
895
|
+
workItemId: number;
|
|
896
|
+
requirementId: string;
|
|
897
|
+
title: string;
|
|
898
|
+
responsibility: string;
|
|
899
|
+
linkedTestCaseIds: Set<number>;
|
|
900
|
+
}
|
|
901
|
+
>();
|
|
902
|
+
|
|
903
|
+
const upsertRequirement = (workItem: any) => {
|
|
904
|
+
this.upsertMewpRequirement(requirementsById, workItem, requirementTypeSet);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
const linkRequirementToTestCase = (requirementWorkItem: any, testCaseWorkItem: any) => {
|
|
908
|
+
const requirementId = Number(requirementWorkItem?.id || 0);
|
|
909
|
+
const testCaseId = Number(testCaseWorkItem?.id || 0);
|
|
910
|
+
if (!Number.isFinite(requirementId) || requirementId <= 0) return;
|
|
911
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0) return;
|
|
912
|
+
|
|
913
|
+
upsertRequirement(requirementWorkItem);
|
|
914
|
+
const requirement = requirementsById.get(requirementId);
|
|
915
|
+
if (!requirement) return;
|
|
916
|
+
requirement.linkedTestCaseIds.add(testCaseId);
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
if (Array.isArray(queryResult?.fetchedWorkItems)) {
|
|
920
|
+
for (const workItem of queryResult.fetchedWorkItems) {
|
|
921
|
+
upsertRequirement(workItem);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (queryResult?.sourceTargetsMap && typeof queryResult.sourceTargetsMap.entries === 'function') {
|
|
926
|
+
for (const [sourceItem, targets] of queryResult.sourceTargetsMap.entries()) {
|
|
927
|
+
const sourceType = this.getMewpWorkItemType(sourceItem);
|
|
928
|
+
const sourceIsRequirement = this.isMewpRequirementType(sourceType, requirementTypeSet);
|
|
929
|
+
const sourceIsTestCase = this.isMewpTestCaseType(sourceType);
|
|
930
|
+
|
|
931
|
+
if (sourceIsRequirement) {
|
|
932
|
+
upsertRequirement(sourceItem);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const relatedItems = Array.isArray(targets) ? targets : [];
|
|
936
|
+
for (const targetItem of relatedItems) {
|
|
937
|
+
const targetType = this.getMewpWorkItemType(targetItem);
|
|
938
|
+
const targetIsRequirement = this.isMewpRequirementType(targetType, requirementTypeSet);
|
|
939
|
+
const targetIsTestCase = this.isMewpTestCaseType(targetType);
|
|
940
|
+
|
|
941
|
+
if (targetIsRequirement) {
|
|
942
|
+
upsertRequirement(targetItem);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (sourceIsRequirement && targetIsTestCase) {
|
|
946
|
+
linkRequirementToTestCase(sourceItem, targetItem);
|
|
947
|
+
} else if (sourceIsTestCase && targetIsRequirement) {
|
|
948
|
+
linkRequirementToTestCase(targetItem, sourceItem);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
await this.hydrateMewpRequirementsFromWorkItems(projectName, requirementsById);
|
|
955
|
+
|
|
956
|
+
return [...requirementsById.values()]
|
|
957
|
+
.map((requirement) => ({
|
|
958
|
+
workItemId: requirement.workItemId,
|
|
959
|
+
requirementId: requirement.requirementId,
|
|
960
|
+
title: requirement.title,
|
|
961
|
+
responsibility: requirement.responsibility,
|
|
962
|
+
linkedTestCaseIds: [...requirement.linkedTestCaseIds].sort((a, b) => a - b),
|
|
963
|
+
}))
|
|
964
|
+
.sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
|
|
965
|
+
} catch (error: any) {
|
|
966
|
+
logger.error(`Could not fetch MEWP requirements from query: ${error?.message || error}`);
|
|
967
|
+
return [];
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private upsertMewpRequirement(
|
|
972
|
+
requirementsById: Map<
|
|
973
|
+
number,
|
|
974
|
+
{
|
|
975
|
+
workItemId: number;
|
|
976
|
+
requirementId: string;
|
|
977
|
+
title: string;
|
|
978
|
+
responsibility: string;
|
|
979
|
+
linkedTestCaseIds: Set<number>;
|
|
980
|
+
}
|
|
981
|
+
>,
|
|
982
|
+
workItem: any,
|
|
983
|
+
requirementTypeSet: Set<string>
|
|
984
|
+
) {
|
|
985
|
+
const workItemId = Number(workItem?.id || 0);
|
|
986
|
+
if (!Number.isFinite(workItemId) || workItemId <= 0) return;
|
|
987
|
+
|
|
988
|
+
const fields = workItem?.fields || {};
|
|
989
|
+
const workItemType = this.getMewpWorkItemType(workItem);
|
|
990
|
+
if (!this.isMewpRequirementType(workItemType, requirementTypeSet)) return;
|
|
991
|
+
|
|
992
|
+
const existing = requirementsById.get(workItemId) || {
|
|
993
|
+
workItemId,
|
|
994
|
+
requirementId: String(workItemId),
|
|
995
|
+
title: '',
|
|
996
|
+
responsibility: '',
|
|
997
|
+
linkedTestCaseIds: new Set<number>(),
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
const extractedRequirementId = this.extractMewpRequirementIdentifier(fields, workItemId);
|
|
1001
|
+
const extractedTitle = this.toMewpComparableText(fields?.['System.Title']);
|
|
1002
|
+
const extractedResponsibility = this.deriveMewpResponsibility(fields);
|
|
1003
|
+
|
|
1004
|
+
existing.requirementId = extractedRequirementId || existing.requirementId || String(workItemId);
|
|
1005
|
+
if (extractedTitle) {
|
|
1006
|
+
existing.title = extractedTitle;
|
|
1007
|
+
}
|
|
1008
|
+
if (extractedResponsibility) {
|
|
1009
|
+
existing.responsibility = extractedResponsibility;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
requirementsById.set(workItemId, existing);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
private async hydrateMewpRequirementsFromWorkItems(
|
|
1016
|
+
projectName: string,
|
|
1017
|
+
requirementsById: Map<
|
|
1018
|
+
number,
|
|
1019
|
+
{
|
|
1020
|
+
workItemId: number;
|
|
1021
|
+
requirementId: string;
|
|
1022
|
+
title: string;
|
|
1023
|
+
responsibility: string;
|
|
1024
|
+
linkedTestCaseIds: Set<number>;
|
|
1025
|
+
}
|
|
1026
|
+
>
|
|
1027
|
+
) {
|
|
1028
|
+
const requirementIds = [...requirementsById.keys()];
|
|
1029
|
+
if (requirementIds.length === 0) return;
|
|
1030
|
+
|
|
1031
|
+
const fetchedRequirements = await this.fetchWorkItemsByIds(projectName, requirementIds, true);
|
|
1032
|
+
for (const requirementWorkItem of fetchedRequirements) {
|
|
1033
|
+
const workItemId = Number(requirementWorkItem?.id || 0);
|
|
1034
|
+
if (!Number.isFinite(workItemId) || workItemId <= 0) continue;
|
|
1035
|
+
const current = requirementsById.get(workItemId);
|
|
1036
|
+
if (!current) continue;
|
|
1037
|
+
|
|
1038
|
+
const fields = requirementWorkItem?.fields || {};
|
|
1039
|
+
const requirementId = this.extractMewpRequirementIdentifier(fields, workItemId);
|
|
1040
|
+
const title = this.toMewpComparableText(fields?.['System.Title']);
|
|
1041
|
+
const responsibility = this.deriveMewpResponsibility(fields);
|
|
1042
|
+
const linkedTestCaseIds = this.extractLinkedTestCaseIdsFromRequirement(
|
|
1043
|
+
requirementWorkItem?.relations || []
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
current.requirementId = requirementId || current.requirementId || String(workItemId);
|
|
1047
|
+
if (title) {
|
|
1048
|
+
current.title = title;
|
|
1049
|
+
}
|
|
1050
|
+
if (responsibility) {
|
|
1051
|
+
current.responsibility = responsibility;
|
|
1052
|
+
}
|
|
1053
|
+
linkedTestCaseIds.forEach((testCaseId) => current.linkedTestCaseIds.add(testCaseId));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private getMewpWorkItemType(workItem: any): string {
|
|
1058
|
+
return this.toMewpComparableText(workItem?.fields?.['System.WorkItemType']);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
private isMewpRequirementType(workItemType: string, requirementTypeSet: Set<string>): boolean {
|
|
1062
|
+
const normalized = String(workItemType || '')
|
|
1063
|
+
.trim()
|
|
1064
|
+
.toLowerCase();
|
|
1065
|
+
if (!normalized) return false;
|
|
1066
|
+
if (requirementTypeSet.has(normalized)) return true;
|
|
1067
|
+
return normalized.includes('requirement') || normalized === 'epic';
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private isMewpTestCaseType(workItemType: string): boolean {
|
|
1071
|
+
const normalized = String(workItemType || '')
|
|
1072
|
+
.trim()
|
|
1073
|
+
.toLowerCase();
|
|
1074
|
+
return normalized === 'test case' || normalized === 'testcase';
|
|
1075
|
+
}
|
|
1076
|
+
|
|
725
1077
|
private async fetchMewpRequirementTypeNames(projectName: string): Promise<string[]> {
|
|
726
1078
|
try {
|
|
727
1079
|
const url = `${this.orgUrl}${projectName}/_apis/wit/workitemtypes?api-version=7.1-preview.2`;
|