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.
Files changed (139) hide show
  1. package/README.md +14 -6
  2. package/bin/bun-version.js +14 -0
  3. package/bin/dss.js +11 -15
  4. package/dist/build-metadata.json +1 -1
  5. package/dist/packages/types/src/index.d.ts +114 -5
  6. package/dist/packages/types/src/index.js +75 -2
  7. package/dist/src/cli/coerce.d.ts +2 -0
  8. package/dist/src/cli/coerce.js +20 -16
  9. package/dist/src/cli/command-syntax.json +3369 -144
  10. package/dist/src/cli/commands/analysis.js +19 -1
  11. package/dist/src/cli/commands/bundle.js +22 -0
  12. package/dist/src/cli/commands/connection.js +26 -2
  13. package/dist/src/cli/commands/data-quality.js +33 -1
  14. package/dist/src/cli/commands/dataset.js +76 -9
  15. package/dist/src/cli/commands/discussion.js +15 -0
  16. package/dist/src/cli/commands/doctor.js +1 -1
  17. package/dist/src/cli/commands/flow-zone.js +241 -46
  18. package/dist/src/cli/commands/flow.d.ts +5 -0
  19. package/dist/src/cli/commands/flow.js +57 -0
  20. package/dist/src/cli/commands/future.js +8 -0
  21. package/dist/src/cli/commands/index.js +6 -0
  22. package/dist/src/cli/commands/job.js +44 -6
  23. package/dist/src/cli/commands/metrics.js +21 -1
  24. package/dist/src/cli/commands/ml-task.d.ts +5 -0
  25. package/dist/src/cli/commands/ml-task.js +86 -16
  26. package/dist/src/cli/commands/mlflow.d.ts +11 -0
  27. package/dist/src/cli/commands/mlflow.js +121 -0
  28. package/dist/src/cli/commands/plugin.js +5 -11
  29. package/dist/src/cli/commands/project-deployer.js +70 -0
  30. package/dist/src/cli/commands/project-library.js +2 -5
  31. package/dist/src/cli/commands/project.js +8 -0
  32. package/dist/src/cli/commands/recipe.d.ts +13 -0
  33. package/dist/src/cli/commands/recipe.js +130 -7
  34. package/dist/src/cli/commands/saved-model.d.ts +1 -1
  35. package/dist/src/cli/commands/saved-model.js +9 -76
  36. package/dist/src/cli/commands/streaming-endpoint.js +30 -1
  37. package/dist/src/cli/commands/webapp.js +8 -0
  38. package/dist/src/cli/commands/wiki.js +78 -0
  39. package/dist/src/cli/commands/workspace.js +8 -0
  40. package/dist/src/cli/contract.d.ts +16 -15
  41. package/dist/src/cli/contract.js +150 -3407
  42. package/dist/src/cli/doctor.d.ts +13 -0
  43. package/dist/src/cli/doctor.js +43 -1
  44. package/dist/src/cli/flags.d.ts +14 -1
  45. package/dist/src/cli/flags.js +106 -8
  46. package/dist/src/cli/helpers/app-successor.js +1 -2
  47. package/dist/src/cli/helpers/dataset.d.ts +12 -0
  48. package/dist/src/cli/helpers/dataset.js +36 -0
  49. package/dist/src/cli/helpers/documentation.d.ts +21 -0
  50. package/dist/src/cli/helpers/documentation.js +53 -0
  51. package/dist/src/cli/helpers/flow-zone.d.ts +21 -0
  52. package/dist/src/cli/helpers/flow-zone.js +144 -4
  53. package/dist/src/cli/helpers/trained-model.d.ts +26 -0
  54. package/dist/src/cli/helpers/trained-model.js +144 -0
  55. package/dist/src/cli/list-output.d.ts +7 -0
  56. package/dist/src/cli/list-output.js +77 -0
  57. package/dist/src/cli/list-shapes.d.ts +21 -0
  58. package/dist/src/cli/list-shapes.js +111 -0
  59. package/dist/src/cli/output-schemas.d.ts +0 -14
  60. package/dist/src/cli/output-schemas.js +4 -7
  61. package/dist/src/cli/output.d.ts +0 -1
  62. package/dist/src/cli/output.js +33 -19
  63. package/dist/src/cli/plans.d.ts +14 -0
  64. package/dist/src/cli/plans.js +3740 -0
  65. package/dist/src/cli/types.d.ts +2 -0
  66. package/dist/src/cli/usage.d.ts +7 -0
  67. package/dist/src/cli/usage.js +20 -1
  68. package/dist/src/cli/version.d.ts +2 -2
  69. package/dist/src/cli/version.js +2 -2
  70. package/dist/src/cli.js +137 -115
  71. package/dist/src/client.d.ts +52 -43
  72. package/dist/src/client.js +267 -96
  73. package/dist/src/errors.d.ts +22 -2
  74. package/dist/src/errors.js +49 -50
  75. package/dist/src/generated/action-output-schemas.json +345 -89
  76. package/dist/src/index.d.ts +5 -1
  77. package/dist/src/index.js +3 -0
  78. package/dist/src/resources/analyses.d.ts +8 -0
  79. package/dist/src/resources/analyses.js +13 -1
  80. package/dist/src/resources/bundles.d.ts +34 -0
  81. package/dist/src/resources/bundles.js +67 -0
  82. package/dist/src/resources/connections.d.ts +9 -0
  83. package/dist/src/resources/connections.js +15 -0
  84. package/dist/src/resources/data-quality.d.ts +20 -0
  85. package/dist/src/resources/data-quality.js +30 -0
  86. package/dist/src/resources/dataset-create.d.ts +38 -0
  87. package/dist/src/resources/dataset-create.js +135 -0
  88. package/dist/src/resources/datasets.d.ts +46 -15
  89. package/dist/src/resources/datasets.js +120 -112
  90. package/dist/src/resources/discussions.d.ts +5 -0
  91. package/dist/src/resources/discussions.js +7 -0
  92. package/dist/src/resources/documentation.d.ts +25 -0
  93. package/dist/src/resources/documentation.js +16 -0
  94. package/dist/src/resources/flow-zones.d.ts +29 -3
  95. package/dist/src/resources/flow-zones.js +53 -3
  96. package/dist/src/resources/flow.d.ts +48 -0
  97. package/dist/src/resources/flow.js +65 -0
  98. package/dist/src/resources/futures.d.ts +23 -0
  99. package/dist/src/resources/futures.js +32 -0
  100. package/dist/src/resources/jobs.d.ts +34 -0
  101. package/dist/src/resources/jobs.js +170 -3
  102. package/dist/src/resources/metrics.d.ts +10 -0
  103. package/dist/src/resources/metrics.js +10 -0
  104. package/dist/src/resources/ml-tasks.d.ts +65 -2
  105. package/dist/src/resources/ml-tasks.js +102 -28
  106. package/dist/src/resources/mlflow-extension.d.ts +73 -0
  107. package/dist/src/resources/mlflow-extension.js +105 -0
  108. package/dist/src/resources/projects.d.ts +15 -0
  109. package/dist/src/resources/projects.js +18 -3
  110. package/dist/src/resources/recipe-create.d.ts +79 -0
  111. package/dist/src/resources/recipe-create.js +454 -0
  112. package/dist/src/resources/recipes.d.ts +28 -29
  113. package/dist/src/resources/recipes.js +201 -267
  114. package/dist/src/resources/saved-models.d.ts +10 -6
  115. package/dist/src/resources/saved-models.js +16 -14
  116. package/dist/src/resources/scenarios.js +8 -14
  117. package/dist/src/resources/sql.js +52 -13
  118. package/dist/src/resources/streaming-endpoints.d.ts +12 -0
  119. package/dist/src/resources/streaming-endpoints.js +15 -0
  120. package/dist/src/resources/trained-model.d.ts +60 -0
  121. package/dist/src/resources/trained-model.js +98 -0
  122. package/dist/src/resources/webapps.d.ts +8 -0
  123. package/dist/src/resources/webapps.js +10 -0
  124. package/dist/src/resources/wiki.d.ts +52 -1
  125. package/dist/src/resources/wiki.js +96 -0
  126. package/dist/src/resources/workspaces.d.ts +2 -0
  127. package/dist/src/resources/workspaces.js +4 -0
  128. package/dist/src/utils/flow-analysis.js +4 -0
  129. package/dist/src/utils/response-file.js +20 -9
  130. package/dist/src/utils/secret-sanitize.d.ts +7 -0
  131. package/dist/src/utils/secret-sanitize.js +46 -0
  132. package/package.json +25 -26
  133. package/plugin.json +1 -1
  134. package/skills/dataiku-dss/SKILL.md +1 -1
  135. package/skills/dataiku-dss/references/authentication.md +3 -1
  136. package/skills/dataiku-dss/references/coding.md +1 -0
  137. package/skills/dataiku-dss/references/discovery.md +4 -6
  138. package/skills/dataiku-dss/references/flow-maps.md +2 -2
  139. 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.topologyFingerprint(pk),
264
+ c.projects.flowTopology(pk),
171
265
  ]);
172
- return flowZonePlanFromZones(zones, topologyFingerprint);
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 plan = readFlowZoneOrganizePlan(f, usage);
278
+ const parsed = readFlowZoneOrganizePlan(f, usage);
182
279
  const sync = f["sync"] === true;
183
280
  const validateObjects = f["validate-objects"] === true;
184
- const [zones, topologyFingerprint,] = await Promise.all([
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.topologyFingerprint(pk),
284
+ c.projects.flowTopology(pk),
187
285
  ]);
188
- if (plan.topologyFingerprint && plan.topologyFingerprint !== topologyFingerprint) {
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: plan.topologyFingerprint, actual: topologyFingerprint, });
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 planned = plan.zones.map((zonePlan) => flowZoneOrganizeStep(zonePlan, findFlowZoneForPlan(zones, zonePlan), sync, plannedItemKeys));
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
- for (const zonePlan of plan.zones) {
225
- let zone = findFlowZoneForPlan(currentZones, zonePlan);
226
- ensureFlowZonePlanTarget(zonePlan, zone);
227
- const moveItems = flowZoneMoveDelta(zone, zonePlan.items);
228
- const pruneItems = sync ? flowZonePruneItems(zone, plannedItemKeys) : [];
229
- if (!zone) {
230
- zone = await c.flowZones.create({
231
- name: zonePlan.name,
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
- else {
240
- const patch = {
241
- ...(zonePlan.name && zonePlan.name !== zone.name ? { name: zonePlan.name, } : {}),
242
- ...(zonePlan.color && zonePlan.color !== zone.color ? { color: zonePlan.color, } : {}),
243
- ...(zonePlan.position !== undefined
244
- && !flowZoneSamePosition(flowZoneCurrentPosition(zone), zonePlan.position)
245
- ? { position: zonePlan.position, }
246
- : {}),
247
- projectKey: pk,
248
- };
249
- if (patch.name !== undefined || patch.color !== undefined || patch.position !== undefined) {
250
- zone = await c.flowZones.update(zone.id, patch);
251
- const index = currentZones.findIndex((candidate) => candidate.id === zone.id);
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
- updated.push(zone);
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
- if (moveItems.length > 0) {
258
- await c.flowZones.moveItems(zone.id, moveItems, pk);
259
- moved.push({ zoneId: zone.id, name: zone.name, items: moveItems, });
426
+ }
427
+ catch (error) {
428
+ failure = error;
429
+ }
430
+ if (suspendManualPositioning) {
431
+ try {
432
+ await setManualPositioning(c, pk, true);
260
433
  }
261
- if (pruneItems.length > 0) {
262
- await c.flowZones.moveItems("default", pruneItems, pk);
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 visual organization plan.",
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
- return c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
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
- return c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
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>;