@smartbear/mcp 0.26.0 → 0.27.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/README.md +10 -2
- package/dist/package.json.js +1 -1
- package/dist/qmetry/client/api/client-api.js +13 -1
- package/dist/qmetry/client/auto-resolve.js +23 -1
- package/dist/qmetry/client/handlers.js +10 -2
- package/dist/qmetry/client/issues.js +113 -1
- package/dist/qmetry/client/testcase.js +82 -1
- package/dist/qmetry/client/testsuite.js +20 -8
- package/dist/qmetry/client/tools/index.js +5 -2
- package/dist/qmetry/client/tools/issue-tools.js +163 -1
- package/dist/qmetry/client/tools/testcase-tools.js +81 -7
- package/dist/qmetry/client/tools/testsuite-tools.js +291 -26
- package/dist/qmetry/client/tools/udf-tools.js +294 -0
- package/dist/qmetry/client/udf.js +376 -0
- package/dist/qmetry/client.js +30 -3
- package/dist/qmetry/config/constants.js +63 -2
- package/dist/qmetry/config/rest-endpoints.js +7 -1
- package/dist/qmetry/types/common.js +85 -47
- package/dist/qmetry/types/issues.js +5 -0
- package/dist/qmetry/types/udf.js +77 -0
- package/dist/swagger/client/api.js +421 -8
- package/dist/swagger/client/configuration.js +9 -0
- package/dist/swagger/client/functional-testing-api.js +38 -2
- package/dist/swagger/client/functional-testing-tools.js +18 -0
- package/dist/swagger/client/functional-testing-types.js +11 -0
- package/dist/swagger/client/portal-types.js +35 -3
- package/dist/swagger/client/portal-utils.js +50 -0
- package/dist/swagger/client/registry-types.js +8 -0
- package/dist/swagger/client/tools.js +25 -4
- package/dist/swagger/client/utils.js +37 -0
- package/dist/swagger/client.js +37 -5
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { QMetryToolsHandlers } from "../../config/constants.js";
|
|
2
|
+
import { BulkUpdateTestRunUdfsArgsSchema, FetchTestRunUdfMetadataArgsSchema, FetchTestRunUdfValuesArgsSchema, FetchCascadeChildValuesArgsSchema } from "../../types/udf.js";
|
|
3
|
+
const UDF_TOOLS = [
|
|
4
|
+
{
|
|
5
|
+
title: "Bulk Update Test Run UDFs",
|
|
6
|
+
toolset: "UDF",
|
|
7
|
+
summary: "Bulk update User Defined Field (UDF) values for one or more Test Case Runs in a test execution. Runs asynchronously in the background.",
|
|
8
|
+
handler: QMetryToolsHandlers.BULK_UPDATE_TEST_RUN_UDFS,
|
|
9
|
+
inputSchema: BulkUpdateTestRunUdfsArgsSchema,
|
|
10
|
+
purpose: "Updates UDF (custom field) values for one or more Test Case Runs in bulk. Supports all UDF field types: STRING, NUMBER, DATETIMEPICKER, LOOKUPLIST (single select), MULTILOOKUPLIST (multi-select), and CASCADINGLIST (parent/child). For MULTILOOKUPLIST fields, use 'append' to add values to existing selections or 'replace' to overwrite existing selections with the new values. The operation runs asynchronously in QMetry's background — track progress under 'Scheduled Task' in the QMetry UI. All UDF fields in the payload are optional — include only the fields you want to update.",
|
|
11
|
+
useCases: [
|
|
12
|
+
"Bulk update a string UDF (e.g. build version, environment name) for multiple test runs",
|
|
13
|
+
"Set a date UDF field (e.g. execution date) across multiple test case runs",
|
|
14
|
+
"Update a numeric UDF field (e.g. story points, priority score) in bulk",
|
|
15
|
+
"Set a single-select lookup UDF to a new value for multiple runs",
|
|
16
|
+
"Append new values to a multi-select UDF field across multiple test runs",
|
|
17
|
+
"Replace all existing selections in a multi-select UDF with new values",
|
|
18
|
+
"Update a cascading list UDF (parent + child) for multiple test runs",
|
|
19
|
+
"Update multiple UDF fields of different types in a single bulk operation",
|
|
20
|
+
"Reset a UDF field value for all runs in a test suite execution",
|
|
21
|
+
"Sync automated test result metadata (environment, build, platform) into UDF fields after execution"
|
|
22
|
+
],
|
|
23
|
+
examples: [
|
|
24
|
+
{
|
|
25
|
+
description: "Bulk update a STRING UDF for multiple test runs",
|
|
26
|
+
parameters: {
|
|
27
|
+
tcRunIDs: [41572006, 41572009, 41572013],
|
|
28
|
+
UDF: {
|
|
29
|
+
"8190_String": {
|
|
30
|
+
fieldID: 229241,
|
|
31
|
+
value: "regression-v2.1"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background. Go to 'Scheduled Task' to track the process."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
description: "Bulk update a DATE UDF field for multiple test runs (MM-DD-YYYY format)",
|
|
39
|
+
parameters: {
|
|
40
|
+
tcRunIDs: [41572006, 41572009],
|
|
41
|
+
UDF: {
|
|
42
|
+
KN_DATE: {
|
|
43
|
+
fieldID: 229255,
|
|
44
|
+
value: "06-20-2026"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
description: "Bulk update a NUMBER UDF field for multiple test runs",
|
|
52
|
+
parameters: {
|
|
53
|
+
tcRunIDs: [41572006, 41572009, 41572013, 41572015],
|
|
54
|
+
UDF: {
|
|
55
|
+
defaultNum: {
|
|
56
|
+
fieldID: 229003,
|
|
57
|
+
value: 5
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
description: "Bulk update a single-select LOOKUPLIST UDF for multiple test runs",
|
|
65
|
+
parameters: {
|
|
66
|
+
tcRunIDs: [41572006, 41572009],
|
|
67
|
+
UDF: {
|
|
68
|
+
"8260LUP": {
|
|
69
|
+
fieldID: 228563,
|
|
70
|
+
value: 5108697
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
description: "Bulk update a MULTILOOKUPLIST UDF — APPEND new values to existing selections",
|
|
78
|
+
parameters: {
|
|
79
|
+
tcRunIDs: [41572006, 41572009, 41572013],
|
|
80
|
+
UDF: {
|
|
81
|
+
m_selections: {
|
|
82
|
+
fieldID: 229223,
|
|
83
|
+
value: [5158524, 5158525],
|
|
84
|
+
multiSelectAction: "append"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
description: "Bulk update a MULTILOOKUPLIST UDF — REPLACE existing selections with new values",
|
|
92
|
+
parameters: {
|
|
93
|
+
tcRunIDs: [41572006, 41572009],
|
|
94
|
+
UDF: {
|
|
95
|
+
mullt_env: {
|
|
96
|
+
fieldID: 229425,
|
|
97
|
+
value: [5108697, 5108698],
|
|
98
|
+
multiSelectAction: "replace"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
description: "Bulk update a CASCADINGLIST UDF (parent + child) for multiple test runs",
|
|
106
|
+
parameters: {
|
|
107
|
+
tcRunIDs: [41572006, 41572009, 41572013],
|
|
108
|
+
UDF: {
|
|
109
|
+
cascade_mcp: {
|
|
110
|
+
fieldID: 229426,
|
|
111
|
+
value: { parent: 5126498, child: 5126499 }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
description: "Bulk update multiple UDF fields of different types in a single operation",
|
|
119
|
+
parameters: {
|
|
120
|
+
tcRunIDs: [41572006, 41572009, 41572013, 41572015, 41579875],
|
|
121
|
+
UDF: {
|
|
122
|
+
"8190_String": {
|
|
123
|
+
fieldID: 229241,
|
|
124
|
+
value: "smoke-test"
|
|
125
|
+
},
|
|
126
|
+
KN_DATE: {
|
|
127
|
+
fieldID: 229255,
|
|
128
|
+
value: "06-20-2026"
|
|
129
|
+
},
|
|
130
|
+
defaultNum: {
|
|
131
|
+
fieldID: 229003,
|
|
132
|
+
value: 3
|
|
133
|
+
},
|
|
134
|
+
"8260LUP": {
|
|
135
|
+
fieldID: 228563,
|
|
136
|
+
value: 5108697
|
|
137
|
+
},
|
|
138
|
+
m_selections: {
|
|
139
|
+
fieldID: 229223,
|
|
140
|
+
value: [5158524, 5158525],
|
|
141
|
+
multiSelectAction: "append"
|
|
142
|
+
},
|
|
143
|
+
mullt_env: {
|
|
144
|
+
fieldID: 229425,
|
|
145
|
+
value: [5108697, 5108698],
|
|
146
|
+
multiSelectAction: "replace"
|
|
147
|
+
},
|
|
148
|
+
cascade_mcp: {
|
|
149
|
+
fieldID: 229426,
|
|
150
|
+
value: { parent: 5126498, child: 5126499 }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
expectedOutput: "Bulk updates to execution UDF values will run in the background. Go to 'Scheduled Task' to track the process."
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
hints: [
|
|
158
|
+
"REQUIRED: 'tcRunIDs' must be a non-empty array of numeric Test Case Run IDs. Get IDs from 'Fetch Test Case Runs by Test Suite Run' tool → data[<index>].tcRunID.",
|
|
159
|
+
"REQUIRED: 'UDF' must be an object with at least one field entry. Each key is the UDF field name; each value has 'fieldID' and 'value'.",
|
|
160
|
+
"VALUE FORMATS by field type:\n STRING: plain string, e.g. 'regression-build'\n NUMBER: number, e.g. 3\n DATETIMEPICKER: date string in MM-DD-YYYY format, e.g. '06-20-2026'\n LOOKUPLIST (single select): numeric item ID, e.g. 5108697\n MULTILOOKUPLIST (multi-select): array of item IDs, e.g. [5158524, 5158525]\n CASCADINGLIST: object with parent and child keys, e.g. {parent: 5126498, child: 5126499}. To get valid child IDs for a CASCADINGLIST field — first call 'Fetch Test Run UDF Metadata' to get the parent item IDs from lookupOptions, then call 'Fetch Cascade Child Values' with a parent item ID to get the available child IDs, then use {parent: <parentId>, child: <childId>} as the value here.",
|
|
161
|
+
"MULTILOOKUPLIST — multiSelectAction rules:\n 'append' (default): new values are ADDED to existing selections. Use when user says 'add', 'include', 'append'.\n 'replace': existing selections are CLEARED and replaced with only the new values. Use when user says 'replace', 'set to', 'overwrite', 'change to'.\n If user does not specify, ALWAYS default to 'append'. Never assume 'replace'.",
|
|
162
|
+
"MULTILOOKUPLIST — apply multiSelectAction per field individually. Different multi-select fields in the same request can have different multiSelectAction values.",
|
|
163
|
+
"DATE FORMAT: Always use MM-DD-YYYY format for DATETIMEPICKER fields (e.g. '06-20-2026', not '2026-06-20'). Convert from any user-supplied date format before calling the tool.",
|
|
164
|
+
"ASYNC OPERATION: This API runs in the background. The success response means the job was queued, not that it completed. Tell the user to check 'Scheduled Task' in QMetry UI to track completion.",
|
|
165
|
+
"CRITICAL — FIELD IDs: The 'fieldID' for each UDF entry MUST be the exact numeric ID from QMetry's UDF definition — do NOT guess, infer, or fabricate fieldIDs. If the user has not provided a fieldID, ask the user to supply it or look it up in QMetry admin settings before calling this tool. Using a wrong fieldID will silently fail or update the wrong field.",
|
|
166
|
+
"CRITICAL — WORKFLOW: When user asks to bulk-update a UDF across all executions of a test suite run (e.g. tsRunID 731600), ALWAYS call 'Fetch Test Case Runs by Test Suite Run' first with that tsRunID to collect ALL tcRunIDs from the response (data[].tcRunID), THEN call this tool. Never skip the fetch step or hard-code tcRunIDs.",
|
|
167
|
+
"ALL UDF FIELDS ARE OPTIONAL: Only include the UDF fields the user wants to update. Do not include fields with no change.",
|
|
168
|
+
"tcRunIDs vs entityIDs: This tool uses 'tcRunIDs' (array of numbers). Do NOT confuse with 'Bulk Update Test Case Execution Status' which uses 'entityIDs' (comma-separated string)."
|
|
169
|
+
],
|
|
170
|
+
outputDescription: "JSON object with success status, code 'CO.BULK_TC_EXECUTION_UDF_UPDATE_STARTED', and message confirming the background job was queued.",
|
|
171
|
+
readOnly: false,
|
|
172
|
+
destructive: false,
|
|
173
|
+
idempotent: false
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
title: "Fetch Test Run UDF Metadata",
|
|
177
|
+
toolset: "UDF",
|
|
178
|
+
summary: "Fetch the metadata (field definitions) for all Test Run UDF (User Defined Fields) configured in this QMetry project. Returns each field's name, display label, type, and numeric fieldID (projectUserFieldID) required for bulk updates.",
|
|
179
|
+
handler: QMetryToolsHandlers.FETCH_TEST_RUN_UDF_METADATA,
|
|
180
|
+
inputSchema: FetchTestRunUdfMetadataArgsSchema,
|
|
181
|
+
purpose: "Retrieves the full list of Test Run UDF field definitions from QMetry's admin metadata endpoint. The response includes each field's projectUserFieldID (used as 'fieldID' in Bulk Update Test Run UDFs), field name, display label, field type, and lookup list options for multi-select fields. Call this tool BEFORE 'Bulk Update Test Run UDFs' when the user does not know the numeric fieldID for a UDF.",
|
|
182
|
+
useCases: [
|
|
183
|
+
"Get the fieldID for 'planned_execution_date' before bulk updating it",
|
|
184
|
+
"List all available Test Run UDF fields and their types in the project",
|
|
185
|
+
"Find the lookup list item IDs for a LOOKUPLIST or MULTILOOKUPLIST Test Run UDF",
|
|
186
|
+
"Discover UDF field names and IDs when user says 'what Test Run UDF fields are available'"
|
|
187
|
+
],
|
|
188
|
+
examples: [
|
|
189
|
+
{
|
|
190
|
+
description: "List all Test Run UDF fields in the project",
|
|
191
|
+
parameters: {},
|
|
192
|
+
expectedOutput: "Array of fields with fieldID, name, label, fieldType, and lookupOptions for list-based fields."
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
hints: [
|
|
196
|
+
"ALWAYS call this tool before 'Bulk Update Test Run UDFs' when the user has not explicitly provided a numeric fieldID. The 'fieldID' in the bulk update corresponds to 'projectUserFieldID' in this response.",
|
|
197
|
+
"This tool is the authoritative source of fieldIDs for all Test Run UDF fields — do NOT guess or hard-code fieldIDs.",
|
|
198
|
+
"For LOOKUPLIST and MULTILOOKUPLIST fields, the response 'lookupOptions' contains the valid item IDs and labels to use as values in bulk updates.",
|
|
199
|
+
"DATE fields use MM-DD-YYYY format (e.g. '06-23-2026') when setting values via Bulk Update Test Run UDFs."
|
|
200
|
+
],
|
|
201
|
+
outputDescription: "JSON object with 'fields' array (each item has fieldID, name, label, fieldType, allowBlank, and optional listName/listMasterID) and 'lookupOptions' map for list-based fields.",
|
|
202
|
+
readOnly: true,
|
|
203
|
+
destructive: false,
|
|
204
|
+
idempotent: true
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: "Fetch Test Run UDF Values",
|
|
208
|
+
toolset: "UDF",
|
|
209
|
+
summary: "Fetch the Test Run UDF (User Defined Field) values for all test case runs in a given test suite run. Returns each run's UDF values enriched with field label and type information from metadata. Use this tool for test suite run UDF values (sourceContext='testSuiteRun'). Do NOT use this tool for test case executions — 'Fetch Test Case Executions' already calls metadata internally and returns 'testRunUdfs' in every execution row.",
|
|
210
|
+
handler: QMetryToolsHandlers.FETCH_TEST_RUN_UDF_VALUES,
|
|
211
|
+
inputSchema: FetchTestRunUdfValuesArgsSchema,
|
|
212
|
+
purpose: "Retrieves the test case runs for a test suite run and extracts their Test Run UDF field values. Internally checks the 'hasTcRunUdf' flag — if UDFs are configured, it also calls the metadata API to enrich each run's UDF values with field label, type, and numeric fieldID. Use this tool for test suite run UDF values (sourceContext='testSuiteRun'). If the user request started from 'Fetch Test Case Runs by Test Suite Run', pass the parent response rows through 'sourceRows'. Do NOT use this tool for test case executions: 'Fetch Test Case Executions' already calls metadata internally, parses udfjson per row, and returns 'testRunUdfs' on every execution record — use that data directly. Do NOT use this tool for issue executions; 'Fetch Issue Executions' already reads UDF values from its own udfjson response and enriches them with metadata.",
|
|
213
|
+
useCases: [
|
|
214
|
+
"Show me the UDF values for all runs in test suite run 731600",
|
|
215
|
+
"What is the planned execution date set on each run in this test cycle?",
|
|
216
|
+
"List the Test Run UDF values for test suite run 87039",
|
|
217
|
+
"Fetch test run UDFs of executions for tsRunID 731600"
|
|
218
|
+
],
|
|
219
|
+
examples: [
|
|
220
|
+
{
|
|
221
|
+
description: "Fetch UDF values for all runs in test suite run 731600",
|
|
222
|
+
parameters: {
|
|
223
|
+
tsrunID: "731600",
|
|
224
|
+
sourceContext: "testSuiteRun"
|
|
225
|
+
},
|
|
226
|
+
expectedOutput: "Present as ONE unified table — never as a separate type+value breakdown. Example:\n| Test Case Key | Test Case Summary | Executed Version | Execution Status | Tested By | Environments UDF | Execution Type | Country |\n| MAC-TC-5 | Login - valid credential | 1 | Passed | varis | chrome, edge, safari | Functional | India > i3 |\n| MAC-TC-6 | Login - invalid password | 2 | Failed | john | firefox | Regression | - |\nColumns: Test Case Key (entityKey) | Test Case Summary (summary) | Executed Version (latestVersion) | Execution Status (runStatus) | Tested By | then one column per UDF label. Use the UDF 'label' as column header. Show null UDF values as '-'."
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
hints: [
|
|
230
|
+
"DEFAULT DISPLAY CONTRACT: Always render 'unifiedTableRows' as ONE table. Do not render UDFs as Label | Type | Value rows.",
|
|
231
|
+
"When sourceContext='testSuiteRun', mandatory columns are: Test Case Key | Test Case Summary | Executed Version | Execution Status | Tested By | then one column per UDF label.",
|
|
232
|
+
"PARENT-TO-UDF WORKFLOW: If Fetch Test Case Runs by Test Suite Run was already called, pass parentResponse.data as sourceRows with sourceContext='testSuiteRun'. This preserves identification fields and avoids repeating the same API call.",
|
|
233
|
+
"For prompts like 'Fetch test case runs of VKMCP-TS-1 and its Test Run UDFs': call Fetch Test Case Runs by Test Suite Run, then call this tool with sourceContext='testSuiteRun' and sourceRows=<that response data>.",
|
|
234
|
+
"For prompts like 'Fetch Test Case Executions and show Test Run UDFs': call Fetch Test Case Executions ONLY — that tool already calls metadata internally and returns testRunUdfs on every execution row. Do NOT call this tool for test case executions.",
|
|
235
|
+
"For prompts like 'Fetch Issue Executions and Test Run UDFs': call Fetch Issue Executions only. Do not call this tool, because issue UDF values come from /rest/execution/getExecutionsForIssue udfjson and are already enriched by the issue tool with metadata.",
|
|
236
|
+
"If no parent rows are available, use 'tsrunID' from the 'Fetch Executions by Test Suite' tool (data[<index>].tsRunID field).",
|
|
237
|
+
"'viewId' is auto-resolved from latestViews.TE.viewId — leave blank unless explicitly overriding. It is only needed when sourceRows is not supplied.",
|
|
238
|
+
"If 'hasTcRunUdf' is false in the response, no Test Run UDFs are configured for this project.",
|
|
239
|
+
"The 'testRunUdfs' array on each run contains enriched UDF values with label and fieldID — use fieldID from here when calling 'Bulk Update Test Run UDFs'.",
|
|
240
|
+
"This tool calls UDF metadata internally — no need to call 'Fetch Test Run UDF Metadata' separately when viewing values.",
|
|
241
|
+
"When sourceRows is omitted, this tool also calls the test-suite-run execution list API internally. When sourceRows is provided, it reuses those rows and does not refetch the parent execution list."
|
|
242
|
+
],
|
|
243
|
+
outputDescription: "JSON with hasTcRunUdf boolean, sourceContext, total count, defaultColumns, udfColumns, unifiedTableRows, runs array, and availableUdfFields array describing all UDF fields in the project. Render unifiedTableRows directly as the final table: default identification fields first, then one column per UDF label.",
|
|
244
|
+
readOnly: true,
|
|
245
|
+
destructive: false,
|
|
246
|
+
idempotent: true
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
title: "Fetch Cascade Child Values",
|
|
250
|
+
toolset: "UDF",
|
|
251
|
+
summary: "Fetch the child values of a CASCADINGLIST UDF field for a given parent item ID. Use this before bulk-updating a CASCADINGLIST Test Run UDF to discover valid child item IDs.",
|
|
252
|
+
handler: QMetryToolsHandlers.FETCH_CASCADE_CHILD_VALUES,
|
|
253
|
+
inputSchema: FetchCascadeChildValuesArgsSchema,
|
|
254
|
+
purpose: "Retrieves the available child items for a selected parent item in a CASCADINGLIST UDF field. CASCADINGLIST fields have a two-level hierarchy: a parent value and a dependent child value. The child options are not static — they depend on which parent item is selected. This tool resolves that dependency by returning all valid child item IDs for a given parent ID. Call this tool AFTER 'Fetch Test Run UDF Metadata' to get parent item IDs from lookupOptions, and BEFORE 'Bulk Update Test Run UDFs' to obtain the correct child item ID to include in the update payload.",
|
|
255
|
+
useCases: [
|
|
256
|
+
"Find valid child values for a CASCADINGLIST UDF before bulk-updating test runs",
|
|
257
|
+
"List all child options available under a specific parent cascade item",
|
|
258
|
+
"Resolve child item ID when user knows the parent but not the child",
|
|
259
|
+
"Discover cascade hierarchy for a UDF field before setting it on test executions"
|
|
260
|
+
],
|
|
261
|
+
examples: [
|
|
262
|
+
{
|
|
263
|
+
description: "Fetch child values for parent cascade item with ID 5173534",
|
|
264
|
+
parameters: {
|
|
265
|
+
id: 5173534
|
|
266
|
+
},
|
|
267
|
+
expectedOutput: `{ parentId: 5173534, parentName: "India", children: [{ id: 5173535, name: "i1", uniqueLabel: "i1", isArchived: false }, ...], _note: "Use 'id' from 'children' as the 'child' value in the CASCADINGLIST update." }`
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
description: "Fetch child values including archived items for parent ID 5126498",
|
|
271
|
+
parameters: {
|
|
272
|
+
id: 5126498,
|
|
273
|
+
isArchReq: true
|
|
274
|
+
},
|
|
275
|
+
expectedOutput: '{ parentId: 5126498, parentName: "abc", children: [...], _note: "..." }'
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
hints: [
|
|
279
|
+
"MANDATORY WORKFLOW for CASCADINGLIST bulk update:\n 1. Call 'Fetch Test Run UDF Metadata' → get the CASCADINGLIST field's 'fieldID' (projectUserFieldID) and parent item options from 'lookupOptions'.\n 2. Call this tool ('Fetch Cascade Child Values') with a parent item 'id' from step 1 → get child item IDs.\n 3. Call 'Bulk Update Test Run UDFs' with value: { parent: <parentId>, child: <childId> } and the 'fieldID' from step 1.",
|
|
280
|
+
"The parent item IDs are in the 'lookupOptions' map returned by 'Fetch Test Run UDF Metadata'. Each entry under the field's listName contains items with 'id' — use that 'id' as the 'id' parameter here.",
|
|
281
|
+
"The response 'children' array contains objects with 'id', 'name', 'uniqueLabel', and 'isArchived'. Use 'id' as the 'child' value in the bulk update payload.",
|
|
282
|
+
"Set 'isArchReq: true' only if the user explicitly asks to include archived/inactive child options.",
|
|
283
|
+
"This endpoint requires 'scope' and 'orgcode' headers — these are injected automatically from the session context when 'Set Project Info' has been called. If you see an authorization error, call 'Set Project Info' first.",
|
|
284
|
+
"Do NOT call this tool for STRING, NUMBER, DATETIMEPICKER, LOOKUPLIST, or MULTILOOKUPLIST fields — only CASCADINGLIST (fieldType: 'CASCADINGLIST') fields have a parent-child hierarchy."
|
|
285
|
+
],
|
|
286
|
+
outputDescription: "JSON object with 'parentId' (the input ID), 'parentName' (the parent item's display name), 'children' array (each item has id, name, uniqueLabel, isArchived), and a '_note' explaining how to use the IDs.",
|
|
287
|
+
readOnly: true,
|
|
288
|
+
destructive: false,
|
|
289
|
+
idempotent: true
|
|
290
|
+
}
|
|
291
|
+
];
|
|
292
|
+
export {
|
|
293
|
+
UDF_TOOLS
|
|
294
|
+
};
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { UDF_MODULES, UDF_FIELD_TYPES } from "../config/constants.js";
|
|
2
|
+
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
|
|
3
|
+
import { qmetryRequest } from "./api/client-api.js";
|
|
4
|
+
import { resolveDefaults } from "./utils.js";
|
|
5
|
+
const TEST_RUN_UDF_DISPLAY_COLUMNS = {
|
|
6
|
+
testSuiteRun: [
|
|
7
|
+
{ header: "Test Case Key", fields: ["entityKey"] },
|
|
8
|
+
{ header: "Test Case Summary", fields: ["summary"] },
|
|
9
|
+
{
|
|
10
|
+
header: "Executed Version",
|
|
11
|
+
fields: ["latestVersion", "executedVersion"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
header: "Execution Status",
|
|
15
|
+
fields: ["runStatus", "runStatusName", "executionStatus"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
header: "Tested By",
|
|
19
|
+
fields: ["testedBy", "executedBy", "executionCreatedByLoginAlias"]
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
testCaseExecutions: [
|
|
23
|
+
{ header: "Test Suite Key", fields: ["tsEntityKey"] },
|
|
24
|
+
{
|
|
25
|
+
header: "Test Suite Name",
|
|
26
|
+
fields: ["testsuiteName", "testSuiteName", "tsName"]
|
|
27
|
+
},
|
|
28
|
+
{ header: "Release", fields: ["releaseName"] },
|
|
29
|
+
{ header: "Cycle", fields: ["cycleName"] },
|
|
30
|
+
{ header: "Platform", fields: ["platform", "platformName"] },
|
|
31
|
+
{ header: "Executed Version", fields: ["executedVersion"] },
|
|
32
|
+
{
|
|
33
|
+
header: "Execution Status",
|
|
34
|
+
fields: ["executionStatus", "runStatusName", "runStatus"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
header: "Tested By",
|
|
38
|
+
fields: ["testedBy", "executedBy", "executionCreatedByLoginAlias"]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
function stripHtml(value) {
|
|
43
|
+
if (!/<[^>]+>/.test(value)) return value;
|
|
44
|
+
return value.replace(/<[^>]*>/g, " ").replace(/&(nbsp|amp|lt|gt|quot);/g, (_, entity) => {
|
|
45
|
+
if (entity === "nbsp") return " ";
|
|
46
|
+
if (entity === "amp") return "&";
|
|
47
|
+
if (entity === "lt") return "<";
|
|
48
|
+
if (entity === "gt") return ">";
|
|
49
|
+
if (entity === "quot") return '"';
|
|
50
|
+
return `&${entity};`;
|
|
51
|
+
}).replace(/\s+/g, " ").trim();
|
|
52
|
+
}
|
|
53
|
+
function readFirstAvailable(row, fields) {
|
|
54
|
+
for (const field of fields) {
|
|
55
|
+
if (Object.hasOwn(row, field) && row[field] !== null && row[field] !== "") {
|
|
56
|
+
return row[field];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function formatTableValue(value) {
|
|
62
|
+
if (value === null || value === void 0 || value === "") return "-";
|
|
63
|
+
if (Array.isArray(value)) return value.length ? value.join(", ") : "-";
|
|
64
|
+
if (typeof value === "object") {
|
|
65
|
+
const objectValue = value;
|
|
66
|
+
if ("parent" in objectValue || "child" in objectValue) {
|
|
67
|
+
return [objectValue.parent, objectValue.child].filter((part) => part !== null && part !== void 0 && part !== "").join(" > ");
|
|
68
|
+
}
|
|
69
|
+
return JSON.stringify(value);
|
|
70
|
+
}
|
|
71
|
+
return String(value);
|
|
72
|
+
}
|
|
73
|
+
function parseRawUdfs(row) {
|
|
74
|
+
if (Array.isArray(row.testRunUdfs)) {
|
|
75
|
+
return Object.fromEntries(
|
|
76
|
+
row.testRunUdfs.map((udf) => [udf.name, udf.value])
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (row.testRunUdfs && typeof row.testRunUdfs === "object") {
|
|
80
|
+
return row.testRunUdfs;
|
|
81
|
+
}
|
|
82
|
+
if (row.udfjson) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(row.udfjson);
|
|
85
|
+
} catch {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
function enrichUdfsForRow(row, fieldDefs) {
|
|
92
|
+
const rawUdfs = parseRawUdfs(row);
|
|
93
|
+
const hasMetadata = Object.keys(fieldDefs).length > 0;
|
|
94
|
+
if (!hasMetadata && Array.isArray(row.testRunUdfs)) return row.testRunUdfs;
|
|
95
|
+
if (hasMetadata) {
|
|
96
|
+
return Object.values(fieldDefs).map((def) => {
|
|
97
|
+
const fieldName = def.name ?? "";
|
|
98
|
+
const rawValue = Object.hasOwn(rawUdfs, fieldName) ? rawUdfs[fieldName] : null;
|
|
99
|
+
const value = typeof rawValue === "string" ? stripHtml(rawValue) : rawValue;
|
|
100
|
+
return {
|
|
101
|
+
name: fieldName,
|
|
102
|
+
label: def.fieldLabel ?? fieldName,
|
|
103
|
+
fieldID: def.projectUserFieldID ?? null,
|
|
104
|
+
fieldType: def.fieldTypeName ?? "UNKNOWN",
|
|
105
|
+
value
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return Object.entries(rawUdfs).map(([name, rawValue]) => ({
|
|
110
|
+
name,
|
|
111
|
+
label: name,
|
|
112
|
+
fieldID: null,
|
|
113
|
+
fieldType: "UNKNOWN",
|
|
114
|
+
value: typeof rawValue === "string" ? stripHtml(rawValue) : rawValue
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
function buildUnifiedTableRows(rows, sourceContext) {
|
|
118
|
+
const contextColumns = TEST_RUN_UDF_DISPLAY_COLUMNS[sourceContext];
|
|
119
|
+
return rows.map((row) => {
|
|
120
|
+
const tableRow = {};
|
|
121
|
+
for (const column of contextColumns) {
|
|
122
|
+
tableRow[column.header] = formatTableValue(
|
|
123
|
+
readFirstAvailable(row, column.fields)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
for (const udf of row.testRunUdfs ?? []) {
|
|
127
|
+
tableRow[udf.label ?? udf.name] = formatTableValue(udf.value);
|
|
128
|
+
}
|
|
129
|
+
return tableRow;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async function bulkUpdateTestRunUdfs(token, baseUrl, project, payload) {
|
|
133
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(
|
|
134
|
+
baseUrl,
|
|
135
|
+
project
|
|
136
|
+
);
|
|
137
|
+
if (!Array.isArray(payload.tcRunIDs) || payload.tcRunIDs.length === 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"[bulkUpdateTestRunUdfs] Missing or invalid required parameter: 'tcRunIDs'. Must be a non-empty array of numeric Test Case Run IDs."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (!payload.UDF || typeof payload.UDF !== "object" || Object.keys(payload.UDF).length === 0) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"[bulkUpdateTestRunUdfs] Missing or invalid required parameter: 'UDF'. Must be an object with at least one UDF field entry."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const normalizedUDF = {};
|
|
148
|
+
for (const [fieldName, fieldEntry] of Object.entries(payload.UDF)) {
|
|
149
|
+
const entry = {
|
|
150
|
+
fieldID: fieldEntry.fieldID,
|
|
151
|
+
value: fieldEntry.value
|
|
152
|
+
};
|
|
153
|
+
if (Array.isArray(fieldEntry.value)) {
|
|
154
|
+
entry.multiSelectAction = fieldEntry.multiSelectAction ?? "append";
|
|
155
|
+
}
|
|
156
|
+
normalizedUDF[fieldName] = entry;
|
|
157
|
+
}
|
|
158
|
+
const body = {
|
|
159
|
+
tcRunIDs: payload.tcRunIDs,
|
|
160
|
+
UDF: normalizedUDF
|
|
161
|
+
};
|
|
162
|
+
return qmetryRequest({
|
|
163
|
+
method: "PUT",
|
|
164
|
+
path: QMETRY_PATHS.UDF.BULK_UPDATE_TEST_RUN_UDFS,
|
|
165
|
+
token,
|
|
166
|
+
project: resolvedProject,
|
|
167
|
+
baseUrl: resolvedBaseUrl,
|
|
168
|
+
body,
|
|
169
|
+
scopeId: payload.scopeId,
|
|
170
|
+
orgCode: payload.orgCode
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function fetchCascadeChildValues(token, baseUrl, project, payload) {
|
|
174
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(
|
|
175
|
+
baseUrl,
|
|
176
|
+
project
|
|
177
|
+
);
|
|
178
|
+
if (typeof payload.id !== "number" || payload.id <= 0) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"[fetchCascadeChildValues] Missing or invalid required parameter: 'id'. Must be a positive integer representing the parent cascade item ID."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const raw = await qmetryRequest({
|
|
184
|
+
method: "POST",
|
|
185
|
+
path: QMETRY_PATHS.UDF.FETCH_CASCADE_CHILD_VALUES,
|
|
186
|
+
token,
|
|
187
|
+
project: resolvedProject,
|
|
188
|
+
baseUrl: resolvedBaseUrl,
|
|
189
|
+
body: {
|
|
190
|
+
id: payload.id,
|
|
191
|
+
isArchReq: payload.isArchReq ?? false
|
|
192
|
+
},
|
|
193
|
+
scopeId: payload.scopeId,
|
|
194
|
+
orgCode: payload.orgCode,
|
|
195
|
+
extraHeaders: {
|
|
196
|
+
action: "fetch-cascade-children",
|
|
197
|
+
screenname: "EXECUTION RUN"
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
const entries = Object.entries(raw);
|
|
201
|
+
if (entries.length === 0) {
|
|
202
|
+
return {
|
|
203
|
+
parentId: payload.id,
|
|
204
|
+
parentName: null,
|
|
205
|
+
children: [],
|
|
206
|
+
_note: "No child values found for this parent cascade item."
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const [parentName, children] = entries[0];
|
|
210
|
+
return {
|
|
211
|
+
parentId: payload.id,
|
|
212
|
+
parentName,
|
|
213
|
+
children: children.map((c) => ({
|
|
214
|
+
id: c.id,
|
|
215
|
+
name: c.name,
|
|
216
|
+
uniqueLabel: c.uniqueLabel,
|
|
217
|
+
isArchived: c.isArchived
|
|
218
|
+
})),
|
|
219
|
+
_note: "Use 'id' from 'children' as the 'child' value in the CASCADINGLIST update: { parent: <parentId>, child: <children[].id> }"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function fetchUdfFieldTypes() {
|
|
223
|
+
return [...UDF_FIELD_TYPES];
|
|
224
|
+
}
|
|
225
|
+
async function fetchUdfModules() {
|
|
226
|
+
return [...UDF_MODULES];
|
|
227
|
+
}
|
|
228
|
+
async function fetchTestRunUdfMetadata(token, baseUrl, project, payload) {
|
|
229
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(
|
|
230
|
+
baseUrl,
|
|
231
|
+
project
|
|
232
|
+
);
|
|
233
|
+
const raw = await qmetryRequest({
|
|
234
|
+
method: "POST",
|
|
235
|
+
path: QMETRY_PATHS.UDF.TEST_RUN_UDF_METADATA,
|
|
236
|
+
token,
|
|
237
|
+
project: resolvedProject,
|
|
238
|
+
baseUrl: resolvedBaseUrl,
|
|
239
|
+
body: { entityType: "TCR" },
|
|
240
|
+
scopeId: payload.scopeId,
|
|
241
|
+
orgCode: payload.orgCode,
|
|
242
|
+
extraHeaders: {
|
|
243
|
+
action: "fetch-steps",
|
|
244
|
+
screenname: "EXECUTION RUN"
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const tcrFields = raw.qmUDF?.TCR ?? {};
|
|
248
|
+
const fields = Object.entries(tcrFields).map(([key, def]) => ({
|
|
249
|
+
fieldKey: key,
|
|
250
|
+
// e.g. "FLD.planned_execution_date"
|
|
251
|
+
fieldID: def.projectUserFieldID,
|
|
252
|
+
// use as fieldID in bulk update
|
|
253
|
+
name: def.name,
|
|
254
|
+
label: def.fieldLabel,
|
|
255
|
+
fieldType: def.fieldTypeName,
|
|
256
|
+
allowBlank: def.allowBlank,
|
|
257
|
+
...def.qmListName ? { listName: def.qmListName } : {},
|
|
258
|
+
...def.listMasterID ? { listMasterID: def.listMasterID } : {}
|
|
259
|
+
}));
|
|
260
|
+
return {
|
|
261
|
+
fields,
|
|
262
|
+
lookupOptions: raw.qmUDFList ?? {},
|
|
263
|
+
_note: "Use 'fieldID' (projectUserFieldID) when calling 'Bulk Update Test Run UDFs'. For LOOKUPLIST/MULTILOOKUPLIST/CASCADINGLIST fields, use IDs from 'lookupOptions' as the value."
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
async function fetchTestRunUdfValues(token, baseUrl, project, payload) {
|
|
267
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(
|
|
268
|
+
baseUrl,
|
|
269
|
+
project
|
|
270
|
+
);
|
|
271
|
+
const sourceContext = payload.sourceContext ?? "testSuiteRun";
|
|
272
|
+
if ((!Array.isArray(payload.sourceRows) || payload.sourceRows.length === 0) && (typeof payload.tsrunID !== "string" || !payload.tsrunID)) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"[fetchTestRunUdfValues] Missing or invalid required parameter: 'tsrunID'. Provide 'tsrunID' or pass parent tool rows in 'sourceRows'."
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if ((!Array.isArray(payload.sourceRows) || payload.sourceRows.length === 0) && typeof payload.viewId !== "number") {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"[fetchTestRunUdfValues] Missing or invalid required parameter: 'viewId'. Provide 'viewId' or pass parent tool rows in 'sourceRows'."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const usingSourceRows = Array.isArray(payload.sourceRows) && payload.sourceRows.length > 0;
|
|
283
|
+
const runsResponse = usingSourceRows ? {
|
|
284
|
+
data: payload.sourceRows,
|
|
285
|
+
hasTcRunUdf: true,
|
|
286
|
+
total: payload.sourceRows?.length ?? 0
|
|
287
|
+
} : await qmetryRequest({
|
|
288
|
+
method: "POST",
|
|
289
|
+
path: QMETRY_PATHS.TESTSUITE.GET_TESTCASE_RUNS_BY_TESTSUITE_RUN,
|
|
290
|
+
token,
|
|
291
|
+
project: resolvedProject,
|
|
292
|
+
baseUrl: resolvedBaseUrl,
|
|
293
|
+
body: {
|
|
294
|
+
tsrunID: payload.tsrunID,
|
|
295
|
+
viewId: payload.viewId,
|
|
296
|
+
start: payload.startIndex ?? 0,
|
|
297
|
+
page: 1,
|
|
298
|
+
limit: payload.size ?? 50
|
|
299
|
+
},
|
|
300
|
+
scopeId: payload.scopeId,
|
|
301
|
+
orgCode: payload.orgCode
|
|
302
|
+
});
|
|
303
|
+
if (runsResponse.hasTcRunUdf === false) {
|
|
304
|
+
return {
|
|
305
|
+
tsRunID: payload.tsrunID,
|
|
306
|
+
hasTcRunUdf: false,
|
|
307
|
+
total: runsResponse.total ?? 0,
|
|
308
|
+
runs: [],
|
|
309
|
+
_note: "No Test Run UDFs are configured for this project. A project administrator must define Test Run UDF fields before values can be fetched."
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
let fieldDefs = {};
|
|
313
|
+
let lookupOptions = {};
|
|
314
|
+
try {
|
|
315
|
+
const meta = await qmetryRequest({
|
|
316
|
+
method: "POST",
|
|
317
|
+
path: QMETRY_PATHS.UDF.TEST_RUN_UDF_METADATA,
|
|
318
|
+
token,
|
|
319
|
+
project: resolvedProject,
|
|
320
|
+
baseUrl: resolvedBaseUrl,
|
|
321
|
+
body: { entityType: "TCR" },
|
|
322
|
+
scopeId: payload.scopeId,
|
|
323
|
+
orgCode: payload.orgCode,
|
|
324
|
+
extraHeaders: {
|
|
325
|
+
action: "fetch-steps",
|
|
326
|
+
screenname: "EXECUTION RUN"
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
fieldDefs = meta.qmUDF?.TCR ?? {};
|
|
330
|
+
lookupOptions = meta.qmUDFList ?? {};
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
const rows = runsResponse.data ?? [];
|
|
334
|
+
const runs = rows.map((row) => {
|
|
335
|
+
const { udfjson: _udfjson, ...rowWithoutRawUdfJson } = row;
|
|
336
|
+
const enrichedUdfs = enrichUdfsForRow(row, fieldDefs);
|
|
337
|
+
return {
|
|
338
|
+
...rowWithoutRawUdfJson,
|
|
339
|
+
testRunUdfs: enrichedUdfs
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
const unifiedTableRows = buildUnifiedTableRows(runs, sourceContext);
|
|
343
|
+
const udfColumns = Object.values(fieldDefs).map(
|
|
344
|
+
(def) => def.fieldLabel ?? def.name ?? "Unnamed UDF"
|
|
345
|
+
);
|
|
346
|
+
return {
|
|
347
|
+
tsRunID: payload.tsrunID,
|
|
348
|
+
sourceContext,
|
|
349
|
+
hasTcRunUdf: runsResponse.hasTcRunUdf ?? true,
|
|
350
|
+
total: runsResponse.total ?? rows.length,
|
|
351
|
+
defaultColumns: TEST_RUN_UDF_DISPLAY_COLUMNS[sourceContext].map(
|
|
352
|
+
(column) => column.header
|
|
353
|
+
),
|
|
354
|
+
udfColumns,
|
|
355
|
+
unifiedTableRows,
|
|
356
|
+
runs,
|
|
357
|
+
availableUdfFields: Object.values(fieldDefs).map((def) => ({
|
|
358
|
+
fieldID: def.projectUserFieldID,
|
|
359
|
+
name: def.name,
|
|
360
|
+
label: def.fieldLabel,
|
|
361
|
+
fieldType: def.fieldTypeName,
|
|
362
|
+
...def.qmListName ? {
|
|
363
|
+
listName: def.qmListName,
|
|
364
|
+
lookupOptions: lookupOptions[def.qmListName] ?? []
|
|
365
|
+
} : {}
|
|
366
|
+
}))
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
export {
|
|
370
|
+
bulkUpdateTestRunUdfs,
|
|
371
|
+
fetchCascadeChildValues,
|
|
372
|
+
fetchTestRunUdfMetadata,
|
|
373
|
+
fetchTestRunUdfValues,
|
|
374
|
+
fetchUdfFieldTypes,
|
|
375
|
+
fetchUdfModules
|
|
376
|
+
};
|