dataiku-sdk 4.1.0 → 4.2.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/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +2 -0
- package/dist/packages/types/src/index.js +2 -0
- package/dist/src/cli/command-syntax.json +2426 -1262
- package/dist/src/cli/commands/future.js +7 -2
- package/dist/src/cli/commands/index.js +4 -0
- package/dist/src/cli/commands/messaging-channel.d.ts +17 -0
- package/dist/src/cli/commands/messaging-channel.js +385 -0
- package/dist/src/cli/commands/project-deployer.js +33 -0
- package/dist/src/cli/commands/project.js +30 -0
- package/dist/src/cli/commands/scenario.d.ts +6 -0
- package/dist/src/cli/commands/scenario.js +423 -13
- package/dist/src/cli/contract.js +17 -2
- package/dist/src/cli/flags.js +34 -0
- package/dist/src/cli/helpers/cleanup.js +11 -0
- package/dist/src/cli/list-shapes.js +5 -0
- package/dist/src/cli/plans.js +94 -2
- package/dist/src/cli/read-shapes.js +2 -1
- package/dist/src/cli/result-shapes.js +1 -0
- package/dist/src/cli/usage.js +7 -0
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +2 -2
- package/dist/src/client.d.ts +11 -0
- package/dist/src/client.js +21 -2
- package/dist/src/generated/action-output-schemas.json +82 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/resources/bundles.d.ts +18 -0
- package/dist/src/resources/bundles.js +26 -0
- package/dist/src/resources/futures.d.ts +3 -0
- package/dist/src/resources/futures.js +5 -1
- package/dist/src/resources/messaging-channels.d.ts +83 -0
- package/dist/src/resources/messaging-channels.js +108 -0
- package/dist/src/resources/projects.d.ts +24 -0
- package/dist/src/resources/projects.js +36 -0
- package/dist/src/resources/scenario-authoring.d.ts +52 -0
- package/dist/src/resources/scenario-authoring.js +216 -0
- package/dist/src/resources/scenarios.d.ts +128 -11
- package/dist/src/resources/scenarios.js +273 -43
- package/dist/src/utils/messaging-channel-secrets.d.ts +17 -0
- package/dist/src/utils/messaging-channel-secrets.js +114 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +4 -3
- package/skills/dataiku-dss/references/notifications.md +29 -0
- package/skills/dataiku-dss/references/troubleshooting.md +1 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { resolve, } from "node:path";
|
|
2
|
+
import { mailReporter, SCENARIO_TRIGGER_FREQUENCIES, selectScenarioListIndex, temporalTrigger, } from "../../resources/scenario-authoring.js";
|
|
3
|
+
import { messagingChannelSecretsFromBody, sanitizeMessagingChannelError, sanitizeMessagingChannelSecrets, } from "../../utils/messaging-channel-secrets.js";
|
|
1
4
|
import { asRecord, } from "../../utils/records.js";
|
|
2
|
-
import { jsonInput, num, parseBooleanOption, } from "../coerce.js";
|
|
5
|
+
import { json, jsonInput, num, parseBooleanOption, parseJsonObject, readInputText, splitCsvFlag, } from "../coerce.js";
|
|
3
6
|
import { executionMode, } from "../flags.js";
|
|
4
|
-
import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
|
|
7
|
+
import { encodedProjectEndpoint, enqueueCliWarning, readIfExists, skipResult, } from "../output.js";
|
|
5
8
|
import { scenarioRunDiagnosis, shapeCommandResult, } from "../result-shapes.js";
|
|
6
9
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
7
10
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
@@ -48,10 +51,20 @@ async function locatedDiagnosis(c, details, projectKey) {
|
|
|
48
51
|
/**
|
|
49
52
|
* Run a scenario and wait; a failed (not timed-out) run gains the first
|
|
50
53
|
* failure, the recipe that failed, and the next commands. Extra reads happen
|
|
51
|
-
* only on failure; a read error leaves the wait result untouched.
|
|
54
|
+
* only on failure; a read error leaves the wait result untouched. A cancelled
|
|
55
|
+
* trigger fire has no run to diagnose: it says why instead.
|
|
52
56
|
*/
|
|
53
57
|
async function runAndWaitWithFailure(c, scenarioId, options) {
|
|
54
58
|
const result = await c.scenarios.runAndWait(scenarioId, options);
|
|
59
|
+
if (result.cancelled === true) {
|
|
60
|
+
return {
|
|
61
|
+
...result,
|
|
62
|
+
failure: {
|
|
63
|
+
message: `DSS cancelled this run before it started (trigger run ${result.triggerRunId}): the scenario was already running, or another trigger fire replaced this one. No scenario run exists; wait for or abort the current run (scenario status, scenario abort), then run again.`,
|
|
64
|
+
},
|
|
65
|
+
next: [["dss", "scenario", "status", scenarioId, ...projectKeyArgs(options.projectKey),],],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
55
68
|
if (result.success || result.timedOut === true || !result.runId)
|
|
56
69
|
return result;
|
|
57
70
|
try {
|
|
@@ -64,6 +77,211 @@ async function runAndWaitWithFailure(c, scenarioId, options) {
|
|
|
64
77
|
return result;
|
|
65
78
|
}
|
|
66
79
|
}
|
|
80
|
+
function projectKeyArgs(projectKey) {
|
|
81
|
+
return projectKey === undefined ? [] : ["--project-key", projectKey,];
|
|
82
|
+
}
|
|
83
|
+
/** `scenario run` result: the trigger fire, with the explicit pointer to the scenario run it will start. */
|
|
84
|
+
function triggerRunResult(fire, scenarioId, projectKey) {
|
|
85
|
+
if (fire.cancelled) {
|
|
86
|
+
return {
|
|
87
|
+
...fire,
|
|
88
|
+
hint: "DSS cancelled this trigger fire: the scenario was already running (or another fire replaced it), so no scenario run exists for this triggerRunId.",
|
|
89
|
+
next: [["dss", "scenario", "status", scenarioId, ...projectKeyArgs(projectKey),],],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...fire,
|
|
94
|
+
hint: "triggerRunId identifies this trigger firing, not a scenario run: get-run and log need the scenario run id that get-run-for-trigger returns (or use --wait).",
|
|
95
|
+
next: [[
|
|
96
|
+
"dss",
|
|
97
|
+
"scenario",
|
|
98
|
+
"get-run-for-trigger",
|
|
99
|
+
scenarioId,
|
|
100
|
+
fire.triggerRunId,
|
|
101
|
+
...(fire.triggerId === "manual" ? [] : ["--trigger-id", fire.triggerId,]),
|
|
102
|
+
...projectKeyArgs(projectKey),
|
|
103
|
+
],],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** `scenario get-run-for-trigger` result: the fire's state and the commands that follow from it. */
|
|
107
|
+
function triggerRunStateResult(state, scenarioId, projectKey) {
|
|
108
|
+
const pk = projectKeyArgs(projectKey);
|
|
109
|
+
const next = state.runId !== undefined
|
|
110
|
+
? [
|
|
111
|
+
["dss", "scenario", "get-run", scenarioId, state.runId, ...pk,],
|
|
112
|
+
["dss", "scenario", "log", scenarioId, state.runId, ...pk,],
|
|
113
|
+
]
|
|
114
|
+
: state.state === "PENDING"
|
|
115
|
+
? [[
|
|
116
|
+
"dss",
|
|
117
|
+
"scenario",
|
|
118
|
+
"get-run-for-trigger",
|
|
119
|
+
scenarioId,
|
|
120
|
+
state.triggerRunId,
|
|
121
|
+
...(state.triggerId === "manual" ? [] : ["--trigger-id", state.triggerId,]),
|
|
122
|
+
...pk,
|
|
123
|
+
],]
|
|
124
|
+
: [["dss", "scenario", "status", scenarioId, ...pk,],];
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
...(state.cancelled
|
|
128
|
+
? {
|
|
129
|
+
hint: "DSS cancelled this trigger fire (the scenario was already running, or another fire replaced it); no scenario run will exist.",
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
next,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Run parameters from `--params JSON` or `--params-file PATH` (the POST /run body). */
|
|
136
|
+
export function scenarioRunParams(flags) {
|
|
137
|
+
const file = flags["params-file"];
|
|
138
|
+
if (flags["params"] !== undefined && file !== undefined) {
|
|
139
|
+
throw new UsageError("Pass --params or --params-file, not both.");
|
|
140
|
+
}
|
|
141
|
+
return typeof file === "string"
|
|
142
|
+
? parseJsonObject(readInputText(file, "--params-file"), "--params-file")
|
|
143
|
+
: json(flags["params"], "--params");
|
|
144
|
+
}
|
|
145
|
+
/** The trigger `trigger-add` appends: a raw `--data` object, or a schedule built from the --frequency flags. */
|
|
146
|
+
export function scenarioTriggerFromFlags(flags) {
|
|
147
|
+
const usage = commandUsage("scenario", "trigger-add");
|
|
148
|
+
const raw = jsonInput(flags);
|
|
149
|
+
if (raw !== undefined) {
|
|
150
|
+
if (flags["frequency"] !== undefined) {
|
|
151
|
+
throw new UsageError(`--frequency builds a schedule; pass it or --data/--data-file/--stdin, not both. Usage: ${usage}`);
|
|
152
|
+
}
|
|
153
|
+
return raw;
|
|
154
|
+
}
|
|
155
|
+
const frequency = SCENARIO_TRIGGER_FREQUENCIES.find((candidate) => candidate === flags["frequency"]);
|
|
156
|
+
if (frequency === undefined) {
|
|
157
|
+
throw new UsageError(`--frequency ${SCENARIO_TRIGGER_FREQUENCIES.join("|")} (or a raw trigger via --data) is required. Usage: ${usage}`);
|
|
158
|
+
}
|
|
159
|
+
const text = (name) => typeof flags[name] === "string" ? flags[name] : undefined;
|
|
160
|
+
return temporalTrigger({
|
|
161
|
+
frequency,
|
|
162
|
+
every: num(flags["every"], "--every"),
|
|
163
|
+
hour: num(flags["hour"], "--hour"),
|
|
164
|
+
minute: num(flags["minute"], "--minute"),
|
|
165
|
+
days: flags["days"] === undefined ? undefined : splitCsvFlag(flags["days"]),
|
|
166
|
+
dayOfMonth: num(flags["day-of-month"], "--day-of-month"),
|
|
167
|
+
runOn: text("run-on"),
|
|
168
|
+
startDate: text("start-date"),
|
|
169
|
+
timezone: text("timezone"),
|
|
170
|
+
name: text("name"),
|
|
171
|
+
active: parseBooleanOption(flags["active"], "--active"),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** The reporter `reporter-add` appends: a raw `--data` object, or a mail reporter built from the --channel flags. */
|
|
175
|
+
export function scenarioReporterFromFlags(flags) {
|
|
176
|
+
const usage = commandUsage("scenario", "reporter-add");
|
|
177
|
+
const raw = jsonInput(flags);
|
|
178
|
+
const mailFlags = ["channel", "recipient", "subject", "message", "condition", "phase",];
|
|
179
|
+
if (raw !== undefined) {
|
|
180
|
+
const clash = mailFlags.find((name) => flags[name] !== undefined);
|
|
181
|
+
if (clash !== undefined) {
|
|
182
|
+
throw new UsageError(`--${clash} builds a mail reporter; pass it or --data/--data-file/--stdin, not both. Usage: ${usage}`);
|
|
183
|
+
}
|
|
184
|
+
return raw;
|
|
185
|
+
}
|
|
186
|
+
const text = (name) => typeof flags[name] === "string" ? flags[name] : undefined;
|
|
187
|
+
const [channel, recipient, subject, message,] = ["channel", "recipient", "subject", "message",]
|
|
188
|
+
.map((name) => {
|
|
189
|
+
const value = text(name);
|
|
190
|
+
if (value === undefined) {
|
|
191
|
+
throw new UsageError(`--${name} is required (or pass a raw reporter via --data). Usage: ${usage}`);
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
});
|
|
195
|
+
return mailReporter({
|
|
196
|
+
channel,
|
|
197
|
+
recipient,
|
|
198
|
+
subject,
|
|
199
|
+
message,
|
|
200
|
+
condition: text("condition"),
|
|
201
|
+
phase: text("phase"),
|
|
202
|
+
name: text("name"),
|
|
203
|
+
active: parseBooleanOption(flags["active"], "--active"),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/** Reporter reduced to what a list needs: identity, phase, condition, channel, recipient, subject. */
|
|
207
|
+
function compactReporter(reporter, index) {
|
|
208
|
+
const fields = asRecord(reporter);
|
|
209
|
+
const messaging = asRecord(fields?.["messaging"]);
|
|
210
|
+
const configuration = asRecord(messaging?.["configuration"]);
|
|
211
|
+
return {
|
|
212
|
+
index,
|
|
213
|
+
id: fields?.["id"],
|
|
214
|
+
name: fields?.["name"],
|
|
215
|
+
active: fields?.["active"],
|
|
216
|
+
phase: fields?.["phase"],
|
|
217
|
+
runCondition: fields?.["runConditionEnabled"] === true ? fields["runCondition"] : undefined,
|
|
218
|
+
type: messaging?.["type"],
|
|
219
|
+
channel: configuration?.["channelId"],
|
|
220
|
+
recipient: configuration?.["recipient"],
|
|
221
|
+
subject: configuration?.["subject"],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** Shared `trigger-add`/`reporter-add`: dry-run previews the appended element, otherwise it is written and verified. */
|
|
225
|
+
async function addScenarioItem(c, kind, scenarioId, item, flags) {
|
|
226
|
+
const projectKey = flags["project-key"];
|
|
227
|
+
const listAction = `${kind}-list`;
|
|
228
|
+
if (executionMode(flags).dryRun) {
|
|
229
|
+
const items = await c.scenarios.listItems(kind, scenarioId, { projectKey, });
|
|
230
|
+
return {
|
|
231
|
+
dryRun: true,
|
|
232
|
+
action: `${kind}-add`,
|
|
233
|
+
resource: "scenario",
|
|
234
|
+
id: scenarioId,
|
|
235
|
+
[kind]: sanitizeMessagingChannelSecrets(item),
|
|
236
|
+
index: items.length,
|
|
237
|
+
count: items.length + 1,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// A raw reporter can carry a webhook URL or token that DSS may echo back in an error.
|
|
241
|
+
const secrets = messagingChannelSecretsFromBody(item);
|
|
242
|
+
const result = await c.scenarios.addItem(kind, scenarioId, item, { projectKey, }).catch((error) => {
|
|
243
|
+
throw sanitizeMessagingChannelError(error, secrets);
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
added: kind,
|
|
247
|
+
resource: "scenario",
|
|
248
|
+
id: scenarioId,
|
|
249
|
+
index: result.index,
|
|
250
|
+
[kind]: sanitizeMessagingChannelSecrets(result.item),
|
|
251
|
+
count: result.count,
|
|
252
|
+
next: [["dss", "scenario", listAction, scenarioId, ...projectKeyArgs(projectKey),],],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/** Shared `trigger-remove`/`reporter-remove`: the element is chosen by --index or --name; the others stay. */
|
|
256
|
+
async function removeScenarioItem(c, kind, scenarioId, flags) {
|
|
257
|
+
const projectKey = flags["project-key"];
|
|
258
|
+
const selector = {
|
|
259
|
+
index: num(flags["index"], "--index"),
|
|
260
|
+
name: flags["name"],
|
|
261
|
+
};
|
|
262
|
+
if (executionMode(flags).dryRun) {
|
|
263
|
+
const items = await c.scenarios.listItems(kind, scenarioId, { projectKey, });
|
|
264
|
+
const index = selectScenarioListIndex(items, selector, kind);
|
|
265
|
+
return {
|
|
266
|
+
dryRun: true,
|
|
267
|
+
action: `${kind}-remove`,
|
|
268
|
+
resource: "scenario",
|
|
269
|
+
id: scenarioId,
|
|
270
|
+
index,
|
|
271
|
+
[kind]: sanitizeMessagingChannelSecrets(items[index]),
|
|
272
|
+
count: items.length - 1,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const result = await c.scenarios.removeItem(kind, scenarioId, selector, { projectKey, });
|
|
276
|
+
return {
|
|
277
|
+
removed: kind,
|
|
278
|
+
resource: "scenario",
|
|
279
|
+
id: scenarioId,
|
|
280
|
+
index: result.index,
|
|
281
|
+
[kind]: sanitizeMessagingChannelSecrets(result.item),
|
|
282
|
+
count: result.count,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
67
285
|
export const scenarioCommands = withUsage("scenario", {
|
|
68
286
|
list: {
|
|
69
287
|
handler: (c, _a, f) => c.scenarios.list(f["project-key"]),
|
|
@@ -82,6 +300,7 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
82
300
|
handler: async (c, a, f) => {
|
|
83
301
|
requireArgs(a, 1, commandUsage("scenario", "run"));
|
|
84
302
|
const pk = f["project-key"];
|
|
303
|
+
const params = scenarioRunParams(f);
|
|
85
304
|
const options = {
|
|
86
305
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
87
306
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
@@ -93,22 +312,28 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
93
312
|
resource: "scenario",
|
|
94
313
|
id: a[0],
|
|
95
314
|
...options,
|
|
315
|
+
...(params === undefined ? {} : { params, }),
|
|
96
316
|
endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/run/`),
|
|
97
317
|
method: "POST",
|
|
98
318
|
};
|
|
99
319
|
}
|
|
100
320
|
if (f["wait"] === true) {
|
|
101
|
-
return runAndWaitWithFailure(c, a[0], { ...options, projectKey: pk, });
|
|
321
|
+
return runAndWaitWithFailure(c, a[0], { ...options, params, projectKey: pk, });
|
|
102
322
|
}
|
|
103
|
-
return c.scenarios.run(a[0], pk);
|
|
323
|
+
return triggerRunResult(await c.scenarios.run(a[0], { params, projectKey: pk, }), a[0], pk);
|
|
104
324
|
},
|
|
105
|
-
description: "Trigger a scenario run,
|
|
106
|
-
examples: [
|
|
325
|
+
description: "Trigger a scenario run; --wait waits for it. --params JSON/--params-file PATH are the run parameters. Without --wait the result is the trigger fire: triggerRunId is NOT a scenario run id, resolve it with get-run-for-trigger (see `next`). cancelled: true means the scenario was already running; no run starts.",
|
|
326
|
+
examples: [
|
|
327
|
+
"dss scenario run my_scenario",
|
|
328
|
+
"dss scenario run my_scenario --wait",
|
|
329
|
+
'dss scenario run my_scenario --params \'{"region":"EU"}\' --wait',
|
|
330
|
+
],
|
|
107
331
|
},
|
|
108
332
|
"run-and-wait": {
|
|
109
333
|
handler: async (c, a, f) => {
|
|
110
334
|
requireArgs(a, 1, commandUsage("scenario", "run-and-wait"));
|
|
111
335
|
const pk = f["project-key"];
|
|
336
|
+
const params = scenarioRunParams(f);
|
|
112
337
|
const options = {
|
|
113
338
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
114
339
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
@@ -120,16 +345,18 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
120
345
|
resource: "scenario",
|
|
121
346
|
id: a[0],
|
|
122
347
|
...options,
|
|
348
|
+
...(params === undefined ? {} : { params, }),
|
|
123
349
|
endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/run/`),
|
|
124
350
|
method: "POST",
|
|
125
351
|
};
|
|
126
352
|
}
|
|
127
|
-
return runAndWaitWithFailure(c, a[0], { ...options, projectKey: pk, });
|
|
353
|
+
return runAndWaitWithFailure(c, a[0], { ...options, params, projectKey: pk, });
|
|
128
354
|
},
|
|
129
|
-
description: "Run a scenario and wait for completion.",
|
|
355
|
+
description: "Run a scenario and wait for completion (--params JSON / --params-file PATH as run parameters). Outcome CANCELLED (exit 4) means DSS cancelled the trigger fire because the scenario was already running; TIMEOUT means the wait ran out while the run continues.",
|
|
130
356
|
examples: [
|
|
131
357
|
"dss scenario run-and-wait my_scenario",
|
|
132
358
|
"dss scenario run-and-wait my_scenario --timeout 300000",
|
|
359
|
+
"dss scenario run-and-wait my_scenario --params-file params.json",
|
|
133
360
|
],
|
|
134
361
|
},
|
|
135
362
|
status: {
|
|
@@ -162,11 +389,13 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
162
389
|
handler: async (c, a, f) => {
|
|
163
390
|
requireArgs(a, 2, commandUsage("scenario", "create"));
|
|
164
391
|
const pk = f["project-key"];
|
|
392
|
+
const data = jsonInput(f);
|
|
165
393
|
const payload = {
|
|
166
394
|
scenarioId: a[0],
|
|
167
395
|
name: a[1],
|
|
168
396
|
scenarioType: f["type"],
|
|
169
397
|
projectKey: pk,
|
|
398
|
+
...(data === undefined ? {} : { data, }),
|
|
170
399
|
};
|
|
171
400
|
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
172
401
|
const list = await c.scenarios.list(pk);
|
|
@@ -187,14 +416,16 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
187
416
|
}
|
|
188
417
|
await c.scenarios.create(a[0], a[1], {
|
|
189
418
|
scenarioType: payload.scenarioType,
|
|
419
|
+
data,
|
|
190
420
|
projectKey: pk,
|
|
191
421
|
});
|
|
192
422
|
return { created: a[0], name: a[1], resource: "scenario", };
|
|
193
423
|
},
|
|
194
|
-
description: "Create a new scenario.",
|
|
424
|
+
description: "Create a new scenario. --data/--data-file/--stdin merge extra top-level fields into the create body (e.g. triggers, reporters, params.steps, runAsUser).",
|
|
195
425
|
examples: [
|
|
196
426
|
'dss scenario create my_scenario "My Scenario"',
|
|
197
427
|
'dss scenario create my_scenario "My Scenario" --type custom_python --dry-run',
|
|
428
|
+
'dss scenario create nightly "Nightly" --data \'{"triggers":[{"active":true,"type":"temporal","params":{"frequency":"Daily","hour":2,"minute":0,"startingFrom":"2026-01-01T00:00:00.000+0000","repeatFrequency":1,"timezone":"SERVER"}}]}\' --dry-run',
|
|
198
429
|
],
|
|
199
430
|
},
|
|
200
431
|
update: {
|
|
@@ -267,11 +498,15 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
267
498
|
requireArgs(a, 1, commandUsage("scenario", "last-runs"));
|
|
268
499
|
return c.scenarios.getLastRuns(a[0], {
|
|
269
500
|
limit: num(f["limit"], "--limit"),
|
|
501
|
+
onlyFinished: f["only-finished"] === true,
|
|
270
502
|
projectKey: f["project-key"],
|
|
271
503
|
});
|
|
272
504
|
},
|
|
273
|
-
description: "List
|
|
274
|
-
examples: [
|
|
505
|
+
description: "List a scenario's most recent runs, newest first (compact: run id, outcome, timing, trigger, failure; --full for DSS objects). --only-finished skips in-progress (and, observed live, aborted) runs: `--only-finished --limit 1` is the last finished run; read its outcome.",
|
|
506
|
+
examples: [
|
|
507
|
+
"dss scenario last-runs my_scenario --limit 10",
|
|
508
|
+
"dss scenario last-runs my_scenario --only-finished --limit 1",
|
|
509
|
+
],
|
|
275
510
|
},
|
|
276
511
|
"get-run": {
|
|
277
512
|
handler: async (c, a, f) => {
|
|
@@ -301,7 +536,6 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
301
536
|
const outputFile = f["output"]
|
|
302
537
|
?? f["output-file"];
|
|
303
538
|
if (outputFile) {
|
|
304
|
-
const { resolve, } = await import("node:path");
|
|
305
539
|
const outputPath = resolve(outputFile);
|
|
306
540
|
await Bun.write(outputPath, result.text);
|
|
307
541
|
return { path: outputPath, truncated: result.truncated, };
|
|
@@ -377,4 +611,180 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
377
611
|
"dss scenario active-set my_scenario true --dry-run",
|
|
378
612
|
],
|
|
379
613
|
},
|
|
614
|
+
"get-run-for-trigger": {
|
|
615
|
+
handler: async (c, a, f) => {
|
|
616
|
+
requireArgs(a, 2, commandUsage("scenario", "get-run-for-trigger"));
|
|
617
|
+
const pk = f["project-key"];
|
|
618
|
+
const state = await c.scenarios.getRunForTrigger(a[0], a[1], {
|
|
619
|
+
triggerId: f["trigger-id"],
|
|
620
|
+
projectKey: pk,
|
|
621
|
+
});
|
|
622
|
+
return triggerRunStateResult(state, a[0], pk);
|
|
623
|
+
},
|
|
624
|
+
description: "Resolve a trigger run (the triggerRunId `scenario run` returns) to its scenario run: state PENDING (no run yet; repeat), RUNNING, FINISHED (outcome), or CANCELLED (DSS cancelled the fire because the scenario was already running; no run will exist). `runId` is what get-run and log take.",
|
|
625
|
+
examples: ["dss scenario get-run-for-trigger my_scenario 2024-09-01-01-02-03-123",],
|
|
626
|
+
},
|
|
627
|
+
"runs-by-date": {
|
|
628
|
+
handler: (c, a, f) => {
|
|
629
|
+
requireArgs(a, 1, commandUsage("scenario", "runs-by-date"));
|
|
630
|
+
const from = f["from"];
|
|
631
|
+
if (typeof from !== "string") {
|
|
632
|
+
throw new UsageError(`--from is required. Usage: ${commandUsage("scenario", "runs-by-date")}`);
|
|
633
|
+
}
|
|
634
|
+
return c.scenarios.getRunsByDate(a[0], {
|
|
635
|
+
from,
|
|
636
|
+
to: f["to"],
|
|
637
|
+
projectKey: f["project-key"],
|
|
638
|
+
});
|
|
639
|
+
},
|
|
640
|
+
description: "List the runs of a scenario that started from --from (inclusive) to --to (exclusive: DSS compares the start of that day, so --from D --to D is empty), each YYYY-MM-DD or an ISO date-time (time dropped). --to defaults to tomorrow, i.e. up to now. Compact like last-runs; --full returns the DSS run objects.",
|
|
641
|
+
examples: ["dss scenario runs-by-date my_scenario --from 2026-10-01 --to 2026-10-31",],
|
|
642
|
+
},
|
|
643
|
+
"run-report": {
|
|
644
|
+
handler: async (c, a, f) => {
|
|
645
|
+
requireArgs(a, 2, commandUsage("scenario", "run-report"));
|
|
646
|
+
const result = await c.scenarios.getRunReport(a[0], a[1], {
|
|
647
|
+
stepId: f["step-id"],
|
|
648
|
+
maxBytes: num(f["max-bytes"], "--max-bytes"),
|
|
649
|
+
projectKey: f["project-key"],
|
|
650
|
+
});
|
|
651
|
+
const outputFile = f["output"]
|
|
652
|
+
?? f["output-file"];
|
|
653
|
+
if (outputFile) {
|
|
654
|
+
const outputPath = resolve(outputFile);
|
|
655
|
+
await Bun.write(outputPath, result.text);
|
|
656
|
+
return { path: outputPath, truncated: result.truncated, };
|
|
657
|
+
}
|
|
658
|
+
return result.text;
|
|
659
|
+
},
|
|
660
|
+
description: "Get the JUnit XML report of a test scenario run (or of one step via --step-id). Only scenarios marked as test (markedAsTest) have reports. Byte-bounded at --max-bytes (default 1 MiB); --output PATH writes it to a file (stdout returns the path).",
|
|
661
|
+
examples: [
|
|
662
|
+
"dss scenario run-report my_test_scenario RUN_ID --output report.xml",
|
|
663
|
+
"dss scenario run-report my_test_scenario RUN_ID --step-id run_tests",
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
"trigger-list": {
|
|
667
|
+
handler: async (c, a, f) => {
|
|
668
|
+
requireArgs(a, 1, commandUsage("scenario", "trigger-list"));
|
|
669
|
+
const triggers = await c.scenarios.listItems("trigger", a[0], {
|
|
670
|
+
projectKey: f["project-key"],
|
|
671
|
+
});
|
|
672
|
+
return triggers.map((trigger, index) => ({ index, ...asRecord(trigger), }));
|
|
673
|
+
},
|
|
674
|
+
description: "List the automatic triggers of a scenario with their 0-based index (the --index of trigger-remove). Triggers only fire while the scenario is active (scenario active-set).",
|
|
675
|
+
examples: ["dss scenario trigger-list my_scenario",],
|
|
676
|
+
},
|
|
677
|
+
"trigger-add": {
|
|
678
|
+
handler: (c, a, f) => {
|
|
679
|
+
requireArgs(a, 1, commandUsage("scenario", "trigger-add"));
|
|
680
|
+
return addScenarioItem(c, "trigger", a[0], scenarioTriggerFromFlags(f), f);
|
|
681
|
+
},
|
|
682
|
+
description: "Append a trigger, keeping the others. Schedule: --frequency (dataikuapi builders) with --every, --hour, --minute, --days (weekly), --day-of-month/--run-on (monthly), --start-date, --timezone (default SERVER), --name, --active. Other trigger types: JSON via --data.",
|
|
683
|
+
examples: [
|
|
684
|
+
"dss scenario trigger-add my_scenario --frequency daily --hour 6 --minute 30",
|
|
685
|
+
"dss scenario trigger-add my_scenario --frequency weekly --days Monday,Friday --hour 8",
|
|
686
|
+
"dss scenario trigger-add my_scenario --frequency monthly --day-of-month 5 --run-on ON_THE_DAY --dry-run",
|
|
687
|
+
'dss scenario trigger-add my_scenario --data \'{"type":"temporal","active":true,"params":{"frequency":"Minutely","repeatFrequency":15}}\'',
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
"trigger-remove": {
|
|
691
|
+
handler: (c, a, f) => {
|
|
692
|
+
requireArgs(a, 1, commandUsage("scenario", "trigger-remove"));
|
|
693
|
+
return removeScenarioItem(c, "trigger", a[0], f);
|
|
694
|
+
},
|
|
695
|
+
description: "Remove one trigger, selected by --index (from trigger-list) or --name, keeping the others.",
|
|
696
|
+
examples: [
|
|
697
|
+
"dss scenario trigger-remove my_scenario --index 0",
|
|
698
|
+
"dss scenario trigger-remove my_scenario --name nightly --dry-run",
|
|
699
|
+
],
|
|
700
|
+
},
|
|
701
|
+
"reporter-list": {
|
|
702
|
+
handler: async (c, a, f) => {
|
|
703
|
+
requireArgs(a, 1, commandUsage("scenario", "reporter-list"));
|
|
704
|
+
const reporters = await c.scenarios.listItems("reporter", a[0], {
|
|
705
|
+
projectKey: f["project-key"],
|
|
706
|
+
});
|
|
707
|
+
// Channel credentials live on the channel, but reporters of other types may carry their own.
|
|
708
|
+
return reporters.map((reporter, index) => f["full"] === true
|
|
709
|
+
? { index, ...asRecord(sanitizeMessagingChannelSecrets(reporter)), }
|
|
710
|
+
: compactReporter(reporter, index));
|
|
711
|
+
},
|
|
712
|
+
description: "List the reporters (notifications) of a scenario with their 0-based index: id, phase, active, channel, recipient, subject (compact by default; --full returns the DSS objects with credential-looking values redacted).",
|
|
713
|
+
examples: ["dss scenario reporter-list my_scenario",],
|
|
714
|
+
},
|
|
715
|
+
"reporter-add": {
|
|
716
|
+
handler: async (c, a, f) => {
|
|
717
|
+
requireArgs(a, 1, commandUsage("scenario", "reporter-add"));
|
|
718
|
+
const reporter = scenarioReporterFromFlags(f);
|
|
719
|
+
// DSS saves a reporter with any channel id; an unknown one never sends. Warn, don't fail:
|
|
720
|
+
// a channel may be hidden from this key yet usable by the scenario's run-as user.
|
|
721
|
+
const channel = f["channel"];
|
|
722
|
+
if (typeof channel === "string") {
|
|
723
|
+
const known = (await c.messagingChannels.list()).map((entry) => entry.id);
|
|
724
|
+
if (!known.includes(channel)) {
|
|
725
|
+
enqueueCliWarning({
|
|
726
|
+
code: "reporter_channel_unknown",
|
|
727
|
+
channel,
|
|
728
|
+
knownChannels: known,
|
|
729
|
+
hint: "No messaging channel with this id is visible: the reporter will not send. Check `dss messaging-channel list`.",
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return addScenarioItem(c, "reporter", a[0], reporter, f);
|
|
734
|
+
},
|
|
735
|
+
description: "Append a reporter, keeping the others. Mail: --channel ID (messaging-channel list), --recipient, --subject, --message (required), --condition EXPR, --phase START|END (default END). Slack, Teams and other channel types: pass the reporter JSON with --data.",
|
|
736
|
+
examples: [
|
|
737
|
+
'dss scenario reporter-add my_scenario --channel smtp --recipient ops@example.com --subject "Scenario done" --message "See DSS"',
|
|
738
|
+
"dss scenario reporter-add my_scenario --channel smtp --recipient ops@example.com --subject Failed --message Failed --condition \"outcome == 'FAILED'\" --dry-run",
|
|
739
|
+
],
|
|
740
|
+
},
|
|
741
|
+
"reporter-remove": {
|
|
742
|
+
handler: (c, a, f) => {
|
|
743
|
+
requireArgs(a, 1, commandUsage("scenario", "reporter-remove"));
|
|
744
|
+
return removeScenarioItem(c, "reporter", a[0], f);
|
|
745
|
+
},
|
|
746
|
+
description: "Remove one reporter, selected by --index (from reporter-list) or --name, keeping the others.",
|
|
747
|
+
examples: ["dss scenario reporter-remove my_scenario --index 0",],
|
|
748
|
+
},
|
|
749
|
+
"run-as-set": {
|
|
750
|
+
handler: async (c, a, f) => {
|
|
751
|
+
requireArgs(a, 1, commandUsage("scenario", "run-as-set"));
|
|
752
|
+
const user = f["user"];
|
|
753
|
+
const clear = f["clear"] === true;
|
|
754
|
+
if ((typeof user === "string") === clear) {
|
|
755
|
+
throw new UsageError(`Pass exactly one of --user LOGIN or --clear. Usage: ${commandUsage("scenario", "run-as-set")}`);
|
|
756
|
+
}
|
|
757
|
+
const runAsUser = typeof user === "string" ? user : null;
|
|
758
|
+
const pk = f["project-key"];
|
|
759
|
+
if (executionMode(f).dryRun) {
|
|
760
|
+
const current = asRecord(await c.scenarios.get(a[0], { projectKey: pk, }));
|
|
761
|
+
return {
|
|
762
|
+
dryRun: true,
|
|
763
|
+
action: "run-as-set",
|
|
764
|
+
resource: "scenario",
|
|
765
|
+
id: a[0],
|
|
766
|
+
runAsUser,
|
|
767
|
+
currentRunAsUser: current?.["runAsUser"] ?? null,
|
|
768
|
+
endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/`),
|
|
769
|
+
method: "PUT",
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const result = await c.scenarios.setRunAs(a[0], runAsUser, { projectKey: pk, });
|
|
773
|
+
return {
|
|
774
|
+
updated: a[0],
|
|
775
|
+
resource: "scenario",
|
|
776
|
+
part: "runAsUser",
|
|
777
|
+
runAsUser: result.runAsUser,
|
|
778
|
+
effectiveRunAs: result.effectiveRunAs,
|
|
779
|
+
...(result.previousRunAsUser === undefined
|
|
780
|
+
? {}
|
|
781
|
+
: { previousRunAsUser: result.previousRunAsUser, }),
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
description: "Set the user a scenario runs as (--user LOGIN, administrators only) or clear it (--clear: it then runs as its last editor). The result reports the effective run-as; scenario status shows runAsUser.",
|
|
785
|
+
examples: [
|
|
786
|
+
"dss scenario run-as-set my_scenario --user service_account",
|
|
787
|
+
"dss scenario run-as-set my_scenario --clear --dry-run",
|
|
788
|
+
],
|
|
789
|
+
},
|
|
380
790
|
});
|
package/dist/src/cli/contract.js
CHANGED
|
@@ -253,6 +253,7 @@ const ALLOWED_CLEANUP_ACTIONS = new Set([
|
|
|
253
253
|
"data-collection delete",
|
|
254
254
|
"plugin delete",
|
|
255
255
|
"connection delete",
|
|
256
|
+
"messaging-channel delete",
|
|
256
257
|
"user delete",
|
|
257
258
|
"group delete",
|
|
258
259
|
]);
|
|
@@ -669,6 +670,9 @@ function inferSideEffect(resource, action) {
|
|
|
669
670
|
}
|
|
670
671
|
if (resource === "install-skill")
|
|
671
672
|
return "write";
|
|
673
|
+
// `send` matches no mutating verb, yet delivers real mail through the channel.
|
|
674
|
+
if (resource === "messaging-channel" && action === "send")
|
|
675
|
+
return "write";
|
|
672
676
|
if (resource === "sql" && action === "query")
|
|
673
677
|
return "write";
|
|
674
678
|
if (resource === "ml-task" && action === "train")
|
|
@@ -735,7 +739,7 @@ function inferSideEffect(resource, action) {
|
|
|
735
739
|
.test(action)
|
|
736
740
|
// Compound actions whose mutating verb is a suffix (e.g. permissions-set,
|
|
737
741
|
// dataset-compute, saved-model external-metadata-put).
|
|
738
|
-
|| /-(set|compute|restore|put)$/.test(action)) {
|
|
742
|
+
|| /-(set|compute|restore|put|add|remove)$/.test(action)) {
|
|
739
743
|
return "write";
|
|
740
744
|
}
|
|
741
745
|
return "read";
|
|
@@ -780,10 +784,15 @@ const STRING_OUTPUT_ACTIONS = new Set([
|
|
|
780
784
|
const SCOPED_OUTPUT_SHAPES = {
|
|
781
785
|
"folder.contents": "array",
|
|
782
786
|
"scenario.last-runs": "array",
|
|
787
|
+
"scenario.runs-by-date": "array",
|
|
788
|
+
"scenario.trigger-list": "array",
|
|
789
|
+
"scenario.reporter-list": "array",
|
|
790
|
+
"scenario.run-report": "string",
|
|
783
791
|
"plugin.usages": "object",
|
|
784
792
|
"plugin.download": "object",
|
|
785
793
|
"dataset.download": "object",
|
|
786
794
|
"project-library.get": "string",
|
|
795
|
+
"messaging-channel.permissions-get": "array",
|
|
787
796
|
"project-library.diff": "object",
|
|
788
797
|
};
|
|
789
798
|
function inferOutputShape(resource, action) {
|
|
@@ -920,6 +929,11 @@ const EXPLICIT_DESTRUCTIVE_KEYS = {
|
|
|
920
929
|
// MLflow cleanup permanently erases experiment-tracking data.
|
|
921
930
|
"mlflow.garbage-collect": true,
|
|
922
931
|
"mlflow.clean-db": true,
|
|
932
|
+
// Removing a trigger or reporter drops its configuration; no public route restores it.
|
|
933
|
+
"scenario.trigger-remove": true,
|
|
934
|
+
"scenario.reporter-remove": true,
|
|
935
|
+
// A sent mail is delivered to external recipients and cannot be recalled.
|
|
936
|
+
"messaging-channel.send": true,
|
|
923
937
|
};
|
|
924
938
|
function inferDestructiveLevel(resource, sideEffect, action) {
|
|
925
939
|
if (sideEffect !== "write")
|
|
@@ -946,7 +960,8 @@ function inferAsyncKind(resource, action) {
|
|
|
946
960
|
return "job";
|
|
947
961
|
if (resource === "future" && ["get", "peek", "wait", "abort",].includes(action))
|
|
948
962
|
return "future";
|
|
949
|
-
if (resource === "scenario"
|
|
963
|
+
if (resource === "scenario"
|
|
964
|
+
&& ["run", "run-and-wait", "status", "get-run-for-trigger",].includes(action)) {
|
|
950
965
|
return "future";
|
|
951
966
|
}
|
|
952
967
|
if (resource === "user" && ADMIN_SECURITY_FUTURE_ACTIONS[action] === true)
|
package/dist/src/cli/flags.js
CHANGED
|
@@ -76,6 +76,9 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
76
76
|
"no-auto-rebuild",
|
|
77
77
|
"all-users",
|
|
78
78
|
"with-scenarios",
|
|
79
|
+
"scenarios-only",
|
|
80
|
+
"only-finished",
|
|
81
|
+
"clear",
|
|
79
82
|
"for-everybody",
|
|
80
83
|
"payload-only",
|
|
81
84
|
"allow-same-path",
|
|
@@ -102,6 +105,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
102
105
|
"conda",
|
|
103
106
|
"force",
|
|
104
107
|
"partitioning-folder",
|
|
108
|
+
"plain-text",
|
|
109
|
+
"single-message",
|
|
105
110
|
]);
|
|
106
111
|
export const SHORT_FLAGS = {
|
|
107
112
|
v: "verbose",
|
|
@@ -231,6 +236,24 @@ export const VALUE_FLAGS = new Set([
|
|
|
231
236
|
"repository",
|
|
232
237
|
"request-timeout",
|
|
233
238
|
"params",
|
|
239
|
+
"params-file",
|
|
240
|
+
"frequency",
|
|
241
|
+
"every",
|
|
242
|
+
"hour",
|
|
243
|
+
"minute",
|
|
244
|
+
"days",
|
|
245
|
+
"day-of-month",
|
|
246
|
+
"run-on",
|
|
247
|
+
"start-date",
|
|
248
|
+
"timezone",
|
|
249
|
+
"index",
|
|
250
|
+
"channel",
|
|
251
|
+
"recipient",
|
|
252
|
+
"subject",
|
|
253
|
+
"condition",
|
|
254
|
+
"phase",
|
|
255
|
+
"user",
|
|
256
|
+
"trigger-id",
|
|
234
257
|
"password-env",
|
|
235
258
|
"results-per-page",
|
|
236
259
|
"record-cleanup",
|
|
@@ -324,6 +347,16 @@ export const VALUE_FLAGS = new Set([
|
|
|
324
347
|
"time-variable",
|
|
325
348
|
"timeseries-ids",
|
|
326
349
|
"full-reguess",
|
|
350
|
+
"cc",
|
|
351
|
+
"bcc",
|
|
352
|
+
"subject",
|
|
353
|
+
"body",
|
|
354
|
+
"body-file",
|
|
355
|
+
"attachment",
|
|
356
|
+
"family",
|
|
357
|
+
"permissions",
|
|
358
|
+
"automation-node-id",
|
|
359
|
+
"id",
|
|
327
360
|
]);
|
|
328
361
|
export const REPEATABLE_VALUE_FLAGS = new Set([
|
|
329
362
|
"dataset",
|
|
@@ -337,6 +370,7 @@ export const REPEATABLE_VALUE_FLAGS = new Set([
|
|
|
337
370
|
"replace-input",
|
|
338
371
|
"replace-output",
|
|
339
372
|
"replace-payload-text",
|
|
373
|
+
"attachment",
|
|
340
374
|
]);
|
|
341
375
|
export const KNOWN_LONG_FLAGS = new Set([
|
|
342
376
|
...BOOLEAN_FLAGS,
|