dataiku-sdk 3.2.0 → 3.3.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 +149 -46
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +102 -0
- package/dist/packages/types/src/index.js +76 -0
- package/dist/src/cli/commands/app.js +57 -15
- package/dist/src/cli/commands/bundle.js +1 -1
- package/dist/src/cli/commands/code-env.js +13 -7
- package/dist/src/cli/commands/connection.d.ts +9 -0
- package/dist/src/cli/commands/connection.js +267 -1
- package/dist/src/cli/commands/data-collection.d.ts +2 -0
- package/dist/src/cli/commands/data-collection.js +164 -0
- package/dist/src/cli/commands/dataset.js +161 -32
- package/dist/src/cli/commands/future.js +2 -2
- package/dist/src/cli/commands/group.d.ts +2 -0
- package/dist/src/cli/commands/group.js +109 -0
- package/dist/src/cli/commands/index.js +151 -78
- package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
- package/dist/src/cli/commands/knowledge-bank.js +80 -0
- package/dist/src/cli/commands/llm.d.ts +2 -0
- package/dist/src/cli/commands/llm.js +93 -0
- package/dist/src/cli/commands/macro.d.ts +2 -0
- package/dist/src/cli/commands/macro.js +221 -0
- package/dist/src/cli/commands/ml-task.js +18 -6
- package/dist/src/cli/commands/notebook.js +6 -2
- package/dist/src/cli/commands/plugin.d.ts +2 -0
- package/dist/src/cli/commands/plugin.js +799 -0
- package/dist/src/cli/commands/project-folder.d.ts +2 -0
- package/dist/src/cli/commands/project-folder.js +197 -0
- package/dist/src/cli/commands/project-git.js +14 -54
- package/dist/src/cli/commands/project.js +52 -6
- package/dist/src/cli/commands/recipe.js +36 -1
- package/dist/src/cli/commands/saved-model.d.ts +5 -0
- package/dist/src/cli/commands/saved-model.js +388 -1
- package/dist/src/cli/commands/scenario.js +138 -1
- package/dist/src/cli/commands/user.d.ts +8 -0
- package/dist/src/cli/commands/user.js +296 -0
- package/dist/src/cli/contract.d.ts +1 -2
- package/dist/src/cli/contract.js +1089 -103
- package/dist/src/cli/flags.js +30 -1
- package/dist/src/cli/helpers/app-successor.d.ts +19 -4
- package/dist/src/cli/helpers/app-successor.js +90 -30
- package/dist/src/cli/helpers/cleanup.js +3 -1
- package/dist/src/cli/helpers/flow-zone.js +1 -2
- package/dist/src/cli/output.d.ts +1 -1
- package/dist/src/cli/output.js +23 -10
- package/dist/src/cli/usage.d.ts +1 -0
- package/dist/src/cli/usage.js +47 -1
- package/dist/src/cli.js +147 -41
- package/dist/src/client.d.ts +83 -5
- package/dist/src/client.js +185 -29
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.js +2 -0
- package/dist/src/index.d.ts +16 -7
- package/dist/src/index.js +11 -2
- package/dist/src/resources/analyses.js +1 -8
- package/dist/src/resources/applications.d.ts +8 -6
- package/dist/src/resources/applications.js +21 -21
- package/dist/src/resources/base.d.ts +4 -0
- package/dist/src/resources/base.js +15 -0
- package/dist/src/resources/bundles.d.ts +9 -4
- package/dist/src/resources/bundles.js +10 -5
- package/dist/src/resources/connections.d.ts +78 -1
- package/dist/src/resources/connections.js +132 -1
- package/dist/src/resources/data-collections.d.ts +118 -0
- package/dist/src/resources/data-collections.js +49 -0
- package/dist/src/resources/datasets.d.ts +69 -0
- package/dist/src/resources/datasets.js +99 -1
- package/dist/src/resources/futures.d.ts +4 -1
- package/dist/src/resources/futures.js +38 -6
- package/dist/src/resources/groups.d.ts +47 -0
- package/dist/src/resources/groups.js +56 -0
- package/dist/src/resources/jobs.d.ts +8 -13
- package/dist/src/resources/jobs.js +41 -16
- package/dist/src/resources/knowledge-banks.d.ts +58 -0
- package/dist/src/resources/knowledge-banks.js +61 -0
- package/dist/src/resources/llms.d.ts +92 -0
- package/dist/src/resources/llms.js +62 -0
- package/dist/src/resources/macros.d.ts +121 -0
- package/dist/src/resources/macros.js +231 -0
- package/dist/src/resources/ml-tasks.d.ts +37 -2
- package/dist/src/resources/ml-tasks.js +100 -20
- package/dist/src/resources/model-evaluation-stores.js +1 -8
- package/dist/src/resources/notebooks.d.ts +43 -7
- package/dist/src/resources/notebooks.js +53 -13
- package/dist/src/resources/plugins.d.ts +265 -0
- package/dist/src/resources/plugins.js +458 -0
- package/dist/src/resources/project-folders.d.ts +71 -0
- package/dist/src/resources/project-folders.js +57 -0
- package/dist/src/resources/project-git.d.ts +6 -0
- package/dist/src/resources/project-git.js +69 -42
- package/dist/src/resources/project-library.d.ts +0 -1
- package/dist/src/resources/project-library.js +1 -17
- package/dist/src/resources/projects.d.ts +19 -1
- package/dist/src/resources/projects.js +27 -1
- package/dist/src/resources/recipes.d.ts +15 -1
- package/dist/src/resources/recipes.js +73 -6
- package/dist/src/resources/saved-models.d.ts +134 -1
- package/dist/src/resources/saved-models.js +214 -6
- package/dist/src/resources/scenarios.d.ts +105 -0
- package/dist/src/resources/scenarios.js +232 -106
- package/dist/src/resources/sql.js +5 -1
- package/dist/src/resources/users.d.ts +129 -0
- package/dist/src/resources/users.js +128 -0
- package/dist/src/resources/webapps.d.ts +15 -2
- package/dist/src/resources/webapps.js +18 -4
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/utils/deep-merge.d.ts +1 -1
- package/dist/src/utils/polling.d.ts +20 -0
- package/dist/src/utils/polling.js +27 -0
- package/dist/src/utils/response-file.js +1 -1
- package/dist/src/utils/secret-sanitize.d.ts +54 -0
- package/dist/src/utils/secret-sanitize.js +101 -0
- package/package.json +6 -5
- package/packages/types/dist/index.d.ts +102 -0
- package/packages/types/dist/index.js +76 -0
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +7 -5
- package/skills/dataiku-dss/references/app-releases.md +8 -7
- package/skills/dataiku-dss/references/discovery.md +4 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID, } from "node:crypto";
|
|
2
|
-
import { DataikuError, } from "../errors.js";
|
|
2
|
+
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
3
3
|
import { ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, } from "../schemas.js";
|
|
4
4
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
|
|
6
|
+
import { BaseResource, requireArrayResponse, requireNonEmpty, } from "./base.js";
|
|
7
7
|
export const SCENARIO_CANONICAL_EDITABLE_FIELDS = [
|
|
8
8
|
"params.steps",
|
|
9
9
|
"params.triggers",
|
|
@@ -12,6 +12,8 @@ export const SCENARIO_CANONICAL_EDITABLE_FIELDS = [
|
|
|
12
12
|
"active",
|
|
13
13
|
"name",
|
|
14
14
|
];
|
|
15
|
+
/** Default byte cap applied when fetching a scenario run log. */
|
|
16
|
+
export const DEFAULT_SCENARIO_MAX_LOG_BYTES = 1_048_576;
|
|
15
17
|
/**
|
|
16
18
|
* Wraps a primary code-run failure so the original error (and its taxonomy:
|
|
17
19
|
* category, exit-code semantics) stays intact while the concurrent throwaway
|
|
@@ -30,6 +32,53 @@ export class ScenarioScriptRunWithCleanupFailureError extends Error {
|
|
|
30
32
|
const DEFAULT_CODE_RUN_MAX_LOG_BYTES = 1_048_576;
|
|
31
33
|
const CODE_RUN_OUTPUT_START = "<<<DSS_CODE_RUN_OUTPUT_b7e3a1>>>";
|
|
32
34
|
const CODE_RUN_OUTPUT_END = "<<<DSS_CODE_RUN_OUTPUT_END_b7e3a1>>>";
|
|
35
|
+
/** Prefer the current runId field, with the legacy id field as fallback. */
|
|
36
|
+
function triggerRunIdOf(trigger, scenarioId) {
|
|
37
|
+
const runId = trigger?.runId ?? trigger?.id;
|
|
38
|
+
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
39
|
+
throw new DataikuError(200, "Unexpected Response", `Scenario${scenarioId ? ` "${scenarioId}"` : ""} run response did not contain a usable run identifier.`);
|
|
40
|
+
}
|
|
41
|
+
return runId;
|
|
42
|
+
}
|
|
43
|
+
function triggerQueryOf(trigger) {
|
|
44
|
+
const triggerObj = trigger.trigger;
|
|
45
|
+
const triggerId = triggerObj?.id ?? "manual";
|
|
46
|
+
const triggerRunId = triggerRunIdOf(trigger);
|
|
47
|
+
return `triggerId=${encodeURIComponent(triggerId)}&triggerRunId=${encodeURIComponent(triggerRunId)}`;
|
|
48
|
+
}
|
|
49
|
+
/** A finite number, or undefined for anything else (absent/malformed JSON fields). */
|
|
50
|
+
function optionalNumber(value) {
|
|
51
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
/** Map one get-last-runs entry to a ScenarioRunSummary, tolerating missing fields. */
|
|
54
|
+
function scenarioRunSummaryFromRaw(entry) {
|
|
55
|
+
if (!isRecord(entry))
|
|
56
|
+
return { runId: "unknown", };
|
|
57
|
+
const runId = typeof entry.runId === "string" ? entry.runId : "unknown";
|
|
58
|
+
const optionalRecord = (value) => isRecord(value) ? value : undefined;
|
|
59
|
+
return {
|
|
60
|
+
runId,
|
|
61
|
+
...(optionalNumber(entry.start) !== undefined ? { start: optionalNumber(entry.start), } : {}),
|
|
62
|
+
...(optionalNumber(entry.end) !== undefined ? { end: optionalNumber(entry.end), } : {}),
|
|
63
|
+
...(optionalRecord(entry.scenario) !== undefined
|
|
64
|
+
? { scenario: optionalRecord(entry.scenario), }
|
|
65
|
+
: {}),
|
|
66
|
+
...(optionalRecord(entry.variables) !== undefined
|
|
67
|
+
? { variables: optionalRecord(entry.variables), }
|
|
68
|
+
: {}),
|
|
69
|
+
...(optionalRecord(entry.result) !== undefined
|
|
70
|
+
? { result: optionalRecord(entry.result), }
|
|
71
|
+
: {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Map a light-endpoint response to the documented ScenarioWithStatus shape. */
|
|
75
|
+
function scenarioLightStatusFromRaw(raw) {
|
|
76
|
+
return {
|
|
77
|
+
...(typeof raw.id === "string" ? { id: raw.id, } : {}),
|
|
78
|
+
...(typeof raw.running === "boolean" ? { running: raw.running, } : {}),
|
|
79
|
+
...(typeof raw.active === "boolean" ? { active: raw.active, } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
33
82
|
/**
|
|
34
83
|
* Wrap a user Python script so its stdout/stderr (and any traceback) are captured
|
|
35
84
|
* into a buffer and re-emitted between unique markers, isolated from DSS scenario
|
|
@@ -290,11 +339,9 @@ export class ScenariosResource extends BaseResource {
|
|
|
290
339
|
}
|
|
291
340
|
/** Trigger a scenario run. */
|
|
292
341
|
async run(scenarioId, projectKey) {
|
|
293
|
-
const scEnc = encodeURIComponent(scenarioId);
|
|
342
|
+
const scEnc = encodeURIComponent(requireNonEmpty(scenarioId, "scenarioId"));
|
|
294
343
|
const result = await this.client.post(`/public/api/projects/${this.enc(projectKey)}/scenarios/${scEnc}/run/`, {});
|
|
295
|
-
return {
|
|
296
|
-
runId: result.id ?? result.runId ?? "unknown",
|
|
297
|
-
};
|
|
344
|
+
return { runId: triggerRunIdOf(result, scenarioId), };
|
|
298
345
|
}
|
|
299
346
|
/** Get the light/status view of a scenario. */
|
|
300
347
|
async status(scenarioId, projectKey) {
|
|
@@ -344,76 +391,27 @@ export class ScenariosResource extends BaseResource {
|
|
|
344
391
|
// run id; resolve the real run via get-run-for-trigger so a completed scenario
|
|
345
392
|
// is not misreported as a timeout (the trigger id never matches lastRun).
|
|
346
393
|
const trigger = await this.client.post(`${base}/run/`, {});
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
let timedOut = false;
|
|
358
|
-
while (true) {
|
|
359
|
-
if (Date.now() - startedAt >= timeout) {
|
|
360
|
-
outcome = "TIMEOUT";
|
|
361
|
-
timedOut = true;
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
pollCount += 1;
|
|
365
|
-
const run = await this.client.get(`${base}/get-run-for-trigger?${trigQuery}`);
|
|
366
|
-
const scenarioRun = run.scenarioRun;
|
|
367
|
-
if (scenarioRun) {
|
|
368
|
-
runId = scenarioRun.runId ?? runId;
|
|
369
|
-
const result = scenarioRun.result;
|
|
370
|
-
const finished = result?.outcome;
|
|
371
|
-
if (finished) {
|
|
372
|
-
outcome = finished;
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
const nextDelayMs = computeNextPollDelayMs({
|
|
377
|
-
pollCount,
|
|
378
|
-
baseIntervalMs,
|
|
379
|
-
adaptiveEnabled: adaptivePolling,
|
|
380
|
-
});
|
|
381
|
-
const { promise, resolve, } = Promise.withResolvers();
|
|
382
|
-
setTimeout(resolve, Math.min(nextDelayMs, Math.max(1, timeout - (Date.now() - startedAt))));
|
|
383
|
-
await promise;
|
|
384
|
-
}
|
|
385
|
-
const resolvedRunId = runId || triggerRunId;
|
|
386
|
-
let steps;
|
|
387
|
-
if (!timedOut && runId) {
|
|
388
|
-
try {
|
|
389
|
-
const details = await this.client.get(`${base}/${encodeURIComponent(runId)}/`);
|
|
390
|
-
const stepRuns = details.stepRuns ?? [];
|
|
391
|
-
const mapped = stepRuns.map((entry) => {
|
|
392
|
-
const stepDef = entry.step;
|
|
393
|
-
const stepResult = entry.result;
|
|
394
|
-
return {
|
|
395
|
-
name: stepDef?.name,
|
|
396
|
-
type: stepDef?.type,
|
|
397
|
-
outcome: stepResult?.outcome ?? "UNKNOWN",
|
|
398
|
-
...scenarioStepWarningSummary(stepResult),
|
|
399
|
-
};
|
|
400
|
-
});
|
|
401
|
-
if (mapped.length > 0)
|
|
402
|
-
steps = mapped;
|
|
403
|
-
}
|
|
404
|
-
catch {
|
|
405
|
-
// Best-effort diagnostics: never fail the wait because the run report is unavailable.
|
|
406
|
-
}
|
|
407
|
-
}
|
|
394
|
+
const tracked = await this.trackTriggerRun(scenarioId, base, trigger, {
|
|
395
|
+
startedAt,
|
|
396
|
+
baseIntervalMs,
|
|
397
|
+
adaptivePolling,
|
|
398
|
+
timeout,
|
|
399
|
+
});
|
|
400
|
+
const resolvedRunId = tracked.runId || triggerRunIdOf(trigger);
|
|
401
|
+
const steps = tracked.timedOut || !tracked.runId
|
|
402
|
+
? undefined
|
|
403
|
+
: await this.runStepSummaries(base, tracked.runId);
|
|
408
404
|
return {
|
|
409
405
|
scenarioId,
|
|
410
406
|
runId: resolvedRunId,
|
|
411
|
-
outcome,
|
|
412
|
-
success: outcome === "SUCCESS",
|
|
407
|
+
outcome: tracked.outcome,
|
|
408
|
+
success: tracked.outcome === "SUCCESS",
|
|
413
409
|
elapsedMs: Date.now() - startedAt,
|
|
414
|
-
pollCount,
|
|
415
|
-
...(
|
|
416
|
-
|
|
410
|
+
pollCount: tracked.pollCount,
|
|
411
|
+
...(triggerRunIdOf(trigger) !== resolvedRunId
|
|
412
|
+
? { triggerRunId: triggerRunIdOf(trigger), }
|
|
413
|
+
: {}),
|
|
414
|
+
...(tracked.timedOut ? { timedOut: true, } : {}),
|
|
417
415
|
...(steps ? { steps, } : {}),
|
|
418
416
|
};
|
|
419
417
|
}
|
|
@@ -453,40 +451,21 @@ export class ScenariosResource extends BaseResource {
|
|
|
453
451
|
});
|
|
454
452
|
await this.client.putVoid(`${base}/payload`, { script: buildCodeRunScript(script), extension: "py", });
|
|
455
453
|
const trigger = await this.client.post(`${base}/run/`, {});
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
timedOut = true;
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
469
|
-
pollCount += 1;
|
|
470
|
-
const run = await this.client.get(`${base}/get-run-for-trigger?${trigQuery}`);
|
|
471
|
-
const scenarioRun = run.scenarioRun;
|
|
472
|
-
if (scenarioRun) {
|
|
473
|
-
runId = scenarioRun.runId ?? runId;
|
|
474
|
-
const result = scenarioRun.result;
|
|
475
|
-
const finished = result?.outcome;
|
|
476
|
-
if (finished) {
|
|
477
|
-
outcome = finished;
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const nextDelayMs = computeNextPollDelayMs({
|
|
482
|
-
pollCount,
|
|
483
|
-
baseIntervalMs,
|
|
484
|
-
adaptiveEnabled: adaptivePolling,
|
|
485
|
-
});
|
|
486
|
-
await new Promise((r) => setTimeout(r, Math.min(nextDelayMs, Math.max(1, timeout - (Date.now() - startedAt)))));
|
|
487
|
-
}
|
|
454
|
+
const tracked = await this.trackTriggerRun(scenarioId, base, trigger, {
|
|
455
|
+
startedAt,
|
|
456
|
+
baseIntervalMs,
|
|
457
|
+
adaptivePolling,
|
|
458
|
+
timeout,
|
|
459
|
+
});
|
|
460
|
+
outcome = tracked.outcome;
|
|
461
|
+
runId = tracked.runId;
|
|
462
|
+
pollCount = tracked.pollCount;
|
|
463
|
+
timedOut = tracked.timedOut;
|
|
488
464
|
if (runId && outcome !== "TIMEOUT") {
|
|
489
|
-
const limitedLog = await this.
|
|
465
|
+
const limitedLog = await this.getRunLog(scenarioId, runId, {
|
|
466
|
+
projectKey: opts?.projectKey,
|
|
467
|
+
maxLogBytes,
|
|
468
|
+
});
|
|
490
469
|
log = limitedLog.text;
|
|
491
470
|
logTruncated = limitedLog.truncated;
|
|
492
471
|
}
|
|
@@ -521,6 +500,153 @@ export class ScenariosResource extends BaseResource {
|
|
|
521
500
|
cleanup,
|
|
522
501
|
};
|
|
523
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Abort a running scenario (POST /scenarios/{id}/abort). The call returns as
|
|
505
|
+
* soon as DSS accepts the abort request; the scenario may take some time to
|
|
506
|
+
* actually stop, so this does not wait for termination.
|
|
507
|
+
*/
|
|
508
|
+
async abort(scenarioId, projectKey) {
|
|
509
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
510
|
+
await this.client.post(`/public/api/projects/${this.enc(projectKey)}/scenarios/${scEnc}/abort`);
|
|
511
|
+
}
|
|
512
|
+
/** Get the raw last-runs list (GET /scenarios/{id}/get-last-runs/?limit=N). */
|
|
513
|
+
async getLastRuns(scenarioId, opts) {
|
|
514
|
+
if (opts?.limit !== undefined && (!Number.isInteger(opts.limit) || opts.limit < 1)) {
|
|
515
|
+
throw new ClientValidationError(`limit must be a positive integer, got ${opts.limit}.`);
|
|
516
|
+
}
|
|
517
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
518
|
+
const query = opts?.limit !== undefined ? `?limit=${opts.limit}` : "";
|
|
519
|
+
const raw = await this.client.get(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/get-last-runs/${query}`);
|
|
520
|
+
return requireArrayResponse(raw, "scenarios.getLastRuns").map(scenarioRunSummaryFromRaw);
|
|
521
|
+
}
|
|
522
|
+
/** Get the details of a specific run (GET /scenarios/{id}/{runId}). */
|
|
523
|
+
async getRunDetails(scenarioId, runId, opts) {
|
|
524
|
+
return this.client.get(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${encodeURIComponent(scenarioId)}/${encodeURIComponent(runId)}/`);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Get the log of a scenario run (GET /scenarios/{id}/{runId}/log), optionally
|
|
528
|
+
* scoped to a single step via `stepId`. The text is byte-bounded: at most
|
|
529
|
+
* `maxLogBytes` (default 1 MiB) of the beginning of the log is retained and
|
|
530
|
+
* `truncated` reports whether the cap cut the body.
|
|
531
|
+
*/
|
|
532
|
+
async getRunLog(scenarioId, runId, opts) {
|
|
533
|
+
if (opts?.stepId !== undefined)
|
|
534
|
+
requireNonEmpty(opts.stepId, "stepId");
|
|
535
|
+
const maxLogBytes = Math.max(0, Math.floor(opts?.maxLogBytes ?? DEFAULT_SCENARIO_MAX_LOG_BYTES));
|
|
536
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
537
|
+
const query = opts?.stepId !== undefined
|
|
538
|
+
? `?stepId=${encodeURIComponent(opts.stepId)}`
|
|
539
|
+
: "";
|
|
540
|
+
const { text, truncated, } = await this.client.getTextLimited(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/${encodeURIComponent(runId)}/log${query}`, maxLogBytes);
|
|
541
|
+
return { text, truncated, maxLogBytes, };
|
|
542
|
+
}
|
|
543
|
+
/** Get the payload of a (custom) scenario (GET /scenarios/{id}/payload). */
|
|
544
|
+
async getPayload(scenarioId, opts) {
|
|
545
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
546
|
+
return this.client.get(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/payload`);
|
|
547
|
+
}
|
|
548
|
+
/** Update the payload of a (custom) scenario (PUT /scenarios/{id}/payload). */
|
|
549
|
+
async setPayload(scenarioId, payload, opts) {
|
|
550
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
551
|
+
await this.client.putVoid(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/payload`, payload);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Toggle the active flag through the light endpoints (GET then PUT
|
|
555
|
+
* /scenarios/{id}/light) without a full-definition round-trip. Returns the
|
|
556
|
+
* light status observed after the PUT.
|
|
557
|
+
*/
|
|
558
|
+
async setActive(scenarioId, active, opts) {
|
|
559
|
+
const scEnc = encodeURIComponent(scenarioId);
|
|
560
|
+
const base = `/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/light`;
|
|
561
|
+
const beforeRaw = await this.client.get(base);
|
|
562
|
+
const before = typeof beforeRaw.active === "boolean" ? beforeRaw.active : undefined;
|
|
563
|
+
// DSS parses the light PUT as a full Scenario object: a minimal {id, active} body 400s with
|
|
564
|
+
// "Could not parse a Scenario from request body" (missing name). Echo the complete GET body
|
|
565
|
+
// with active overridden — the same contract as the python client's set_definition(with_status).
|
|
566
|
+
const nextLight = { ...beforeRaw, active, };
|
|
567
|
+
await this.client.putVoid(base, nextLight);
|
|
568
|
+
const after = await this.client.get(base);
|
|
569
|
+
return {
|
|
570
|
+
scenarioId,
|
|
571
|
+
active: typeof after.active === "boolean" ? after.active : active,
|
|
572
|
+
...(before !== undefined ? { before, } : {}),
|
|
573
|
+
status: scenarioLightStatusFromRaw(after),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Poll get-run-for-trigger until the triggered run reports an outcome or the
|
|
578
|
+
* deadline elapses. Shared by runAndWait and runScript: POST /run/ returns a
|
|
579
|
+
* TRIGGER run id, which differs from the actual scenario run id, so the real
|
|
580
|
+
* run is resolved via get-run-for-trigger (the trigger id never matches
|
|
581
|
+
* lastRun and would misreport a completed scenario as a timeout).
|
|
582
|
+
*/
|
|
583
|
+
async trackTriggerRun(scenarioId, base, trigger, timing) {
|
|
584
|
+
triggerRunIdOf(trigger, scenarioId);
|
|
585
|
+
const trigQuery = triggerQueryOf(trigger);
|
|
586
|
+
let runId = "";
|
|
587
|
+
let pollCount = 0;
|
|
588
|
+
while (true) {
|
|
589
|
+
if (Date.now() - timing.startedAt >= timing.timeout) {
|
|
590
|
+
return { runId, outcome: "TIMEOUT", timedOut: true, pollCount, };
|
|
591
|
+
}
|
|
592
|
+
pollCount += 1;
|
|
593
|
+
// Budget the per-poll GET by the remaining wait so a stalling
|
|
594
|
+
// trigger-status endpoint cannot defeat the overall run deadline.
|
|
595
|
+
const remainingMs = Math.max(1, timing.timeout - (Date.now() - timing.startedAt));
|
|
596
|
+
let run;
|
|
597
|
+
try {
|
|
598
|
+
run = await this.client.get(`${base}/get-run-for-trigger?${trigQuery}`, { timeoutMs: remainingMs, });
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
if (!isRequestDeadlineError(error, timing.startedAt + timing.timeout))
|
|
602
|
+
throw error;
|
|
603
|
+
return { runId, outcome: "TIMEOUT", timedOut: true, pollCount, };
|
|
604
|
+
}
|
|
605
|
+
const scenarioRun = run.scenarioRun;
|
|
606
|
+
if (scenarioRun) {
|
|
607
|
+
runId = scenarioRun.runId ?? runId;
|
|
608
|
+
const result = scenarioRun.result;
|
|
609
|
+
const finished = result?.outcome;
|
|
610
|
+
if (finished) {
|
|
611
|
+
return { runId, outcome: finished, timedOut: false, pollCount, };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const nextDelayMs = computeNextPollDelayMs({
|
|
615
|
+
pollCount,
|
|
616
|
+
baseIntervalMs: timing.baseIntervalMs,
|
|
617
|
+
adaptiveEnabled: timing.adaptivePolling,
|
|
618
|
+
});
|
|
619
|
+
const { promise, resolve, } = Promise.withResolvers();
|
|
620
|
+
setTimeout(resolve, Math.min(nextDelayMs, Math.max(1, timing.timeout - (Date.now() - timing.startedAt))));
|
|
621
|
+
await promise;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Best-effort step outcome summaries from the run report
|
|
626
|
+
* (GET /scenarios/{id}/{runId}). Never fails the wait: an unavailable
|
|
627
|
+
* run report returns undefined.
|
|
628
|
+
*/
|
|
629
|
+
async runStepSummaries(base, runId) {
|
|
630
|
+
try {
|
|
631
|
+
const details = await this.client.get(`${base}/${encodeURIComponent(runId)}/`);
|
|
632
|
+
const stepRuns = details.stepRuns ?? [];
|
|
633
|
+
const mapped = stepRuns.map((entry) => {
|
|
634
|
+
const stepDef = entry.step;
|
|
635
|
+
const stepResult = entry.result;
|
|
636
|
+
return {
|
|
637
|
+
name: stepDef?.name,
|
|
638
|
+
type: stepDef?.type,
|
|
639
|
+
outcome: stepResult?.outcome ?? "UNKNOWN",
|
|
640
|
+
...scenarioStepWarningSummary(stepResult),
|
|
641
|
+
};
|
|
642
|
+
});
|
|
643
|
+
return mapped.length > 0 ? mapped : undefined;
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
// Best-effort diagnostics: never fail the wait because the run report is unavailable.
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
524
650
|
/**
|
|
525
651
|
* Delete the throwaway scenario at the end of a code run, or report the
|
|
526
652
|
* explicit keep outcome. Never throws: delete failures are surfaced in the
|
|
@@ -101,7 +101,11 @@ function withSqlErrorContext(error) {
|
|
|
101
101
|
catch {
|
|
102
102
|
body = `SQL query failed: ${detail}\n${error.body}`;
|
|
103
103
|
}
|
|
104
|
-
throw new DataikuError(error.status, error.statusText, body, error.retry, error.requestId, {
|
|
104
|
+
throw new DataikuError(error.status, error.statusText, body, error.retry, error.requestId, {
|
|
105
|
+
target: error.trustedTarget,
|
|
106
|
+
elapsedMs: error.trustedElapsedMs,
|
|
107
|
+
bodyTruncated: error.bodyTruncated,
|
|
108
|
+
});
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
throw error;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { FutureState } from "../schemas.js";
|
|
2
|
+
import { BaseResource } from "./base.js";
|
|
3
|
+
/**
|
|
4
|
+
* A DSS user as returned by the admin Security endpoints. The documented
|
|
5
|
+
* attributes below are stable; DSS may return undocumented extra attributes
|
|
6
|
+
* (per the API docs they must not be modified on update), so they are preserved
|
|
7
|
+
* under `Record<string, unknown>` and are never stripped by this SDK.
|
|
8
|
+
*/
|
|
9
|
+
export interface DssUser extends Record<string, unknown> {
|
|
10
|
+
login?: string;
|
|
11
|
+
sourceType?: string;
|
|
12
|
+
displayName?: string;
|
|
13
|
+
groups?: string[];
|
|
14
|
+
/** "True if the user is allowed to write native code" (string or boolean per DSS). */
|
|
15
|
+
codeAllowed?: unknown;
|
|
16
|
+
email?: string;
|
|
17
|
+
password?: string;
|
|
18
|
+
userProfile?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Last activity timestamps for one user. Epoch-millisecond timestamps are
|
|
22
|
+
* absent for a user who has never logged in or whose session was never loaded.
|
|
23
|
+
*/
|
|
24
|
+
export interface UserActivity extends Record<string, unknown> {
|
|
25
|
+
login?: string;
|
|
26
|
+
lastSuccessfulLogin?: number | null;
|
|
27
|
+
lastFailedLogin?: number | null;
|
|
28
|
+
lastLoaded?: number | null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One entry of the external-users listing: an externally sourced user together
|
|
32
|
+
* with its provisioning status against DSS groups/profiles.
|
|
33
|
+
*/
|
|
34
|
+
export interface ExternalUserEntry extends Record<string, unknown> {
|
|
35
|
+
status?: string;
|
|
36
|
+
userAttributes?: Record<string, unknown>;
|
|
37
|
+
profile?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Body of `POST /admin/users` (create). `login` is required by DSS; everything
|
|
41
|
+
* else is optional because DSS accepts partial definitions and fills defaults.
|
|
42
|
+
* Secret attributes (e.g. `password`) are forwarded verbatim over TLS but must
|
|
43
|
+
* never be logged — see the CLI-layer redaction in commands/user.ts.
|
|
44
|
+
*/
|
|
45
|
+
export interface UserCreateRequest extends Record<string, unknown> {
|
|
46
|
+
login: string;
|
|
47
|
+
sourceType?: string;
|
|
48
|
+
displayName?: string;
|
|
49
|
+
email?: string;
|
|
50
|
+
groups?: string[];
|
|
51
|
+
userProfile?: string;
|
|
52
|
+
password?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Body of `POST /admin/users/actions/provision`. Each provisioned user carries
|
|
56
|
+
* the source (external) group names and the DSS group names to map them to.
|
|
57
|
+
*/
|
|
58
|
+
export interface ProvisionRequest extends Record<string, unknown> {
|
|
59
|
+
userSourceType: string;
|
|
60
|
+
users: Array<{
|
|
61
|
+
login?: string;
|
|
62
|
+
displayName?: string;
|
|
63
|
+
email?: string;
|
|
64
|
+
sourceGroupNames?: string[];
|
|
65
|
+
dkuGroupNames?: string[];
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
export declare class UsersResource extends BaseResource {
|
|
69
|
+
/**
|
|
70
|
+
* Retrieves the list of DSS users (Admin required).
|
|
71
|
+
*
|
|
72
|
+
* With `connected: true`, DSS additionally reports which users are currently
|
|
73
|
+
* connected. The connected status is detected over WebSockets per the docs;
|
|
74
|
+
* disabled WebSockets make some or all connected users report as offline.
|
|
75
|
+
*/
|
|
76
|
+
list(opts?: {
|
|
77
|
+
connected?: boolean;
|
|
78
|
+
}): Promise<DssUser[]>;
|
|
79
|
+
/** Retrieves a User object describing a DSS user (Admin required). */
|
|
80
|
+
get(login: string): Promise<DssUser>;
|
|
81
|
+
/**
|
|
82
|
+
* Adds a user account on DSS (Admin required). DSS answers 201 with
|
|
83
|
+
* `{msg: "Created user <login>"}`; the raw server record is returned as-is.
|
|
84
|
+
*/
|
|
85
|
+
create(body: UserCreateRequest): Promise<Record<string, unknown>>;
|
|
86
|
+
/**
|
|
87
|
+
* Updates a user (Admin required). Per the docs the body MUST have been
|
|
88
|
+
* obtained from a prior GET at the same URL; undocumented attributes should
|
|
89
|
+
* be passed through unmodified. DSS answers with `{msg: "Edited user ..."}`.
|
|
90
|
+
*/
|
|
91
|
+
update(login: string, body: DssUser): Promise<Record<string, unknown>>;
|
|
92
|
+
/** Deletes a DSS user (Admin required). */
|
|
93
|
+
delete(login: string): Promise<{
|
|
94
|
+
deleted: string;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Resyncs one user (Admin required). Returns a DSS future reference; poll
|
|
98
|
+
* with `client.futures` when the result is not inline.
|
|
99
|
+
*/
|
|
100
|
+
resync(login: string): Promise<FutureState>;
|
|
101
|
+
/**
|
|
102
|
+
* Resyncs multiple users (Admin required). Body is a plain array of logins
|
|
103
|
+
* per the docs. Returns a future reference.
|
|
104
|
+
*/
|
|
105
|
+
resyncMulti(logins: string[]): Promise<FutureState>;
|
|
106
|
+
/**
|
|
107
|
+
* Fetches externally sourced users and their provisioning status
|
|
108
|
+
* (Admin required). Returns a future whose `result` (when present) is an
|
|
109
|
+
* array of {@link ExternalUserEntry}; poll with `client.futures` when
|
|
110
|
+
* `hasResult` is false.
|
|
111
|
+
*/
|
|
112
|
+
externalUsers(): Promise<FutureState>;
|
|
113
|
+
/**
|
|
114
|
+
* Fetches externally sourced groups (Admin required). Returns a future whose
|
|
115
|
+
* `result` (when present) is an array of group-name strings; poll with
|
|
116
|
+
* `client.futures` when `hasResult` is false.
|
|
117
|
+
*/
|
|
118
|
+
externalGroups(): Promise<FutureState>;
|
|
119
|
+
/**
|
|
120
|
+
* Provisions users from an external source (Admin required). Body is the
|
|
121
|
+
* documented `{userSourceType, users[]}` request. Returns a future whose
|
|
122
|
+
* result carries a provisioning summary; poll with `client.futures`.
|
|
123
|
+
*/
|
|
124
|
+
provision(body: ProvisionRequest): Promise<FutureState>;
|
|
125
|
+
/** Gets the last activity timestamps for all users (Admin required). */
|
|
126
|
+
activityAll(): Promise<UserActivity[]>;
|
|
127
|
+
/** Gets the last activity timestamps for one user (Admin required). */
|
|
128
|
+
activity(login: string): Promise<UserActivity>;
|
|
129
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
|
+
import { FutureStateSchema, } from "../schemas.js";
|
|
3
|
+
import { BaseResource, requireArrayResponse, requireNonEmpty, } from "./base.js";
|
|
4
|
+
function validateGroupsBody(body, method) {
|
|
5
|
+
if (body.groups !== undefined && !Array.isArray(body.groups)) {
|
|
6
|
+
throw new ClientValidationError(`${method}: groups must be an array of group names.`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class UsersResource extends BaseResource {
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves the list of DSS users (Admin required).
|
|
12
|
+
*
|
|
13
|
+
* With `connected: true`, DSS additionally reports which users are currently
|
|
14
|
+
* connected. The connected status is detected over WebSockets per the docs;
|
|
15
|
+
* disabled WebSockets make some or all connected users report as offline.
|
|
16
|
+
*/
|
|
17
|
+
async list(opts) {
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
if (opts?.connected !== undefined)
|
|
20
|
+
params.set("connected", String(opts.connected));
|
|
21
|
+
const query = params.toString();
|
|
22
|
+
const raw = await this.client.get(`/public/api/admin/users/${query ? `?${query}` : ""}`);
|
|
23
|
+
return requireArrayResponse(raw, "users.list");
|
|
24
|
+
}
|
|
25
|
+
/** Retrieves a User object describing a DSS user (Admin required). */
|
|
26
|
+
async get(login) {
|
|
27
|
+
const enc = requireNonEmpty(login, "login").trim();
|
|
28
|
+
return this.client.get(`/public/api/admin/users/${encodeURIComponent(enc)}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Adds a user account on DSS (Admin required). DSS answers 201 with
|
|
32
|
+
* `{msg: "Created user <login>"}`; the raw server record is returned as-is.
|
|
33
|
+
*/
|
|
34
|
+
async create(body) {
|
|
35
|
+
if (typeof body?.login !== "string" || body.login.trim().length === 0) {
|
|
36
|
+
throw new ClientValidationError("users.create: body.login is required.");
|
|
37
|
+
}
|
|
38
|
+
validateGroupsBody(body, "users.create");
|
|
39
|
+
return this.client.post("/public/api/admin/users", body);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Updates a user (Admin required). Per the docs the body MUST have been
|
|
43
|
+
* obtained from a prior GET at the same URL; undocumented attributes should
|
|
44
|
+
* be passed through unmodified. DSS answers with `{msg: "Edited user ..."}`.
|
|
45
|
+
*/
|
|
46
|
+
async update(login, body) {
|
|
47
|
+
const enc = requireNonEmpty(login, "login").trim();
|
|
48
|
+
if (body === null || body === undefined || typeof body !== "object" || Array.isArray(body)) {
|
|
49
|
+
throw new ClientValidationError("users.update: body must be a User object.");
|
|
50
|
+
}
|
|
51
|
+
validateGroupsBody(body, "users.update");
|
|
52
|
+
return this.client.put(`/public/api/admin/users/${encodeURIComponent(enc)}`, body);
|
|
53
|
+
}
|
|
54
|
+
/** Deletes a DSS user (Admin required). */
|
|
55
|
+
async delete(login) {
|
|
56
|
+
const enc = requireNonEmpty(login, "login").trim();
|
|
57
|
+
await this.client.del(`/public/api/admin/users/${encodeURIComponent(enc)}`);
|
|
58
|
+
return { deleted: enc, };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resyncs one user (Admin required). Returns a DSS future reference; poll
|
|
62
|
+
* with `client.futures` when the result is not inline.
|
|
63
|
+
*/
|
|
64
|
+
async resync(login) {
|
|
65
|
+
const enc = requireNonEmpty(login, "login").trim();
|
|
66
|
+
const raw = await this.client.post(`/public/api/admin/users/${encodeURIComponent(enc)}/actions/resync`);
|
|
67
|
+
return this.client.safeParse(FutureStateSchema, raw, "users.resync");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resyncs multiple users (Admin required). Body is a plain array of logins
|
|
71
|
+
* per the docs. Returns a future reference.
|
|
72
|
+
*/
|
|
73
|
+
async resyncMulti(logins) {
|
|
74
|
+
if (!Array.isArray(logins) || logins.length === 0
|
|
75
|
+
|| logins.some((l) => typeof l !== "string" || l.trim().length === 0)) {
|
|
76
|
+
throw new ClientValidationError("users.resyncMulti: logins must be a non-empty array of logins.");
|
|
77
|
+
}
|
|
78
|
+
const raw = await this.client.post("/public/api/admin/users/actions/resync-multi", logins);
|
|
79
|
+
return this.client.safeParse(FutureStateSchema, raw, "users.resyncMulti");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fetches externally sourced users and their provisioning status
|
|
83
|
+
* (Admin required). Returns a future whose `result` (when present) is an
|
|
84
|
+
* array of {@link ExternalUserEntry}; poll with `client.futures` when
|
|
85
|
+
* `hasResult` is false.
|
|
86
|
+
*/
|
|
87
|
+
async externalUsers() {
|
|
88
|
+
const raw = await this.client.get("/public/api/admin/users/actions/external-users");
|
|
89
|
+
return this.client.safeParse(FutureStateSchema, raw, "users.externalUsers");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Fetches externally sourced groups (Admin required). Returns a future whose
|
|
93
|
+
* `result` (when present) is an array of group-name strings; poll with
|
|
94
|
+
* `client.futures` when `hasResult` is false.
|
|
95
|
+
*/
|
|
96
|
+
async externalGroups() {
|
|
97
|
+
const raw = await this.client.get("/public/api/admin/users/actions/external-groups");
|
|
98
|
+
return this.client.safeParse(FutureStateSchema, raw, "users.externalGroups");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Provisions users from an external source (Admin required). Body is the
|
|
102
|
+
* documented `{userSourceType, users[]}` request. Returns a future whose
|
|
103
|
+
* result carries a provisioning summary; poll with `client.futures`.
|
|
104
|
+
*/
|
|
105
|
+
async provision(body) {
|
|
106
|
+
if (body === null || body === undefined || typeof body !== "object" || Array.isArray(body)) {
|
|
107
|
+
throw new ClientValidationError("users.provision: body must be an object.");
|
|
108
|
+
}
|
|
109
|
+
if (typeof body.userSourceType !== "string" || body.userSourceType.trim().length === 0) {
|
|
110
|
+
throw new ClientValidationError("users.provision: body.userSourceType is required.");
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(body.users)) {
|
|
113
|
+
throw new ClientValidationError("users.provision: body.users must be an array.");
|
|
114
|
+
}
|
|
115
|
+
const raw = await this.client.post("/public/api/admin/users/actions/provision", body);
|
|
116
|
+
return this.client.safeParse(FutureStateSchema, raw, "users.provision");
|
|
117
|
+
}
|
|
118
|
+
/** Gets the last activity timestamps for all users (Admin required). */
|
|
119
|
+
async activityAll() {
|
|
120
|
+
const raw = await this.client.get("/public/api/admin/users-activity");
|
|
121
|
+
return requireArrayResponse(raw, "users.activityAll");
|
|
122
|
+
}
|
|
123
|
+
/** Gets the last activity timestamps for one user (Admin required). */
|
|
124
|
+
async activity(login) {
|
|
125
|
+
const enc = requireNonEmpty(login, "login").trim();
|
|
126
|
+
return this.client.get(`/public/api/admin/users/${encodeURIComponent(enc)}/activity`);
|
|
127
|
+
}
|
|
128
|
+
}
|