@elisra-devops/docgen-data-provider 1.109.0 → 1.109.1

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.
@@ -666,6 +666,31 @@ export default class TicketsDataProvider {
666
666
  return { systemRequirementsQueryTree };
667
667
  }
668
668
 
669
+ /**
670
+ * Fetches flat Customer/System requirement queries for SysRS traceability.
671
+ * OneHop/tree queries are intentionally excluded for this picker.
672
+ */
673
+ private async fetchFlatUpstreamQueries(
674
+ queries: any,
675
+ excludedFolderNames: string[] = [],
676
+ allowedTypes: string[] = ['requirement'],
677
+ ) {
678
+ const { tree1: systemRequirementsQueryTree } = await this.structureFetchedQueries(
679
+ queries,
680
+ false,
681
+ null,
682
+ allowedTypes,
683
+ [],
684
+ undefined,
685
+ undefined,
686
+ false,
687
+ excludedFolderNames,
688
+ true,
689
+ false,
690
+ );
691
+ return { systemRequirementsQueryTree };
692
+ }
693
+
669
694
  private async fetchSrsQueries(rootQueries: any) {
670
695
  const srsFolder = await this.findQueryFolderByName(rootQueries, 'srs');
671
696
  if (!srsFolder) {
@@ -710,31 +735,54 @@ export default class TicketsDataProvider {
710
735
  const { root: sysRsRoot, found: sysRsRootFound } = await this.getDocTypeRoot(rootQueries, 'sysrs');
711
736
  logger.debug(`[GetSharedQueries][sysrs] using ${sysRsRootFound ? 'dedicated folder' : 'root queries'}`);
712
737
 
713
- const systemRequirementsQueries = await this.fetchSystemRequirementQueries(sysRsRoot, [
714
- 'System To Customer',
715
- 'System To Subsystem',
716
- ]);
717
-
718
- const systemToCustomerFolder = await this.findChildFolderByPossibleNames(sysRsRoot, [
738
+ const systemToCustomerFolderNames = [
719
739
  'system to customer',
720
740
  'system-to-customer',
721
741
  'system customer',
722
742
  'subsystem to system',
723
743
  'customer to system',
724
- ]);
725
- const systemToSubsystemFolder = await this.findChildFolderByPossibleNames(sysRsRoot, [
726
- 'system to subsystem',
727
- 'system-to-subsystem',
728
- 'system subsystem',
744
+ ];
745
+ const systemToSubsystemFolderNames = ['system to subsystem', 'system-to-subsystem', 'system subsystem'];
746
+
747
+ const systemRequirementsQueries = await this.fetchSystemRequirementQueries(sysRsRoot, [
748
+ ...systemToCustomerFolderNames,
749
+ ...systemToSubsystemFolderNames,
729
750
  ]);
730
751
 
752
+ const systemToCustomerFolder = await this.findChildFolderByPossibleNames(
753
+ sysRsRoot,
754
+ systemToCustomerFolderNames,
755
+ );
756
+ const systemToSubsystemFolder = await this.findChildFolderByPossibleNames(
757
+ sysRsRoot,
758
+ systemToSubsystemFolderNames,
759
+ );
760
+
731
761
  const subsystemToSystemRequirementsQueries =
732
762
  await this.fetchRequirementsTraceQueriesForFolder(systemToCustomerFolder);
733
763
  const systemToSubsystemRequirementsQueries =
734
764
  await this.fetchRequirementsTraceQueriesForFolder(systemToSubsystemFolder);
735
765
 
766
+ // Customer/System requirements (traceability table) picker: only scan the dedicated
767
+ // System-to-Customer folder. If it doesn't exist in the tenant, return null
768
+ // rather than scanning the whole SysRS root, which would surface unrelated
769
+ // flat queries into the picker.
770
+ let customerRequirementsQueries: any = null;
771
+ if (systemToCustomerFolder) {
772
+ customerRequirementsQueries = await this.fetchFlatUpstreamQueries(
773
+ systemToCustomerFolder,
774
+ [],
775
+ ['requirement'],
776
+ );
777
+ } else {
778
+ logger.debug(
779
+ '[GetSharedQueries][sysrs] System-to-Customer folder not found; skipping customer-requirements picker',
780
+ );
781
+ }
782
+
736
783
  return {
737
784
  systemRequirementsQueries,
785
+ customerRequirementsQueries,
738
786
  subsystemToSystemRequirementsQueries,
739
787
  systemToSubsystemRequirementsQueries,
740
788
  };
@@ -2494,7 +2542,7 @@ export default class TicketsDataProvider {
2494
2542
  * Recursively structures fetched queries into two hierarchical trees (tree1 and tree2)
2495
2543
  * by matching WIQL against allowed Source/Target types and optional area filters.
2496
2544
  * Supports leaf queries of type:
2497
- * - oneHop (always)
2545
+ * - oneHop (when includeOneHopQueries === true)
2498
2546
  * - tree (when includeTreeQueries === true)
2499
2547
  * - flat (when includeFlatQueries === true)
2500
2548
  *
@@ -2508,6 +2556,7 @@ export default class TicketsDataProvider {
2508
2556
  * @param includeTreeQueries - Include 'tree' queries in addition to 'oneHop'. Defaults to `false`.
2509
2557
  * @param excludedFolderNames - Optional list of folder names to skip entirely (case-insensitive exact match).
2510
2558
  * @param includeFlatQueries - Include 'flat' queries (matched by [System.WorkItemType] and [System.AreaPath]). Defaults to `false`.
2559
+ * @param includeOneHopQueries - Include 'oneHop' queries. Defaults to `true` for legacy callers.
2511
2560
  * @returns A promise resolving to an object with `tree1` and `tree2` nodes, or `null` for each when none match.
2512
2561
  * @throws Logs an error if an exception occurs during processing.
2513
2562
  */
@@ -2522,6 +2571,7 @@ export default class TicketsDataProvider {
2522
2571
  includeTreeQueries: boolean = false,
2523
2572
  excludedFolderNames: string[] = [],
2524
2573
  includeFlatQueries: boolean = false,
2574
+ includeOneHopQueries: boolean = true,
2525
2575
  workItemTypeCache?: Map<string, string | null>,
2526
2576
  ): Promise<any> {
2527
2577
  try {
@@ -2540,7 +2590,7 @@ export default class TicketsDataProvider {
2540
2590
  if (!rootQuery.hasChildren) {
2541
2591
  const isLeafCandidate =
2542
2592
  !rootQuery.isFolder &&
2543
- (rootQuery.queryType === 'oneHop' ||
2593
+ ((includeOneHopQueries && rootQuery.queryType === 'oneHop') ||
2544
2594
  (includeTreeQueries && rootQuery.queryType === 'tree') ||
2545
2595
  (includeFlatQueries && rootQuery.queryType === 'flat'));
2546
2596
  if (isLeafCandidate) {
@@ -2636,6 +2686,7 @@ export default class TicketsDataProvider {
2636
2686
  includeTreeQueries,
2637
2687
  excludedFolderNames,
2638
2688
  includeFlatQueries,
2689
+ includeOneHopQueries,
2639
2690
  typeCache,
2640
2691
  );
2641
2692
  }
@@ -2654,6 +2705,7 @@ export default class TicketsDataProvider {
2654
2705
  includeTreeQueries,
2655
2706
  excludedFolderNames,
2656
2707
  includeFlatQueries,
2708
+ includeOneHopQueries,
2657
2709
  typeCache,
2658
2710
  ),
2659
2711
  ),
@@ -93,6 +93,66 @@ describe('TicketsDataProvider', () => {
93
93
  });
94
94
  });
95
95
 
96
+ describe('fetchFlatUpstreamQueries', () => {
97
+ it('should allow only Requirement flat queries and exclude oneHop/tree query types', async () => {
98
+ const structureSpy = jest
99
+ .spyOn(ticketsDataProvider as any, 'structureFetchedQueries')
100
+ .mockResolvedValue({ tree1: { id: 't1' }, tree2: null });
101
+
102
+ await (ticketsDataProvider as any).fetchFlatUpstreamQueries({ hasChildren: false }, []);
103
+
104
+ expect(structureSpy.mock.calls[0][3]).toEqual(['requirement']);
105
+ expect(structureSpy.mock.calls[0][7]).toBe(false);
106
+ expect(structureSpy.mock.calls[0][9]).toBe(true);
107
+ expect(structureSpy.mock.calls[0][10]).toBe(false);
108
+ });
109
+
110
+ it('should allow Requirement flat queries for fallback discovery', async () => {
111
+ const structureSpy = jest
112
+ .spyOn(ticketsDataProvider as any, 'structureFetchedQueries')
113
+ .mockResolvedValue({ tree1: { id: 't1' }, tree2: null });
114
+
115
+ await (ticketsDataProvider as any).fetchFlatUpstreamQueries(
116
+ { hasChildren: false },
117
+ ['system to subsystem', 'system-to-subsystem', 'system subsystem'],
118
+ ['requirement'],
119
+ );
120
+
121
+ expect(structureSpy.mock.calls[0][3]).toEqual(['requirement']);
122
+ expect(structureSpy.mock.calls[0][8]).toEqual([
123
+ 'system to subsystem',
124
+ 'system-to-subsystem',
125
+ 'system subsystem',
126
+ ]);
127
+ expect(structureSpy.mock.calls[0][9]).toBe(true);
128
+ expect(structureSpy.mock.calls[0][10]).toBe(false);
129
+ });
130
+
131
+ it('should let structureFetchedQueries exclude oneHop leaves only for flat-only callers', async () => {
132
+ const result = await (ticketsDataProvider as any).structureFetchedQueries(
133
+ {
134
+ id: 'one-hop',
135
+ hasChildren: false,
136
+ isFolder: false,
137
+ queryType: 'oneHop',
138
+ wiql: "[Source].[System.WorkItemType] = 'Requirement'",
139
+ },
140
+ false,
141
+ null,
142
+ ['requirement'],
143
+ [],
144
+ undefined,
145
+ undefined,
146
+ false,
147
+ [],
148
+ true,
149
+ false,
150
+ );
151
+
152
+ expect(result).toEqual({ tree1: null, tree2: null });
153
+ });
154
+ });
155
+
96
156
  describe('fetchSysRsQueries', () => {
97
157
  it('should use sysrs root, exclude trace folders from system requirements and resolve both trace directions', async () => {
98
158
  const rootQueries = { id: 'root' };
@@ -106,6 +166,9 @@ describe('TicketsDataProvider', () => {
106
166
  const fetchSystemRequirementQueriesSpy = jest
107
167
  .spyOn(ticketsDataProvider as any, 'fetchSystemRequirementQueries')
108
168
  .mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
169
+ const fetchFlatUpstreamQueriesSpy = jest
170
+ .spyOn(ticketsDataProvider as any, 'fetchFlatUpstreamQueries')
171
+ .mockResolvedValue({ systemRequirementsQueryTree: { id: 'customer-tree' } });
109
172
  const findChildFolderByPossibleNamesSpy = jest
110
173
  .spyOn(ticketsDataProvider as any, 'findChildFolderByPossibleNames')
111
174
  .mockResolvedValueOnce(subsystemToSystemFolder)
@@ -119,8 +182,14 @@ describe('TicketsDataProvider', () => {
119
182
 
120
183
  expect(getDocTypeRootSpy).toHaveBeenCalledWith(rootQueries, 'sysrs');
121
184
  expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(sysRsRoot, [
122
- 'System To Customer',
123
- 'System To Subsystem',
185
+ 'system to customer',
186
+ 'system-to-customer',
187
+ 'system customer',
188
+ 'subsystem to system',
189
+ 'customer to system',
190
+ 'system to subsystem',
191
+ 'system-to-subsystem',
192
+ 'system subsystem',
124
193
  ]);
125
194
 
126
195
  expect(findChildFolderByPossibleNamesSpy).toHaveBeenNthCalledWith(
@@ -136,15 +205,17 @@ describe('TicketsDataProvider', () => {
136
205
 
137
206
  expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(1, subsystemToSystemFolder);
138
207
  expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, systemToSubsystemFolder);
208
+ expect(fetchFlatUpstreamQueriesSpy).toHaveBeenCalledWith(subsystemToSystemFolder, [], ['requirement']);
139
209
 
140
210
  expect(result).toEqual({
141
211
  systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
212
+ customerRequirementsQueries: { systemRequirementsQueryTree: { id: 'customer-tree' } },
142
213
  subsystemToSystemRequirementsQueries: { id: 'fwd-trace-tree' },
143
214
  systemToSubsystemRequirementsQueries: { id: 'rev-trace-tree' },
144
215
  });
145
216
  });
146
217
 
147
- it('should fall back to root queries and return null trace trees when trace folders are missing', async () => {
218
+ it('should return null customer/trace trees when trace folders are missing (no sysRsRoot fallback scan)', async () => {
148
219
  const rootQueries = { id: 'root' };
149
220
 
150
221
  jest
@@ -153,6 +224,9 @@ describe('TicketsDataProvider', () => {
153
224
  const fetchSystemRequirementQueriesSpy = jest
154
225
  .spyOn(ticketsDataProvider as any, 'fetchSystemRequirementQueries')
155
226
  .mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
227
+ const fetchFlatUpstreamQueriesSpy = jest
228
+ .spyOn(ticketsDataProvider as any, 'fetchFlatUpstreamQueries')
229
+ .mockResolvedValue({ systemRequirementsQueryTree: { id: 'should-not-be-used' } });
156
230
  jest
157
231
  .spyOn(ticketsDataProvider as any, 'findChildFolderByPossibleNames')
158
232
  .mockResolvedValueOnce(null)
@@ -165,13 +239,22 @@ describe('TicketsDataProvider', () => {
165
239
  const result = await (ticketsDataProvider as any).fetchSysRsQueries(rootQueries);
166
240
 
167
241
  expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(rootQueries, [
168
- 'System To Customer',
169
- 'System To Subsystem',
242
+ 'system to customer',
243
+ 'system-to-customer',
244
+ 'system customer',
245
+ 'subsystem to system',
246
+ 'customer to system',
247
+ 'system to subsystem',
248
+ 'system-to-subsystem',
249
+ 'system subsystem',
170
250
  ]);
171
251
  expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(1, null);
172
252
  expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, null);
253
+ // No fallback scan of sysRsRoot when the System-to-Customer folder is absent.
254
+ expect(fetchFlatUpstreamQueriesSpy).not.toHaveBeenCalled();
173
255
  expect(result).toEqual({
174
256
  systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
257
+ customerRequirementsQueries: null,
175
258
  subsystemToSystemRequirementsQueries: null,
176
259
  systemToSubsystemRequirementsQueries: null,
177
260
  });