@testomatio/mcp 2.1.2 → 2.2.0-beta.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.
- package/README.md +25 -6
- package/package.json +1 -1
- package/src/api/testomatio-client.js +0 -4
- package/src/cli/main.js +4 -0
- package/src/config/constants.js +4 -0
- package/src/config/load-config.js +10 -1
- package/src/index.js +9 -1
- package/src/mcp/configs/entity-crud-config.js +0 -6
- package/src/mcp/definitions/issues.js +0 -37
- package/src/mcp/definitions/plans.js +0 -39
- package/src/mcp/definitions/projects.js +12 -0
- package/src/mcp/definitions/requirements.js +0 -27
- package/src/mcp/definitions/runs.js +0 -23
- package/src/mcp/definitions/suites.js +10 -37
- package/src/mcp/definitions/system.js +8 -0
- package/src/mcp/definitions/testruns.js +14 -123
- package/src/mcp/definitions/tests.js +28 -27
- package/src/mcp/definitions/tql-reference.js +31 -51
- package/src/mcp/list-projection.js +148 -0
- package/src/mcp/registry/attachments.js +2 -2
- package/src/mcp/registry/handlers.js +53 -24
- package/src/mcp/registry/issues.js +6 -7
- package/src/mcp/registry/listings.js +24 -53
- package/src/mcp/tool-definitions.js +5 -2
- package/src/mcp/tool-profiles.js +37 -0
- package/src/mcp/tool-registry.js +30 -2
|
@@ -59,7 +59,14 @@ export const TESTS_TOOLS = [
|
|
|
59
59
|
"type": "string"
|
|
60
60
|
},
|
|
61
61
|
"priority": {
|
|
62
|
-
"type": "string"
|
|
62
|
+
"type": "string",
|
|
63
|
+
"enum": [
|
|
64
|
+
"low",
|
|
65
|
+
"normal",
|
|
66
|
+
"important",
|
|
67
|
+
"high",
|
|
68
|
+
"critical"
|
|
69
|
+
]
|
|
63
70
|
},
|
|
64
71
|
"assigned_to": {
|
|
65
72
|
"type": "string"
|
|
@@ -68,7 +75,12 @@ export const TESTS_TOOLS = [
|
|
|
68
75
|
"type": "string"
|
|
69
76
|
},
|
|
70
77
|
"state": {
|
|
71
|
-
"type": "string"
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": [
|
|
80
|
+
"manual",
|
|
81
|
+
"detached",
|
|
82
|
+
"automated"
|
|
83
|
+
]
|
|
72
84
|
},
|
|
73
85
|
"link": {
|
|
74
86
|
"type": "array",
|
|
@@ -135,7 +147,14 @@ export const TESTS_TOOLS = [
|
|
|
135
147
|
"type": "string"
|
|
136
148
|
},
|
|
137
149
|
"priority": {
|
|
138
|
-
"type": "string"
|
|
150
|
+
"type": "string",
|
|
151
|
+
"enum": [
|
|
152
|
+
"low",
|
|
153
|
+
"normal",
|
|
154
|
+
"important",
|
|
155
|
+
"high",
|
|
156
|
+
"critical"
|
|
157
|
+
]
|
|
139
158
|
},
|
|
140
159
|
"assigned_to": {
|
|
141
160
|
"type": "string"
|
|
@@ -144,7 +163,12 @@ export const TESTS_TOOLS = [
|
|
|
144
163
|
"type": "string"
|
|
145
164
|
},
|
|
146
165
|
"state": {
|
|
147
|
-
"type": "string"
|
|
166
|
+
"type": "string",
|
|
167
|
+
"enum": [
|
|
168
|
+
"manual",
|
|
169
|
+
"detached",
|
|
170
|
+
"automated"
|
|
171
|
+
]
|
|
148
172
|
},
|
|
149
173
|
"sync": {
|
|
150
174
|
"type": "boolean"
|
|
@@ -207,29 +231,6 @@ export const TESTS_TOOLS = [
|
|
|
207
231
|
"additionalProperties": false
|
|
208
232
|
}
|
|
209
233
|
},
|
|
210
|
-
{
|
|
211
|
-
"name": "tests_search",
|
|
212
|
-
"description": `Search tests using TQL (delegates to tests_list). ${TESTS_TQL_REFERENCE}`,
|
|
213
|
-
"inputSchema": {
|
|
214
|
-
"type": "object",
|
|
215
|
-
"properties": {
|
|
216
|
-
"page": {
|
|
217
|
-
"type": "integer",
|
|
218
|
-
"minimum": 1
|
|
219
|
-
},
|
|
220
|
-
"per_page": {
|
|
221
|
-
"type": "integer",
|
|
222
|
-
"minimum": 1,
|
|
223
|
-
"maximum": 100
|
|
224
|
-
},
|
|
225
|
-
"tql": {
|
|
226
|
-
"type": "string",
|
|
227
|
-
"description": TESTS_TQL_INPUT_DESCRIPTION
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
"additionalProperties": false
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
234
|
{
|
|
234
235
|
"name": "tests_issues_list",
|
|
235
236
|
"description": "List linked issues for a test (/api/v2/{project_id}/issues?test_id=...)",
|
|
@@ -89,62 +89,42 @@ const RUNS_TQL_EXAMPLES = [
|
|
|
89
89
|
const COMMON_TQL_SYNTAX =
|
|
90
90
|
"Supported syntax includes logical operators `and`, `or`, `not`, equality operators `==` and `!=`, list membership `in [...]`, `%` for partial text match on supported text fields, and parentheses for grouping. Ordered comparisons `>`, `<`, `>=`, `<=` are for ordered fields such as `priority`, dates, and numeric counters/durations. Use quotes for string values, for example `state == 'automated'`.";
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`Documented test variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
95
|
-
`Documented examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
96
|
-
'Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.';
|
|
92
|
+
const TESTS_TQL_FIELDS = TESTS_TQL_VARIABLES.join(', ');
|
|
93
|
+
const RUNS_TQL_FIELDS = RUNS_TQL_VARIABLES.join(', ');
|
|
97
94
|
|
|
98
|
-
export const
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
export const TQL_FULL_REFERENCE = [
|
|
96
|
+
'TQL (Testomat.io Query Language) is a string expression used to filter tests and runs.',
|
|
97
|
+
COMMON_TQL_SYNTAX,
|
|
98
|
+
'',
|
|
99
|
+
`Tests filter variables: ${TESTS_TQL_VARIABLES.map((v) => `\`${v}\``).join(', ')}.`,
|
|
100
|
+
`Tests examples: ${TESTS_TQL_EXAMPLES.map((e) => `\`${e}\``).join(', ')}.`,
|
|
101
|
+
'',
|
|
102
|
+
`Runs filter variables: ${RUNS_TQL_VARIABLES.map((v) => `\`${v}\``).join(', ')}.`,
|
|
103
|
+
'Runs also support boolean flags used without comparison, e.g. `failed`, `finished`, `automated`, `with_defect`.',
|
|
104
|
+
`Runs examples: ${RUNS_TQL_EXAMPLES.map((e) => `\`${e}\``).join(', ')}.`,
|
|
105
|
+
'',
|
|
106
|
+
'Parameter name: tests/runs/plans use `tql`; analytics tools use `q`.',
|
|
107
|
+
'Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.',
|
|
108
|
+
].join('\n');
|
|
101
109
|
|
|
110
|
+
export const TESTS_TQL_REFERENCE =
|
|
111
|
+
'Filter tests with `tql` (TQL); call `tql_help` for the syntax and full field list.';
|
|
102
112
|
export const RUNS_TQL_REFERENCE =
|
|
103
|
-
`
|
|
104
|
-
'Runs also support boolean flags without comparison such as `failed`, `finished`, `automated`, or `with_defect`. ' +
|
|
105
|
-
`Documented run variables: ${RUNS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
106
|
-
`Documented examples: ${RUNS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
107
|
-
'Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.';
|
|
108
|
-
|
|
109
|
-
export const RUNS_TQL_INPUT_DESCRIPTION =
|
|
110
|
-
`TQL filter for runs. Documented variables: ${RUNS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
111
|
-
`Examples: ${RUNS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}.`;
|
|
112
|
-
|
|
113
|
+
'Filter runs with `tql` (TQL); runs also accept boolean flags. Call `tql_help` for the syntax and full field list.';
|
|
113
114
|
export const PLANS_TQL_REFERENCE =
|
|
114
|
-
|
|
115
|
-
`Plan TQL uses documented test variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
116
|
-
`Documented examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
117
|
-
'Use `tql` when you want the API to resolve matching tests automatically instead of sending explicit `test_ids`. Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.';
|
|
118
|
-
|
|
119
|
-
export const PLANS_TQL_INPUT_DESCRIPTION =
|
|
120
|
-
'TQL filter for selecting tests included in the plan. ' +
|
|
121
|
-
`Documented test variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
122
|
-
`Examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}.`;
|
|
123
|
-
|
|
115
|
+
'Select tests for the plan with `tql` (TQL); the API resolves matching tests. Call `tql_help` for the syntax and full field list.';
|
|
124
116
|
export const ANALYTICS_TESTS_TQL_REFERENCE =
|
|
125
|
-
|
|
126
|
-
'For analytics tools, the API parameter name is `q`, not `tql`. ' +
|
|
127
|
-
`Documented analytics test variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
128
|
-
`Documented examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
129
|
-
'Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.';
|
|
130
|
-
|
|
131
|
-
export const ANALYTICS_TESTS_TQL_INPUT_DESCRIPTION =
|
|
132
|
-
'TQL filter for analytics test reports. The API parameter name is `q`, not `tql`. ' +
|
|
133
|
-
`Documented variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
134
|
-
`Examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}.`;
|
|
135
|
-
|
|
117
|
+
'Filter analytics test reports with `q` (TQL). Call `tql_help` for the syntax and full field list.';
|
|
136
118
|
export const ANALYTICS_STATS_TQL_REFERENCE =
|
|
137
|
-
|
|
138
|
-
'For analytics tools, the API parameter name is `q`, not `tql`. ' +
|
|
139
|
-
'According to the official Analytics docs, analytics queries are configured using supported query variables for two data sources: `Tests Variables` and `Runs Variables`. ' +
|
|
140
|
-
`Documented Tests Variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
141
|
-
`Documented Runs Variables: ${RUNS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
142
|
-
`Documented Tests examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
143
|
-
`Documented Runs examples: ${RUNS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
144
|
-
'Use Tests Variables for test-centric filters and Runs Variables for run-centric filters such as `plan`, `rungroup`, `env`, `finished_at`, or `has_test_tag`. Do not invent undocumented fields or syntax. If a query fails, simplify it to one documented predicate.';
|
|
119
|
+
'Filter analytics aggregated reports with `q` (TQL; tests or runs variables). Call `tql_help` for the syntax and full field list.';
|
|
145
120
|
|
|
121
|
+
export const TESTS_TQL_INPUT_DESCRIPTION =
|
|
122
|
+
`TQL filter for tests. Fields: ${TESTS_TQL_FIELDS}. Call \`tql_help\` for syntax. Examples: \`priority == 'high'\`, \`state == 'automated'\`, \`suite % 'Checkout'\`.`;
|
|
123
|
+
export const RUNS_TQL_INPUT_DESCRIPTION =
|
|
124
|
+
`TQL filter for runs. Fields: ${RUNS_TQL_FIELDS}. Call \`tql_help\` for syntax. Examples: \`finished and with_defect\`, \`env in ['Windows', 'Linux']\`, \`has_retries > 2\`.`;
|
|
125
|
+
export const PLANS_TQL_INPUT_DESCRIPTION =
|
|
126
|
+
`TQL to select tests for the plan. Fields: ${TESTS_TQL_FIELDS}. Call \`tql_help\` for syntax. Examples: \`priority == 'high'\`, \`tag in ['smoke', 'stage1']\`.`;
|
|
127
|
+
export const ANALYTICS_TESTS_TQL_INPUT_DESCRIPTION =
|
|
128
|
+
`TQL filter (param \`q\`) for analytics test reports. Fields: ${TESTS_TQL_FIELDS}. Call \`tql_help\` for syntax. Examples: \`priority == 'high'\`, \`state == 'automated'\`.`;
|
|
146
129
|
export const ANALYTICS_STATS_TQL_INPUT_DESCRIPTION =
|
|
147
|
-
|
|
148
|
-
`Documented Tests Variables: ${TESTS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
149
|
-
`Documented Runs Variables: ${RUNS_TQL_VARIABLES.map((item) => `\`${item}\``).join(', ')}. ` +
|
|
150
|
-
`Examples: ${TESTS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}, ${RUNS_TQL_EXAMPLES.map((item) => `\`${item}\``).join(', ')}.`;
|
|
130
|
+
`TQL filter (param \`q\`) for analytics reports. Test fields: ${TESTS_TQL_FIELDS}. Run fields: ${RUNS_TQL_FIELDS}. Call \`tql_help\` for syntax. Examples: \`priority == 'high'\`, \`finished_at >= '2025-07-01' and failed\`.`;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slim projection for list-tool responses.
|
|
3
|
+
*
|
|
4
|
+
* List tools return only what an index view needs: entity-specific heavy fields
|
|
5
|
+
* and null values are dropped.
|
|
6
|
+
* `verbose:true` returns the full body; `fields:[...]` selects a custom field set.
|
|
7
|
+
* Every shaped response carries a `_view` marker so the caller knows it is partial.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const HEAVY_FIELDS_BY_ENTITY = {
|
|
11
|
+
tests: new Set(['description', 'code', 'cleanTitle', 'publicTitle']),
|
|
12
|
+
suites: new Set(['description', 'code', 'cleanTitle', 'publicTitle']),
|
|
13
|
+
runs: new Set(['description']),
|
|
14
|
+
testruns: new Set(['description', 'code']),
|
|
15
|
+
rungroups: new Set(['description']),
|
|
16
|
+
steps: new Set(['description']),
|
|
17
|
+
snippets: new Set(['description']),
|
|
18
|
+
plans: new Set(['description']),
|
|
19
|
+
requirements: new Set(['description']),
|
|
20
|
+
analytics_tests: new Set(['description', 'code', 'cleanTitle', 'publicTitle']),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function isNullish(value) {
|
|
24
|
+
return value === null || value === undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pickFields(item, fields) {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const key of fields) {
|
|
30
|
+
if (!(key in item)) continue;
|
|
31
|
+
const value = item[key];
|
|
32
|
+
if (isNullish(value)) continue;
|
|
33
|
+
result[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stripHeavy(item, heavyFields) {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const [key, value] of Object.entries(item)) {
|
|
41
|
+
if (heavyFields.has(key)) continue;
|
|
42
|
+
if (isNullish(value)) continue;
|
|
43
|
+
result[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function mapItems(items, fn) {
|
|
49
|
+
return items.map((item) =>
|
|
50
|
+
item && typeof item === 'object' && !Array.isArray(item) ? fn(item) : item
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function applyToEnvelope(payload, fn) {
|
|
55
|
+
if (!payload || typeof payload !== 'object') return payload;
|
|
56
|
+
if (Array.isArray(payload)) return mapItems(payload, fn);
|
|
57
|
+
if (Array.isArray(payload.data)) {
|
|
58
|
+
return { ...payload, data: mapItems(payload.data, fn) };
|
|
59
|
+
}
|
|
60
|
+
return payload;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const VIEW_NOTE =
|
|
64
|
+
'Entity-specific heavy fields and null values removed. Pass verbose:true for full bodies or fields:[...] to select fields.';
|
|
65
|
+
|
|
66
|
+
function withViewMarker(result, note = VIEW_NOTE) {
|
|
67
|
+
if (
|
|
68
|
+
result &&
|
|
69
|
+
typeof result === 'object' &&
|
|
70
|
+
!Array.isArray(result) &&
|
|
71
|
+
Array.isArray(result.data)
|
|
72
|
+
) {
|
|
73
|
+
return { ...result, _view: note };
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Shape a list payload: drop entity-specific heavy and null fields by default, full body on `verbose`,
|
|
80
|
+
* custom field set on `fields`. A `_view` marker is added to shaped envelopes.
|
|
81
|
+
*
|
|
82
|
+
* @param {*} payload raw API response ({ data, meta } or a bare array)
|
|
83
|
+
* @param {{ verbose?: boolean, fields?: string[], entity?: string }} [opts]
|
|
84
|
+
*/
|
|
85
|
+
export function slimList(payload, { verbose = false, fields, entity } = {}) {
|
|
86
|
+
if (verbose) {
|
|
87
|
+
return payload;
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(fields) && fields.length) {
|
|
90
|
+
return withViewMarker(
|
|
91
|
+
applyToEnvelope(payload, (item) => pickFields(item, fields)),
|
|
92
|
+
`Custom fields [${fields.join(', ')}]. Pass verbose:true for the full object.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const heavyFields = HEAVY_FIELDS_BY_ENTITY[entity] ?? new Set();
|
|
96
|
+
return withViewMarker(applyToEnvelope(payload, (item) => stripHeavy(item, heavyFields)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Ask the API to omit heavy list fields only when MCP does not need them for a
|
|
101
|
+
* full or custom projection.
|
|
102
|
+
*/
|
|
103
|
+
export function backendSlimQuery({ verbose = false, fields } = {}) {
|
|
104
|
+
return !verbose && !(Array.isArray(fields) && fields.length) ? { slim: true } : {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const LIST_OPTION_PROPERTIES = {
|
|
108
|
+
verbose: {
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
default: false,
|
|
111
|
+
description:
|
|
112
|
+
'Return full response bodies. Default strips entity-specific heavy fields and null values; set true when you need those.',
|
|
113
|
+
},
|
|
114
|
+
fields: {
|
|
115
|
+
type: 'array',
|
|
116
|
+
items: { type: 'string' },
|
|
117
|
+
description:
|
|
118
|
+
'Fields to keep per item (e.g. ["id","title","status","message"]). Ignored when verbose is true.',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Inject `verbose`/`fields` input params into every list-style tool definition so the
|
|
124
|
+
* model can opt into full bodies or a custom projection. Returns shallow copies; the
|
|
125
|
+
* source definitions are not mutated.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array} tools
|
|
128
|
+
* @param {{ extraNames?: string[] }} [options] additional tool names to augment
|
|
129
|
+
*/
|
|
130
|
+
export function withListOptions(tools, { extraNames = [] } = {}) {
|
|
131
|
+
return tools.map((tool) => {
|
|
132
|
+
if (!tool || !tool.name) return tool;
|
|
133
|
+
const isListStyle = tool.name.endsWith('_list') || extraNames.includes(tool.name);
|
|
134
|
+
if (!isListStyle) return tool;
|
|
135
|
+
|
|
136
|
+
const inputSchema = tool.inputSchema || { type: 'object', properties: {} };
|
|
137
|
+
const properties = { ...(inputSchema.properties || {}), ...LIST_OPTION_PROPERTIES };
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
...tool,
|
|
141
|
+
description: `${tool.description ?? ''} Entity-specific heavy fields are stripped by default; verbose:true for full bodies.`.trim(),
|
|
142
|
+
inputSchema: {
|
|
143
|
+
...inputSchema,
|
|
144
|
+
properties,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -4,9 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import { ATTACHMENT_RESOURCE_KEYS } from '../configs/attachments-config.js';
|
|
5
5
|
|
|
6
6
|
export const attachmentMethods = {
|
|
7
|
-
listAttachmentsForKey({ resourceKey, resourceId }) {
|
|
7
|
+
listAttachmentsForKey({ resourceKey, resourceId, slim }) {
|
|
8
8
|
this.assertSupportedAttachmentResourceKey(resourceKey);
|
|
9
|
-
return this.apiClient.list('attachments', { [resourceKey]: resourceId });
|
|
9
|
+
return this.apiClient.list('attachments', { [resourceKey]: resourceId, slim });
|
|
10
10
|
},
|
|
11
11
|
|
|
12
12
|
async uploadAttachmentForKey({ resourceKey, resourceId, filePath }) {
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { ENTITY_CRUD_CONFIGS } from '../configs/entity-crud-config.js';
|
|
2
2
|
import { ATTACHMENT_SCOPED_TOOL_CONFIGS } from '../configs/attachments-config.js';
|
|
3
3
|
import { ISSUE_SCOPED_TOOL_CONFIGS } from '../configs/issues-config.js';
|
|
4
|
+
import { backendSlimQuery, slimList } from '../list-projection.js';
|
|
4
5
|
|
|
5
6
|
export const handlerMethods = {
|
|
6
7
|
registerEntityCrudHandlers(handlers) {
|
|
7
8
|
for (const spec of ENTITY_CRUD_CONFIGS) {
|
|
8
|
-
const { toolPrefix, resource, idArg, listMethod
|
|
9
|
-
|
|
10
|
-
handlers[`${toolPrefix}_list`] = async (args = {}) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const { toolPrefix, resource, idArg, listMethod } = spec;
|
|
10
|
+
|
|
11
|
+
handlers[`${toolPrefix}_list`] = async (args = {}) => {
|
|
12
|
+
const { verbose, fields, ...listArgs } = args;
|
|
13
|
+
Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
|
|
14
|
+
return this.asText(
|
|
15
|
+
slimList(await this[listMethod](listArgs), {
|
|
16
|
+
verbose,
|
|
17
|
+
fields,
|
|
18
|
+
entity: toolPrefix,
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
};
|
|
16
22
|
handlers[`${toolPrefix}_get`] = async (args = {}) =>
|
|
17
23
|
this.asText(await this.apiClient.get(resource, this.pickRequiredArg(args, idArg)));
|
|
18
24
|
handlers[`${toolPrefix}_create`] = async (args = {}) =>
|
|
@@ -31,13 +37,17 @@ export const handlerMethods = {
|
|
|
31
37
|
for (const { toolPrefix, resourceKey } of ISSUE_SCOPED_TOOL_CONFIGS) {
|
|
32
38
|
handlers[`${toolPrefix}_issues_list`] = async (args = {}) =>
|
|
33
39
|
this.asText(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
slimList(
|
|
41
|
+
await this.listIssuesForKey({
|
|
42
|
+
resourceKey,
|
|
43
|
+
resourceId: this.pickRequiredArg(args, resourceKey),
|
|
44
|
+
page: args.page,
|
|
45
|
+
per_page: args.per_page,
|
|
46
|
+
source: args.source,
|
|
47
|
+
...backendSlimQuery(args),
|
|
48
|
+
}),
|
|
49
|
+
{ verbose: args.verbose, fields: args.fields, entity: 'issues' }
|
|
50
|
+
)
|
|
41
51
|
);
|
|
42
52
|
|
|
43
53
|
handlers[`${toolPrefix}_issues_link`] = async (args = {}) =>
|
|
@@ -59,10 +69,14 @@ export const handlerMethods = {
|
|
|
59
69
|
for (const { toolPrefix, resourceKey } of ATTACHMENT_SCOPED_TOOL_CONFIGS) {
|
|
60
70
|
handlers[`${toolPrefix}_attachments_list`] = async (args = {}) =>
|
|
61
71
|
this.asText(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
slimList(
|
|
73
|
+
await this.listAttachmentsForKey({
|
|
74
|
+
resourceKey,
|
|
75
|
+
resourceId: this.pickRequiredArg(args, resourceKey),
|
|
76
|
+
...backendSlimQuery(args),
|
|
77
|
+
}),
|
|
78
|
+
{ verbose: args.verbose, fields: args.fields, entity: 'attachments' }
|
|
79
|
+
)
|
|
66
80
|
);
|
|
67
81
|
|
|
68
82
|
handlers[`${toolPrefix}_attachments_upload`] = async (args = {}) =>
|
|
@@ -86,16 +100,31 @@ export const handlerMethods = {
|
|
|
86
100
|
},
|
|
87
101
|
|
|
88
102
|
registerGlobalHandlers(handlers) {
|
|
89
|
-
handlers.
|
|
103
|
+
handlers.project_info = async () => this.asText(await this.apiClient.get('info'));
|
|
104
|
+
|
|
105
|
+
handlers.tags_list = async (args = {}) =>
|
|
106
|
+
this.asText(
|
|
107
|
+
slimList(await this.listTags(backendSlimQuery(args)), { ...args, entity: 'tags' })
|
|
108
|
+
);
|
|
90
109
|
handlers.tags_get = async ({ tag_id: tagId }) => this.asText(await this.getTagByTitle(tagId));
|
|
91
|
-
handlers.tags_search = async (args = {}) => this.asText(await this.searchTags(args));
|
|
92
110
|
|
|
93
|
-
handlers.milestones_list = async (args = {}) =>
|
|
111
|
+
handlers.milestones_list = async (args = {}) => {
|
|
112
|
+
const { verbose, fields, ...listArgs } = args;
|
|
113
|
+
Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
|
|
114
|
+
return this.asText(
|
|
115
|
+
slimList(await this.listMilestones(listArgs), { verbose, fields, entity: 'milestones' })
|
|
116
|
+
);
|
|
117
|
+
};
|
|
94
118
|
handlers.milestones_get = async ({ milestone_id: milestoneId }) =>
|
|
95
119
|
this.asText(await this.apiClient.get('milestones', milestoneId));
|
|
96
120
|
|
|
97
|
-
handlers.issues_list = async (args = {}) =>
|
|
98
|
-
|
|
121
|
+
handlers.issues_list = async (args = {}) => {
|
|
122
|
+
const { verbose, fields, ...listArgs } = args;
|
|
123
|
+
Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
|
|
124
|
+
return this.asText(
|
|
125
|
+
slimList(await this.listIssues(listArgs), { verbose, fields, entity: 'issues' })
|
|
126
|
+
);
|
|
127
|
+
};
|
|
99
128
|
handlers.issues_create = async (args = {}) => this.asText(await this.createIssue(args));
|
|
100
129
|
handlers.issues_delete = async ({ issue_id: issueId, type }) =>
|
|
101
130
|
this.asText(await this.apiClient.delete('issues', issueId, { type }));
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { ISSUE_RESOURCE_KEYS } from '../configs/issues-config.js';
|
|
2
2
|
|
|
3
3
|
export const issueMethods = {
|
|
4
|
-
listIssues({ page, per_page: perPage, source, ...resourceQuery } = {}) {
|
|
4
|
+
listIssues({ page, per_page: perPage, source, slim, ...resourceQuery } = {}) {
|
|
5
5
|
this.validateIssueResourceQuery(resourceQuery, { allowEmpty: true, allowMany: false });
|
|
6
6
|
return this.listIssuesForResource({
|
|
7
7
|
...resourceQuery,
|
|
8
8
|
page,
|
|
9
9
|
per_page: perPage,
|
|
10
10
|
source,
|
|
11
|
+
slim,
|
|
11
12
|
});
|
|
12
13
|
},
|
|
13
14
|
|
|
14
|
-
searchIssues(args = {}) {
|
|
15
|
-
return this.listIssues(args);
|
|
16
|
-
},
|
|
17
|
-
|
|
18
15
|
createIssue({ url, jira_id: jiraId, ...resourceQuery } = {}) {
|
|
19
16
|
this.validateIssueResourceQuery(resourceQuery, { allowEmpty: false, allowMany: false });
|
|
20
17
|
return this.linkIssueToResource({
|
|
@@ -24,22 +21,24 @@ export const issueMethods = {
|
|
|
24
21
|
});
|
|
25
22
|
},
|
|
26
23
|
|
|
27
|
-
listIssuesForResource({ page, per_page: perPage, source, ...resourceQuery } = {}) {
|
|
24
|
+
listIssuesForResource({ page, per_page: perPage, source, slim, ...resourceQuery } = {}) {
|
|
28
25
|
return this.apiClient.list('issues', {
|
|
29
26
|
...resourceQuery,
|
|
30
27
|
page,
|
|
31
28
|
per_page: perPage,
|
|
32
29
|
source,
|
|
30
|
+
slim,
|
|
33
31
|
});
|
|
34
32
|
},
|
|
35
33
|
|
|
36
|
-
listIssuesForKey({ resourceKey, resourceId, page, per_page: perPage, source }) {
|
|
34
|
+
listIssuesForKey({ resourceKey, resourceId, page, per_page: perPage, source, slim }) {
|
|
37
35
|
this.assertSupportedIssueResourceKey(resourceKey);
|
|
38
36
|
return this.listIssuesForResource({
|
|
39
37
|
[resourceKey]: resourceId,
|
|
40
38
|
page,
|
|
41
39
|
per_page: perPage,
|
|
42
40
|
source,
|
|
41
|
+
slim,
|
|
43
42
|
});
|
|
44
43
|
},
|
|
45
44
|
|
|
@@ -3,23 +3,17 @@ export const listingMethods = {
|
|
|
3
3
|
page,
|
|
4
4
|
per_page: perPage,
|
|
5
5
|
tql,
|
|
6
|
+
slim,
|
|
6
7
|
} = {}) {
|
|
7
8
|
return this.apiClient.list('tests', {
|
|
8
9
|
page,
|
|
9
10
|
per_page: perPage,
|
|
10
11
|
tql,
|
|
12
|
+
slim,
|
|
11
13
|
});
|
|
12
14
|
},
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
return this.listTests({
|
|
16
|
-
page,
|
|
17
|
-
per_page: perPage,
|
|
18
|
-
tql,
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText } = {}) {
|
|
16
|
+
listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText, slim } = {}) {
|
|
23
17
|
return this.apiClient.list('suites', {
|
|
24
18
|
page,
|
|
25
19
|
per_page: perPage,
|
|
@@ -27,13 +21,7 @@ export const listingMethods = {
|
|
|
27
21
|
tag,
|
|
28
22
|
labels,
|
|
29
23
|
search_text: searchText,
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
searchSuites({ search_text: searchText, ...rest } = {}) {
|
|
34
|
-
return this.listSuites({
|
|
35
|
-
...rest,
|
|
36
|
-
search_text: searchText,
|
|
24
|
+
slim,
|
|
37
25
|
});
|
|
38
26
|
},
|
|
39
27
|
|
|
@@ -41,18 +29,16 @@ export const listingMethods = {
|
|
|
41
29
|
page,
|
|
42
30
|
per_page: perPage,
|
|
43
31
|
tql,
|
|
32
|
+
slim,
|
|
44
33
|
} = {}) {
|
|
45
34
|
return this.apiClient.list('runs', {
|
|
46
35
|
page,
|
|
47
36
|
per_page: perPage,
|
|
48
37
|
tql,
|
|
38
|
+
slim,
|
|
49
39
|
});
|
|
50
40
|
},
|
|
51
41
|
|
|
52
|
-
searchRuns({ page, per_page: perPage, tql } = {}) {
|
|
53
|
-
return this.listRuns({ page, per_page: perPage, tql });
|
|
54
|
-
},
|
|
55
|
-
|
|
56
42
|
listTestruns({
|
|
57
43
|
page,
|
|
58
44
|
per_page: perPage,
|
|
@@ -72,6 +58,7 @@ export const listingMethods = {
|
|
|
72
58
|
envs,
|
|
73
59
|
rungroups,
|
|
74
60
|
defects,
|
|
61
|
+
slim,
|
|
75
62
|
} = {}) {
|
|
76
63
|
return this.apiClient.list('testruns', {
|
|
77
64
|
page,
|
|
@@ -92,36 +79,27 @@ export const listingMethods = {
|
|
|
92
79
|
envs: Array.isArray(envs) ? envs.join(',') : envs,
|
|
93
80
|
rungroups: Array.isArray(rungroups) ? rungroups.join(',') : rungroups,
|
|
94
81
|
defects,
|
|
82
|
+
slim,
|
|
95
83
|
});
|
|
96
84
|
},
|
|
97
85
|
|
|
98
|
-
|
|
99
|
-
return this.
|
|
100
|
-
page,
|
|
101
|
-
per_page: perPage,
|
|
102
|
-
run_id: runId,
|
|
103
|
-
filter_search: filterSearch,
|
|
104
|
-
...rest,
|
|
105
|
-
});
|
|
86
|
+
listRungroups({ page, per_page: perPage, slim } = {}) {
|
|
87
|
+
return this.apiClient.list('rungroups', { page, per_page: perPage, slim });
|
|
106
88
|
},
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
return this.apiClient.list('
|
|
90
|
+
listSteps({ page, per_page: perPage, slim } = {}) {
|
|
91
|
+
return this.apiClient.list('steps', { page, per_page: perPage, slim });
|
|
110
92
|
},
|
|
111
93
|
|
|
112
|
-
|
|
113
|
-
return this.apiClient.list('
|
|
94
|
+
listSnippets({ page, per_page: perPage, slim } = {}) {
|
|
95
|
+
return this.apiClient.list('snippets', { page, per_page: perPage, slim });
|
|
114
96
|
},
|
|
115
97
|
|
|
116
|
-
|
|
117
|
-
return this.apiClient.list('
|
|
98
|
+
listLabels({ page, per_page: perPage, slim } = {}) {
|
|
99
|
+
return this.apiClient.list('labels', { page, per_page: perPage, slim });
|
|
118
100
|
},
|
|
119
101
|
|
|
120
|
-
|
|
121
|
-
return this.apiClient.list('labels', { page, per_page: perPage });
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
listPlans({ page, per_page: perPage, kind, hidden, labels, search_text: searchText } = {}) {
|
|
102
|
+
listPlans({ page, per_page: perPage, kind, hidden, labels, search_text: searchText, slim } = {}) {
|
|
125
103
|
return this.apiClient.list('plans', {
|
|
126
104
|
page,
|
|
127
105
|
per_page: perPage,
|
|
@@ -129,27 +107,20 @@ export const listingMethods = {
|
|
|
129
107
|
hidden,
|
|
130
108
|
'labels[]': labels,
|
|
131
109
|
search_text: searchText,
|
|
110
|
+
slim,
|
|
132
111
|
});
|
|
133
112
|
},
|
|
134
113
|
|
|
135
|
-
|
|
136
|
-
return this.
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
listRequirements({ page, per_page: perPage, source, scope } = {}) {
|
|
140
|
-
return this.apiClient.list('requirements', { page, per_page: perPage, source, scope });
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
searchRequirements({ page, per_page: perPage, source, scope } = {}) {
|
|
144
|
-
return this.listRequirements({ page, per_page: perPage, source, scope });
|
|
114
|
+
listRequirements({ page, per_page: perPage, source, scope, slim } = {}) {
|
|
115
|
+
return this.apiClient.list('requirements', { page, per_page: perPage, source, scope, slim });
|
|
145
116
|
},
|
|
146
117
|
|
|
147
|
-
listMilestones({ page, per_page: perPage, type, status } = {}) {
|
|
148
|
-
return this.apiClient.list('milestones', { page, per_page: perPage, type, status });
|
|
118
|
+
listMilestones({ page, per_page: perPage, type, status, slim } = {}) {
|
|
119
|
+
return this.apiClient.list('milestones', { page, per_page: perPage, type, status, slim });
|
|
149
120
|
},
|
|
150
121
|
|
|
151
|
-
listTags() {
|
|
152
|
-
return this.apiClient.list('tags');
|
|
122
|
+
listTags({ slim } = {}) {
|
|
123
|
+
return this.apiClient.list('tags', { slim });
|
|
153
124
|
},
|
|
154
125
|
|
|
155
126
|
getTagByTitle(tagId) {
|