dataiku-sdk 3.6.1 → 3.8.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 +14 -6
- package/bin/bun-version.js +14 -0
- package/bin/dss.js +11 -15
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +114 -5
- package/dist/packages/types/src/index.js +75 -2
- package/dist/src/cli/coerce.d.ts +2 -0
- package/dist/src/cli/coerce.js +20 -16
- package/dist/src/cli/command-syntax.json +3369 -144
- package/dist/src/cli/commands/analysis.js +19 -1
- package/dist/src/cli/commands/bundle.js +22 -0
- package/dist/src/cli/commands/connection.js +26 -2
- package/dist/src/cli/commands/data-quality.js +33 -1
- package/dist/src/cli/commands/dataset.js +76 -9
- package/dist/src/cli/commands/discussion.js +15 -0
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/flow-zone.js +241 -46
- package/dist/src/cli/commands/flow.d.ts +5 -0
- package/dist/src/cli/commands/flow.js +57 -0
- package/dist/src/cli/commands/future.js +8 -0
- package/dist/src/cli/commands/index.js +6 -0
- package/dist/src/cli/commands/job.js +44 -6
- package/dist/src/cli/commands/metrics.js +21 -1
- package/dist/src/cli/commands/ml-task.d.ts +5 -0
- package/dist/src/cli/commands/ml-task.js +86 -16
- package/dist/src/cli/commands/mlflow.d.ts +11 -0
- package/dist/src/cli/commands/mlflow.js +121 -0
- package/dist/src/cli/commands/plugin.js +5 -11
- package/dist/src/cli/commands/project-deployer.js +70 -0
- package/dist/src/cli/commands/project-library.js +2 -5
- package/dist/src/cli/commands/project.js +8 -0
- package/dist/src/cli/commands/recipe.d.ts +13 -0
- package/dist/src/cli/commands/recipe.js +130 -7
- package/dist/src/cli/commands/saved-model.d.ts +1 -1
- package/dist/src/cli/commands/saved-model.js +9 -76
- package/dist/src/cli/commands/streaming-endpoint.js +30 -1
- package/dist/src/cli/commands/webapp.js +8 -0
- package/dist/src/cli/commands/wiki.js +78 -0
- package/dist/src/cli/commands/workspace.js +8 -0
- package/dist/src/cli/contract.d.ts +16 -15
- package/dist/src/cli/contract.js +150 -3407
- package/dist/src/cli/doctor.d.ts +13 -0
- package/dist/src/cli/doctor.js +43 -1
- package/dist/src/cli/flags.d.ts +14 -1
- package/dist/src/cli/flags.js +106 -8
- package/dist/src/cli/helpers/app-successor.js +1 -2
- package/dist/src/cli/helpers/dataset.d.ts +12 -0
- package/dist/src/cli/helpers/dataset.js +36 -0
- package/dist/src/cli/helpers/documentation.d.ts +21 -0
- package/dist/src/cli/helpers/documentation.js +53 -0
- package/dist/src/cli/helpers/flow-zone.d.ts +21 -0
- package/dist/src/cli/helpers/flow-zone.js +144 -4
- package/dist/src/cli/helpers/trained-model.d.ts +26 -0
- package/dist/src/cli/helpers/trained-model.js +144 -0
- package/dist/src/cli/list-output.d.ts +7 -0
- package/dist/src/cli/list-output.js +77 -0
- package/dist/src/cli/list-shapes.d.ts +21 -0
- package/dist/src/cli/list-shapes.js +111 -0
- package/dist/src/cli/output-schemas.d.ts +0 -14
- package/dist/src/cli/output-schemas.js +4 -7
- package/dist/src/cli/output.d.ts +0 -1
- package/dist/src/cli/output.js +33 -19
- package/dist/src/cli/plans.d.ts +14 -0
- package/dist/src/cli/plans.js +3740 -0
- package/dist/src/cli/types.d.ts +2 -0
- package/dist/src/cli/usage.d.ts +7 -0
- package/dist/src/cli/usage.js +20 -1
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +2 -2
- package/dist/src/cli.js +137 -115
- package/dist/src/client.d.ts +52 -43
- package/dist/src/client.js +267 -96
- package/dist/src/errors.d.ts +22 -2
- package/dist/src/errors.js +49 -50
- package/dist/src/generated/action-output-schemas.json +345 -89
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +3 -0
- package/dist/src/resources/analyses.d.ts +8 -0
- package/dist/src/resources/analyses.js +13 -1
- package/dist/src/resources/bundles.d.ts +34 -0
- package/dist/src/resources/bundles.js +67 -0
- package/dist/src/resources/connections.d.ts +9 -0
- package/dist/src/resources/connections.js +15 -0
- package/dist/src/resources/data-quality.d.ts +20 -0
- package/dist/src/resources/data-quality.js +30 -0
- package/dist/src/resources/dataset-create.d.ts +38 -0
- package/dist/src/resources/dataset-create.js +135 -0
- package/dist/src/resources/datasets.d.ts +46 -15
- package/dist/src/resources/datasets.js +120 -112
- package/dist/src/resources/discussions.d.ts +5 -0
- package/dist/src/resources/discussions.js +7 -0
- package/dist/src/resources/documentation.d.ts +25 -0
- package/dist/src/resources/documentation.js +16 -0
- package/dist/src/resources/flow-zones.d.ts +29 -3
- package/dist/src/resources/flow-zones.js +53 -3
- package/dist/src/resources/flow.d.ts +48 -0
- package/dist/src/resources/flow.js +65 -0
- package/dist/src/resources/futures.d.ts +23 -0
- package/dist/src/resources/futures.js +32 -0
- package/dist/src/resources/jobs.d.ts +34 -0
- package/dist/src/resources/jobs.js +170 -3
- package/dist/src/resources/metrics.d.ts +10 -0
- package/dist/src/resources/metrics.js +10 -0
- package/dist/src/resources/ml-tasks.d.ts +65 -2
- package/dist/src/resources/ml-tasks.js +102 -28
- package/dist/src/resources/mlflow-extension.d.ts +73 -0
- package/dist/src/resources/mlflow-extension.js +105 -0
- package/dist/src/resources/projects.d.ts +15 -0
- package/dist/src/resources/projects.js +18 -3
- package/dist/src/resources/recipe-create.d.ts +79 -0
- package/dist/src/resources/recipe-create.js +454 -0
- package/dist/src/resources/recipes.d.ts +28 -29
- package/dist/src/resources/recipes.js +201 -267
- package/dist/src/resources/saved-models.d.ts +10 -6
- package/dist/src/resources/saved-models.js +16 -14
- package/dist/src/resources/scenarios.js +8 -14
- package/dist/src/resources/sql.js +52 -13
- package/dist/src/resources/streaming-endpoints.d.ts +12 -0
- package/dist/src/resources/streaming-endpoints.js +15 -0
- package/dist/src/resources/trained-model.d.ts +60 -0
- package/dist/src/resources/trained-model.js +98 -0
- package/dist/src/resources/webapps.d.ts +8 -0
- package/dist/src/resources/webapps.js +10 -0
- package/dist/src/resources/wiki.d.ts +52 -1
- package/dist/src/resources/wiki.js +96 -0
- package/dist/src/resources/workspaces.d.ts +2 -0
- package/dist/src/resources/workspaces.js +4 -0
- package/dist/src/utils/flow-analysis.js +4 -0
- package/dist/src/utils/response-file.js +20 -9
- package/dist/src/utils/secret-sanitize.d.ts +7 -0
- package/dist/src/utils/secret-sanitize.js +46 -0
- package/package.json +25 -26
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +1 -1
- package/skills/dataiku-dss/references/authentication.md +3 -1
- package/skills/dataiku-dss/references/coding.md +1 -0
- package/skills/dataiku-dss/references/discovery.md +4 -6
- package/skills/dataiku-dss/references/flow-maps.md +2 -2
- package/skills/dataiku-dss/references/troubleshooting.md +3 -2
|
@@ -1,10 +1,104 @@
|
|
|
1
1
|
import { ClientValidationError, } from "../../errors.js";
|
|
2
2
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
3
|
+
import { asRecord, } from "../../utils/records.js";
|
|
3
4
|
import { executionMode, } from "../flags.js";
|
|
4
|
-
import { ensureFlowZonePlanTarget, findFlowZoneForPlan, flowZoneColor, flowZoneContains, flowZoneCurrentPosition, flowZoneDetailSummary, flowZoneId, flowZoneMoveDelta, flowZoneMoveItems, flowZoneName, flowZoneOrganizeStep, flowZonePlanFromZones, flowZonePlanItemKeys, flowZonePruneItems, flowZoneSamePosition, flowZoneSummary, readFlowZoneOrganizePlan, resolveFlowZoneIdFromFlags, throwFlowZoneValidationError, validateFlowZoneOrganizeObjects, } from "../helpers/flow-zone.js";
|
|
5
|
-
import { readIfExists, skipResult, } from "../output.js";
|
|
5
|
+
import { applyFlowZoneUnits, ensureFlowZonePlanTarget, findFlowZoneForPlan, flowZoneColor, flowZoneContains, flowZoneCurrentPosition, flowZoneDetailSummary, flowZoneId, flowZoneMoveDelta, flowZoneMoveItems, flowZoneName, flowZoneOrganizeStep, flowZonePlanFromZones, flowZonePlanItemKeys, flowZonePruneItems, flowZoneRepositionTarget, flowZoneSamePosition, flowZoneSummary, flowZoneUnassignedItems, readFlowZoneOrganizePlan, resolveFlowZoneIdFromFlags, throwFlowZoneValidationError, validateFlowZoneOrganizeObjects, } from "../helpers/flow-zone.js";
|
|
6
|
+
import { enqueueCliWarning, readIfExists, skipResult, } from "../output.js";
|
|
6
7
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
7
8
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
9
|
+
const ZONE_OBJECT_IDS = { type: "array", items: { type: "string", minLength: 1, }, };
|
|
10
|
+
/** JSON Schema of the `flow-zone organize` plan, as parsed by `parseFlowZoneOrganizePlan`. */
|
|
11
|
+
const FLOW_ZONE_ORGANIZE_PLAN_SCHEMA = {
|
|
12
|
+
type: "object",
|
|
13
|
+
required: ["zones",],
|
|
14
|
+
properties: {
|
|
15
|
+
topologyFingerprint: {
|
|
16
|
+
type: "string",
|
|
17
|
+
pattern: "^[0-9a-f]{64}$",
|
|
18
|
+
description: "From dss flow-zone plan; organize refuses to apply if the flow topology changed since.",
|
|
19
|
+
},
|
|
20
|
+
zones: {
|
|
21
|
+
type: "array",
|
|
22
|
+
items: {
|
|
23
|
+
type: "object",
|
|
24
|
+
anyOf: [{ required: ["id",], }, { required: ["name",], },],
|
|
25
|
+
properties: {
|
|
26
|
+
id: { type: "string", description: "Existing zone id (alias zoneId); matched before name.", },
|
|
27
|
+
name: { type: "string", description: "Zone name; required to create a zone.", },
|
|
28
|
+
color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$", },
|
|
29
|
+
position: {
|
|
30
|
+
type: "object",
|
|
31
|
+
required: ["x", "y",],
|
|
32
|
+
properties: { x: { type: "number", }, y: { type: "number", }, },
|
|
33
|
+
description: "Applied when the zone is created; moving an existing zone requires --recreate-on-move.",
|
|
34
|
+
},
|
|
35
|
+
items: {
|
|
36
|
+
type: "array",
|
|
37
|
+
description: "Alias objects. Each entry is TYPE:ID or {objectType|type, objectId|id|name, projectKey?}.",
|
|
38
|
+
items: {
|
|
39
|
+
anyOf: [
|
|
40
|
+
{ type: "string", pattern: "^[A-Z_]+:.+$", },
|
|
41
|
+
{
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
object: { type: "string", },
|
|
45
|
+
objectType: { type: "string", },
|
|
46
|
+
type: { type: "string", },
|
|
47
|
+
objectId: { type: "string", },
|
|
48
|
+
id: { type: "string", },
|
|
49
|
+
name: { type: "string", },
|
|
50
|
+
projectKey: { type: "string", },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
datasets: ZONE_OBJECT_IDS,
|
|
57
|
+
recipes: ZONE_OBJECT_IDS,
|
|
58
|
+
folders: ZONE_OBJECT_IDS,
|
|
59
|
+
savedModels: ZONE_OBJECT_IDS,
|
|
60
|
+
modelEvaluationStores: ZONE_OBJECT_IDS,
|
|
61
|
+
streamingEndpoints: ZONE_OBJECT_IDS,
|
|
62
|
+
labelingTasks: ZONE_OBJECT_IDS,
|
|
63
|
+
knowledgeBanks: ZONE_OBJECT_IDS,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* DSS stores a zone `position` sent on creation only while the project's
|
|
71
|
+
* manual zone positioning is off (with it on, DSS auto-places the new zone),
|
|
72
|
+
* and draws stored positions only while it is on.
|
|
73
|
+
*/
|
|
74
|
+
async function readManualPositioning(c, projectKey) {
|
|
75
|
+
try {
|
|
76
|
+
const settings = asRecord(await c.projects.getSettings(projectKey));
|
|
77
|
+
const display = asRecord(asRecord(settings?.settings)?.flowDisplaySettings);
|
|
78
|
+
return { manual: display?.zonesManualPositioning === true, };
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return { error: error instanceof Error ? error.message.split("\n")[0] : String(error), };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Read-modify-write of `flowDisplaySettings.zonesManualPositioning`, keeping every other setting. */
|
|
85
|
+
async function setManualPositioning(c, projectKey, manual) {
|
|
86
|
+
const current = await c.projects.getSettings(projectKey);
|
|
87
|
+
const settings = asRecord(current.settings) ?? {};
|
|
88
|
+
await c.projects.setSettings(projectKey, {
|
|
89
|
+
...current,
|
|
90
|
+
settings: {
|
|
91
|
+
...settings,
|
|
92
|
+
flowDisplaySettings: {
|
|
93
|
+
...asRecord(settings.flowDisplaySettings),
|
|
94
|
+
zonesManualPositioning: manual,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function enableManualPositioningCommand(projectKey) {
|
|
100
|
+
return `dss project settings-set --data '{"settings":{"flowDisplaySettings":{"zonesManualPositioning":true}}}'${projectKey ? ` --project-key ${projectKey}` : ""}`;
|
|
101
|
+
}
|
|
8
102
|
export const flowZoneCommands = withUsage("flow-zone", {
|
|
9
103
|
list: {
|
|
10
104
|
handler: async (c, _a, f) => {
|
|
@@ -165,36 +259,77 @@ export const flowZoneCommands = withUsage("flow-zone", {
|
|
|
165
259
|
plan: {
|
|
166
260
|
handler: async (c, _a, f) => {
|
|
167
261
|
const pk = f["project-key"];
|
|
168
|
-
const [zones, topologyFingerprint,] = await Promise.all([
|
|
262
|
+
const [zones, { graph, topologyFingerprint, },] = await Promise.all([
|
|
169
263
|
c.flowZones.list(pk),
|
|
170
|
-
c.projects.
|
|
264
|
+
c.projects.flowTopology(pk),
|
|
171
265
|
]);
|
|
172
|
-
return
|
|
266
|
+
return {
|
|
267
|
+
...flowZonePlanFromZones(zones, topologyFingerprint),
|
|
268
|
+
unassigned: flowZoneUnassignedItems(graph, zones),
|
|
269
|
+
};
|
|
173
270
|
},
|
|
174
|
-
description: "Export current visual flow-zone organization as an organize-compatible plan.",
|
|
271
|
+
description: "Export current visual flow-zone organization as an organize-compatible plan. `unassigned` lists default-zone recipes and source objects still to place (recipe outputs follow their recipe); organize ignores it.",
|
|
175
272
|
examples: ["dss flow-zone plan --project-key MYPROJ",],
|
|
176
273
|
},
|
|
177
274
|
organize: {
|
|
178
275
|
handler: async (c, _a, f) => {
|
|
179
276
|
const usage = commandUsage("flow-zone", "organize");
|
|
180
277
|
const pk = f["project-key"];
|
|
181
|
-
const
|
|
278
|
+
const parsed = readFlowZoneOrganizePlan(f, usage);
|
|
182
279
|
const sync = f["sync"] === true;
|
|
183
280
|
const validateObjects = f["validate-objects"] === true;
|
|
184
|
-
const [
|
|
281
|
+
const recreateOnMove = f["recreate-on-move"] === true;
|
|
282
|
+
const [zones, { graph, topologyFingerprint, },] = await Promise.all([
|
|
185
283
|
c.flowZones.list(pk),
|
|
186
|
-
c.projects.
|
|
284
|
+
c.projects.flowTopology(pk),
|
|
187
285
|
]);
|
|
188
|
-
if (
|
|
189
|
-
throw new ClientValidationError("Flow topology changed after this visual organization plan was generated.", "assertion_failed", "Regenerate the plan with dss flow-zone plan, then review it before applying.", { expected:
|
|
286
|
+
if (parsed.topologyFingerprint && parsed.topologyFingerprint !== topologyFingerprint) {
|
|
287
|
+
throw new ClientValidationError("Flow topology changed after this visual organization plan was generated.", "assertion_failed", "Regenerate the plan with dss flow-zone plan, then review it before applying.", { expected: parsed.topologyFingerprint, actual: topologyFingerprint, });
|
|
190
288
|
}
|
|
289
|
+
const plan = applyFlowZoneUnits(parsed, graph, zones, pk);
|
|
191
290
|
const plannedItemKeys = flowZonePlanItemKeys(plan);
|
|
192
|
-
const
|
|
291
|
+
const targets = plan.zones.map((zonePlan) => ({
|
|
292
|
+
zonePlan,
|
|
293
|
+
existing: findFlowZoneForPlan(zones, zonePlan),
|
|
294
|
+
}));
|
|
295
|
+
const planned = targets.map(({ zonePlan, existing, }) => flowZoneOrganizeStep(zonePlan, existing, sync, plannedItemKeys));
|
|
296
|
+
const repositions = targets.flatMap(({ zonePlan, existing, }) => {
|
|
297
|
+
const requestedPosition = existing && flowZoneRepositionTarget(zonePlan, existing);
|
|
298
|
+
return requestedPosition
|
|
299
|
+
? [{ ...flowZoneSummary(existing), position: existing.position, requestedPosition, },]
|
|
300
|
+
: [];
|
|
301
|
+
});
|
|
302
|
+
if (repositions.length > 0 && !recreateOnMove) {
|
|
303
|
+
throw new ClientValidationError(`DSS cannot move ${repositions.length} existing flow zone(s): its API applies a zone position only when the zone is created.`, "validation_failed", "Drop position from those zones, or pass --recreate-on-move to replace each with a new zone at the requested position (items, shared items, and metadata carry over; the zone id changes, so update scenarios that build the zone by id).", { zones: repositions, });
|
|
304
|
+
}
|
|
193
305
|
const validation = validateObjects
|
|
194
306
|
? await validateFlowZoneOrganizeObjects(c, plan, pk)
|
|
195
307
|
: undefined;
|
|
196
308
|
if (validation)
|
|
197
309
|
throwFlowZoneValidationError(validation);
|
|
310
|
+
const writesPositions = repositions.length > 0
|
|
311
|
+
|| targets.some(({ zonePlan, existing, }) => !existing && zonePlan.position !== undefined);
|
|
312
|
+
const positioning = writesPositions ? await readManualPositioning(c, pk) : undefined;
|
|
313
|
+
if (positioning?.error !== undefined) {
|
|
314
|
+
enqueueCliWarning({
|
|
315
|
+
code: "zone_manual_positioning_unverified",
|
|
316
|
+
message: `Could not read project settings (${positioning.error}). DSS stores a zone position only when manual zone positioning is off at creation, and draws it only when it is on.`,
|
|
317
|
+
hint: enableManualPositioningCommand(pk),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else if (positioning?.manual === false) {
|
|
321
|
+
enqueueCliWarning({
|
|
322
|
+
code: "zone_manual_positioning_disabled",
|
|
323
|
+
message: "This plan sets zone positions, but the project's manual zone positioning is off, so DSS lays zones out automatically and the positions do not show.",
|
|
324
|
+
hint: enableManualPositioningCommand(pk),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// With manual positioning on, DSS auto-places new zones and drops the
|
|
328
|
+
// requested position, so it is switched off while zones are created.
|
|
329
|
+
const suspendManualPositioning = positioning?.manual === true;
|
|
330
|
+
const positionReport = positioning?.error === undefined && positioning
|
|
331
|
+
? { manualPositioning: positioning.manual, suspendManualPositioning, }
|
|
332
|
+
: {};
|
|
198
333
|
const itemCount = plan.zones.reduce((count, zonePlan) => count + zonePlan.items.length, 0);
|
|
199
334
|
const pruneItemCount = planned.reduce((count, step) => {
|
|
200
335
|
const pruneItems = Array.isArray(step.pruneItems) ? step.pruneItems : [];
|
|
@@ -208,10 +343,12 @@ export const flowZoneCommands = withUsage("flow-zone", {
|
|
|
208
343
|
projectKey: pk,
|
|
209
344
|
sync,
|
|
210
345
|
validateObjects,
|
|
346
|
+
recreateOnMove,
|
|
211
347
|
topologyFingerprint,
|
|
212
348
|
zoneCount: plan.zones.length,
|
|
213
349
|
itemCount,
|
|
214
350
|
pruneItemCount,
|
|
351
|
+
...positionReport,
|
|
215
352
|
...(validation ? { validation, } : {}),
|
|
216
353
|
planned,
|
|
217
354
|
};
|
|
@@ -219,54 +356,101 @@ export const flowZoneCommands = withUsage("flow-zone", {
|
|
|
219
356
|
const currentZones = [...zones,];
|
|
220
357
|
const created = [];
|
|
221
358
|
const updated = [];
|
|
359
|
+
const recreated = [];
|
|
222
360
|
const moved = [];
|
|
223
361
|
const pruned = [];
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
color: zonePlan.color,
|
|
233
|
-
position: zonePlan.position,
|
|
234
|
-
projectKey: pk,
|
|
362
|
+
const positionsNotApplied = [];
|
|
363
|
+
const checkPosition = (zone, requested) => {
|
|
364
|
+
if (requested && !flowZoneSamePosition(flowZoneCurrentPosition(zone), requested)) {
|
|
365
|
+
positionsNotApplied.push({
|
|
366
|
+
zoneId: zone.id,
|
|
367
|
+
name: zone.name,
|
|
368
|
+
requested,
|
|
369
|
+
stored: zone.position,
|
|
235
370
|
});
|
|
236
|
-
currentZones.push(zone);
|
|
237
|
-
created.push(zone);
|
|
238
371
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
zone = await c.flowZones.
|
|
251
|
-
|
|
372
|
+
};
|
|
373
|
+
let failure;
|
|
374
|
+
if (suspendManualPositioning)
|
|
375
|
+
await setManualPositioning(c, pk, false);
|
|
376
|
+
try {
|
|
377
|
+
for (const zonePlan of plan.zones) {
|
|
378
|
+
let zone = findFlowZoneForPlan(currentZones, zonePlan);
|
|
379
|
+
ensureFlowZonePlanTarget(zonePlan, zone);
|
|
380
|
+
const moveItems = flowZoneMoveDelta(zone, zonePlan.items);
|
|
381
|
+
const pruneItems = sync ? flowZonePruneItems(zone, plannedItemKeys) : [];
|
|
382
|
+
if (!zone) {
|
|
383
|
+
zone = await c.flowZones.create({
|
|
384
|
+
name: zonePlan.name,
|
|
385
|
+
color: zonePlan.color,
|
|
386
|
+
position: zonePlan.position,
|
|
387
|
+
projectKey: pk,
|
|
388
|
+
});
|
|
389
|
+
currentZones.push(zone);
|
|
390
|
+
created.push(zone);
|
|
391
|
+
checkPosition(zone, zonePlan.position);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const name = zonePlan.name && zonePlan.name !== zone.name ? zonePlan.name : undefined;
|
|
395
|
+
const color = zonePlan.color && zonePlan.color !== zone.color ? zonePlan.color : undefined;
|
|
396
|
+
const replacedId = zone.id;
|
|
397
|
+
const position = flowZoneRepositionTarget(zonePlan, zone);
|
|
398
|
+
if (position) {
|
|
399
|
+
const result = await c.flowZones.recreate(replacedId, {
|
|
400
|
+
position,
|
|
401
|
+
name,
|
|
402
|
+
color,
|
|
403
|
+
projectKey: pk,
|
|
404
|
+
});
|
|
405
|
+
zone = result.zone;
|
|
406
|
+
recreated.push(result);
|
|
407
|
+
checkPosition(zone, position);
|
|
408
|
+
}
|
|
409
|
+
else if (name !== undefined || color !== undefined) {
|
|
410
|
+
zone = await c.flowZones.update(replacedId, { name, color, projectKey: pk, });
|
|
411
|
+
updated.push(zone);
|
|
412
|
+
}
|
|
413
|
+
const index = currentZones.findIndex((candidate) => candidate.id === replacedId);
|
|
252
414
|
if (index !== -1)
|
|
253
415
|
currentZones[index] = zone;
|
|
254
|
-
|
|
416
|
+
}
|
|
417
|
+
if (moveItems.length > 0) {
|
|
418
|
+
await c.flowZones.moveItems(zone.id, moveItems, pk);
|
|
419
|
+
moved.push({ zoneId: zone.id, name: zone.name, items: moveItems, });
|
|
420
|
+
}
|
|
421
|
+
if (pruneItems.length > 0) {
|
|
422
|
+
await c.flowZones.moveItems("default", pruneItems, pk);
|
|
423
|
+
pruned.push({ zoneId: "default", fromZoneId: zone.id, name: zone.name, items: pruneItems, });
|
|
255
424
|
}
|
|
256
425
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
failure = error;
|
|
429
|
+
}
|
|
430
|
+
if (suspendManualPositioning) {
|
|
431
|
+
try {
|
|
432
|
+
await setManualPositioning(c, pk, true);
|
|
260
433
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
pruned.push({ zoneId: "default", fromZoneId: zone.id, name: zone.name, items: pruneItems, });
|
|
434
|
+
catch (restoreError) {
|
|
435
|
+
throw new ClientValidationError("Manual zone positioning was switched off to place zones and could not be switched back on.", "ambiguous_outcome", enableManualPositioningCommand(pk), failure instanceof Error ? { organizeError: failure.message, } : undefined, { cause: restoreError, });
|
|
264
436
|
}
|
|
265
437
|
}
|
|
438
|
+
if (failure !== undefined)
|
|
439
|
+
throw failure;
|
|
266
440
|
const topologyFingerprintAfter = await c.projects.topologyFingerprint(pk);
|
|
267
441
|
if (topologyFingerprintAfter !== topologyFingerprint) {
|
|
268
442
|
throw new ClientValidationError("Visual flow-zone changes modified flow topology.", "assertion_failed", "Inspect the flow before applying any further organization changes.", { before: topologyFingerprint, after: topologyFingerprintAfter, });
|
|
269
443
|
}
|
|
444
|
+
if (positionsNotApplied.length > 0) {
|
|
445
|
+
throw new ClientValidationError(`DSS did not store the requested position for ${positionsNotApplied.length} flow zone(s).`, "assertion_failed", "Items were organized; inspect the zones with dss flow-zone list.", {
|
|
446
|
+
positionsNotApplied,
|
|
447
|
+
created: created.map((zone) => zone.id),
|
|
448
|
+
recreated: recreated.map(({ replacedZoneId, zone, }) => ({
|
|
449
|
+
replacedZoneId,
|
|
450
|
+
zoneId: zone.id,
|
|
451
|
+
})),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
270
454
|
return {
|
|
271
455
|
organized: true,
|
|
272
456
|
action: "organize",
|
|
@@ -274,22 +458,33 @@ export const flowZoneCommands = withUsage("flow-zone", {
|
|
|
274
458
|
projectKey: pk,
|
|
275
459
|
sync,
|
|
276
460
|
validateObjects,
|
|
461
|
+
recreateOnMove,
|
|
277
462
|
topologyFingerprint,
|
|
278
463
|
topologyUnchanged: true,
|
|
279
464
|
zoneCount: plan.zones.length,
|
|
280
465
|
itemCount,
|
|
281
466
|
pruneItemCount,
|
|
467
|
+
...positionReport,
|
|
282
468
|
created,
|
|
283
469
|
updated,
|
|
470
|
+
recreated,
|
|
284
471
|
moved,
|
|
285
472
|
pruned,
|
|
286
473
|
};
|
|
287
474
|
},
|
|
288
|
-
description: "Create/update flow zones and move objects from a declarative
|
|
475
|
+
description: "Create/update flow zones and move objects from a declarative plan. A recipe's outputs share its zone: listing either places both (impliedItems); splitting them is rejected. DSS stores a zone position only at creation: moving an existing zone needs --recreate-on-move (new id); manual zone positioning is switched off while zones are placed, then restored.",
|
|
289
476
|
examples: [
|
|
290
477
|
"dss flow-zone organize --file flow-zones.json --dry-run",
|
|
291
478
|
`dss flow-zone organize --data '{"zones":[{"name":"Raw","color":"#64748b","datasets":["raw_orders"]}]}'`,
|
|
479
|
+
"dss flow-zone organize --file flow-zones.json --recreate-on-move --dry-run",
|
|
292
480
|
],
|
|
481
|
+
payloadSchema: {
|
|
482
|
+
stdin: true,
|
|
483
|
+
dataFlag: true,
|
|
484
|
+
dataFileFlag: true,
|
|
485
|
+
jsonShape: "object",
|
|
486
|
+
jsonSchema: FLOW_ZONE_ORGANIZE_PLAN_SCHEMA,
|
|
487
|
+
},
|
|
293
488
|
},
|
|
294
489
|
graph: {
|
|
295
490
|
handler: (c, a, f) => {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SchemaPropagationOptions } from "../../resources/flow.js";
|
|
2
|
+
import type { CommandMeta } from "../types.js";
|
|
3
|
+
/** Propagation options from flags; shared with the `--plan` builder. */
|
|
4
|
+
export declare function schemaPropagationOptionsFromFlags(flags: Record<string, string | boolean>): SchemaPropagationOptions;
|
|
5
|
+
export declare const flowCommands: Record<string, CommandMeta>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { writeResponseToFile, } from "../../utils/response-file.js";
|
|
2
|
+
import { splitCsvFlag, } from "../coerce.js";
|
|
3
|
+
import { documentationTemplateFromFlags, runDocumentationCommand, taskWaitOptions, } from "../helpers/documentation.js";
|
|
4
|
+
import { commandUsage, withUsage, } from "../syntax.js";
|
|
5
|
+
import { requireArgs, } from "../usage.js";
|
|
6
|
+
/** Propagation options from flags; shared with the `--plan` builder. */
|
|
7
|
+
export function schemaPropagationOptionsFromFlags(flags) {
|
|
8
|
+
const excludedRecipes = splitCsvFlag(flags["stop-at"]);
|
|
9
|
+
const markAsOkRecipes = splitCsvFlag(flags["mark-ok"]);
|
|
10
|
+
return {
|
|
11
|
+
...(flags["no-auto-rebuild"] === true ? { autoRebuild: false, } : {}),
|
|
12
|
+
...(excludedRecipes.length > 0 ? { excludedRecipes, } : {}),
|
|
13
|
+
...(markAsOkRecipes.length > 0 ? { markAsOkRecipes, } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export const flowCommands = withUsage("flow", {
|
|
17
|
+
"propagate-schema": {
|
|
18
|
+
handler: async (c, a, f) => {
|
|
19
|
+
requireArgs(a, 1, commandUsage("flow", "propagate-schema"));
|
|
20
|
+
const opts = {
|
|
21
|
+
...schemaPropagationOptionsFromFlags(f),
|
|
22
|
+
projectKey: f["project-key"],
|
|
23
|
+
};
|
|
24
|
+
if (f["wait"] === true) {
|
|
25
|
+
return c.flow.propagateSchemaAndWait(a[0], { ...opts, ...taskWaitOptions(f), });
|
|
26
|
+
}
|
|
27
|
+
return c.flow.propagateSchema(a[0], opts);
|
|
28
|
+
},
|
|
29
|
+
description: "Propagate a dataset's schema change through all downstream recipes and datasets (UI schema propagation). Rebuilds as needed unless --no-auto-rebuild; --stop-at / --mark-ok RECIPES limit it. Returns the DSS task; --wait waits. One recipe only: dss recipe update-schema.",
|
|
30
|
+
examples: [
|
|
31
|
+
"dss flow propagate-schema orders_raw --wait",
|
|
32
|
+
"dss flow propagate-schema orders_raw --stop-at compute_report --no-auto-rebuild --plan",
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
"generate-documentation": {
|
|
36
|
+
handler: async (c, _a, f) => {
|
|
37
|
+
const pk = f["project-key"];
|
|
38
|
+
const task = await c.flow.generateDocumentation(documentationTemplateFromFlags(f), pk);
|
|
39
|
+
return runDocumentationCommand(c, f, task, (exportId) => c.flow.downloadDocumentation(exportId, pk));
|
|
40
|
+
},
|
|
41
|
+
description: "Generate the flow documentation (Word docx) from the DSS default template, an uploaded --template-file, or a template in a managed folder (--folder FOLDER_ID --path PATH). Returns the DSS future; --wait waits; --output PATH waits and downloads the document.",
|
|
42
|
+
examples: [
|
|
43
|
+
"dss flow generate-documentation --output flow.docx",
|
|
44
|
+
"dss flow generate-documentation --template-file template.docx --wait",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
"download-documentation": {
|
|
48
|
+
handler: async (c, a, f) => {
|
|
49
|
+
requireArgs(a, 1, commandUsage("flow", "download-documentation"));
|
|
50
|
+
const output = f["output"];
|
|
51
|
+
const res = await c.flow.downloadDocumentation(a[0], f["project-key"]);
|
|
52
|
+
return { path: output, bytes: await writeResponseToFile(output, res), };
|
|
53
|
+
},
|
|
54
|
+
description: "Download a generated flow documentation by the exportId from a finished generate-documentation future.",
|
|
55
|
+
examples: ["dss flow download-documentation EXPORT_ID --output flow.docx",],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -4,6 +4,14 @@ import { planResult, } from "../output.js";
|
|
|
4
4
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
5
5
|
import { requireArgs, } from "../usage.js";
|
|
6
6
|
export const futureCommands = withUsage("future", {
|
|
7
|
+
list: {
|
|
8
|
+
handler: (c, _a, f) => c.futures.list({
|
|
9
|
+
allUsers: f["all-users"] === true,
|
|
10
|
+
withScenarios: f["with-scenarios"] === true,
|
|
11
|
+
}),
|
|
12
|
+
description: "List long tasks in progress on the instance (DSS futures): your own, or every user's with --all-users; --with-scenarios adds running scenarios.",
|
|
13
|
+
examples: ["dss future list", "dss future list --all-users --with-scenarios",],
|
|
14
|
+
},
|
|
7
15
|
get: {
|
|
8
16
|
handler: (c, a) => {
|
|
9
17
|
requireArgs(a, 1, commandUsage("future", "get"));
|
|
@@ -91,6 +91,12 @@ export const commands = {
|
|
|
91
91
|
get "future"() {
|
|
92
92
|
return require("./future.js").futureCommands;
|
|
93
93
|
},
|
|
94
|
+
get "flow"() {
|
|
95
|
+
return require("./flow.js").flowCommands;
|
|
96
|
+
},
|
|
97
|
+
get "mlflow"() {
|
|
98
|
+
return require("./mlflow.js").mlflowCommands;
|
|
99
|
+
},
|
|
94
100
|
get "flow-zone"() {
|
|
95
101
|
return require("./flow-zone.js").flowZoneCommands;
|
|
96
102
|
},
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { mkdir, writeFile, } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve, } from "node:path";
|
|
3
|
+
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
3
4
|
import { asRecord, } from "../../utils/records.js";
|
|
4
5
|
import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, } from "../coerce.js";
|
|
5
6
|
import { executionMode, } from "../flags.js";
|
|
6
|
-
import { encodedProjectEndpoint, } from "../output.js";
|
|
7
|
+
import { encodedProjectEndpoint, enqueueCliWarning, } from "../output.js";
|
|
7
8
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
8
9
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
10
|
+
/**
|
|
11
|
+
* With an empty output schema DSS ends a dataset build DONE after writing no
|
|
12
|
+
* columns and no rows, without an error. Flag that after a successful build so
|
|
13
|
+
* it is not mistaken for real output.
|
|
14
|
+
*/
|
|
15
|
+
async function warnIfBuiltDatasetHasNoColumns(c, target, targetType, success, projectKey) {
|
|
16
|
+
if (!success || targetType !== "DATASET")
|
|
17
|
+
return;
|
|
18
|
+
let schema;
|
|
19
|
+
try {
|
|
20
|
+
schema = await c.datasets.schema(target, projectKey);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!(error instanceof DataikuError || error instanceof ClientValidationError))
|
|
24
|
+
throw error;
|
|
25
|
+
enqueueCliWarning({
|
|
26
|
+
code: "built_dataset_schema_unchecked",
|
|
27
|
+
dataset: target,
|
|
28
|
+
error: error.message.split("\n")[0],
|
|
29
|
+
hint: "The build succeeded but the dataset schema could not be read to check it is not empty.",
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (schema.columns.length > 0)
|
|
34
|
+
return;
|
|
35
|
+
enqueueCliWarning({
|
|
36
|
+
code: "built_dataset_has_no_columns",
|
|
37
|
+
dataset: target,
|
|
38
|
+
hint: "The build succeeded but the dataset schema is empty, so no data was written. For a visual or SQL query recipe, run dss recipe update-schema RECIPE (or rebuild with --auto-update-schema).",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
9
41
|
// ---------------------------------------------------------------------------
|
|
10
42
|
// Utility helpers
|
|
11
43
|
// ---------------------------------------------------------------------------
|
|
@@ -236,7 +268,7 @@ export const jobCommands = withUsage("job", {
|
|
|
236
268
|
await writeFile(outputPath, log.endsWith("\n") ? log : `${log}\n`, "utf-8");
|
|
237
269
|
return outputPath;
|
|
238
270
|
},
|
|
239
|
-
description: "Get public API job log output. Use --errors-only (or --log-filter errors) to surface just error/traceback lines, and --output PATH to write the log to a file (stdout returns the path). --log-id is accepted for UI parity but DSS API-key auth cannot select browser-only cat-activity-log files.",
|
|
271
|
+
description: "Get public API job log output; find why a job failed. Use --errors-only (or --log-filter errors) to surface just error/traceback lines, and --output PATH to write the log to a file (stdout returns the path). --log-id is accepted for UI parity but DSS API-key auth cannot select browser-only cat-activity-log files.",
|
|
240
272
|
examples: [
|
|
241
273
|
"dss job log JOB_ID",
|
|
242
274
|
"dss job log JOB_ID --activity main --max-log-lines 200",
|
|
@@ -270,6 +302,7 @@ export const jobCommands = withUsage("job", {
|
|
|
270
302
|
requireArgs(a, 1, commandUsage("job", "build"));
|
|
271
303
|
const pk = f["project-key"];
|
|
272
304
|
const options = {
|
|
305
|
+
autoUpdateSchema: f["auto-update-schema"] === true,
|
|
273
306
|
buildMode: f["build-mode"],
|
|
274
307
|
partition: f["partition"],
|
|
275
308
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
@@ -288,11 +321,13 @@ export const jobCommands = withUsage("job", {
|
|
|
288
321
|
};
|
|
289
322
|
}
|
|
290
323
|
if (f["wait"] === true) {
|
|
291
|
-
|
|
324
|
+
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
325
|
+
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
326
|
+
return result;
|
|
292
327
|
}
|
|
293
328
|
return c.jobs.build(a[0], { ...options, projectKey: pk, });
|
|
294
329
|
},
|
|
295
|
-
description: "Start a dataset or managed-folder build, optionally waiting for completion.",
|
|
330
|
+
description: "Start a dataset or managed-folder build, optionally waiting for completion. --auto-update-schema lets DSS update each recipe's output schema before it runs.",
|
|
296
331
|
examples: [
|
|
297
332
|
"dss job build orders",
|
|
298
333
|
"dss job build orders --build-mode RECURSIVE_BUILD --wait",
|
|
@@ -304,6 +339,7 @@ export const jobCommands = withUsage("job", {
|
|
|
304
339
|
requireArgs(a, 1, commandUsage("job", "build-and-wait"));
|
|
305
340
|
const pk = f["project-key"];
|
|
306
341
|
const options = {
|
|
342
|
+
autoUpdateSchema: f["auto-update-schema"] === true,
|
|
307
343
|
buildMode: f["build-mode"],
|
|
308
344
|
includeLogs: f["include-logs"] === true,
|
|
309
345
|
logFilter: jobLogFilterFromFlag(f["log-filter"]),
|
|
@@ -325,9 +361,11 @@ export const jobCommands = withUsage("job", {
|
|
|
325
361
|
method: "POST",
|
|
326
362
|
};
|
|
327
363
|
}
|
|
328
|
-
|
|
364
|
+
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
365
|
+
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
366
|
+
return result;
|
|
329
367
|
},
|
|
330
|
-
description: "Build a dataset or managed folder and wait for completion.",
|
|
368
|
+
description: "Build a dataset or managed folder and wait for completion. --auto-update-schema lets DSS update each recipe's output schema before it runs; a successful dataset build that leaves an empty schema emits a built_dataset_has_no_columns warning. A managed dataset no recipe produces fails (exit 4).",
|
|
331
369
|
examples: [
|
|
332
370
|
"dss job build-and-wait orders",
|
|
333
371
|
"dss job build-and-wait orders --include-logs --log-filter stdout --summary",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { unknownJsonInput, } from "../coerce.js";
|
|
1
2
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
2
|
-
import { requireArgs, } from "../usage.js";
|
|
3
|
+
import { requireArgs, UsageError, } from "../usage.js";
|
|
3
4
|
export const metricsCommands = withUsage("metrics", {
|
|
4
5
|
"dataset-get": {
|
|
5
6
|
handler: (c, a, f) => {
|
|
@@ -17,6 +18,25 @@ export const metricsCommands = withUsage("metrics", {
|
|
|
17
18
|
description: "Compute the DSS-configured metrics for a dataset.",
|
|
18
19
|
examples: ["dss metrics dataset-compute customers",],
|
|
19
20
|
},
|
|
21
|
+
"dataset-run-checks": {
|
|
22
|
+
handler: (c, a, f) => {
|
|
23
|
+
const data = unknownJsonInput(f);
|
|
24
|
+
const checks = data && typeof data === "object" && "checks" in data ? data.checks : undefined;
|
|
25
|
+
if (data !== undefined && !Array.isArray(checks)) {
|
|
26
|
+
throw new UsageError('--data must be {"checks": [...]}.', "validation_failed");
|
|
27
|
+
}
|
|
28
|
+
return c.metrics.runDatasetChecks(a[0], {
|
|
29
|
+
projectKey: f["project-key"],
|
|
30
|
+
partitions: f["partitions"],
|
|
31
|
+
...(Array.isArray(checks) ? { checks, } : {}),
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
description: 'Run the checks configured on a dataset, or the {"checks": [...]} definitions passed as JSON, and return their outcomes.',
|
|
35
|
+
examples: [
|
|
36
|
+
"dss metrics dataset-run-checks customers",
|
|
37
|
+
"dss metrics dataset-run-checks customers --data-file checks.json",
|
|
38
|
+
],
|
|
39
|
+
},
|
|
20
40
|
"dataset-history": {
|
|
21
41
|
handler: (c, a, f) => {
|
|
22
42
|
requireArgs(a, 2, commandUsage("metrics", "dataset-history"));
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
import type { MlTaskCreateFields, MlTaskReguessOptions } from "../../resources/ml-tasks.js";
|
|
1
2
|
import type { CommandMeta } from "../types.js";
|
|
3
|
+
/** Task definition flags shared by `create`, `create-for-dataset`, and their --plan. */
|
|
4
|
+
export declare function mlTaskFieldsFromFlags(flags: Record<string, string | boolean>, usage: string): MlTaskCreateFields;
|
|
5
|
+
/** `reguess` flags → SDK options (shared with --plan). */
|
|
6
|
+
export declare function mlTaskReguessOptionsFromFlags(flags: Record<string, string | boolean>): MlTaskReguessOptions;
|
|
2
7
|
export declare const mlTaskCommands: Record<string, CommandMeta>;
|