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,6 +1,7 @@
|
|
|
1
1
|
import { basename, } from "node:path";
|
|
2
2
|
import { ClientValidationError, } from "../errors.js";
|
|
3
3
|
import { BaseResource, requireNonEmpty, requireNonEmptyArray, requireObject, } from "./base.js";
|
|
4
|
+
import { TrainedModel, } from "./trained-model.js";
|
|
4
5
|
function boolQuery(value) {
|
|
5
6
|
return value === undefined ? "true" : String(value);
|
|
6
7
|
}
|
|
@@ -187,30 +188,31 @@ export class SavedModelsResource extends BaseResource {
|
|
|
187
188
|
* the stream is read under the client body deadline.
|
|
188
189
|
*/
|
|
189
190
|
async downloadScoringJar(options, savedModelId, versionId, projectKey) {
|
|
190
|
-
|
|
191
|
-
const query = new URLSearchParams();
|
|
192
|
-
if (options?.fullClassName !== undefined) {
|
|
193
|
-
query.set("fullClassName", options.fullClassName);
|
|
194
|
-
}
|
|
195
|
-
if (options?.includeLibs !== undefined) {
|
|
196
|
-
query.set("includeLibs", String(options.includeLibs));
|
|
197
|
-
}
|
|
198
|
-
const suffix = [...query.keys(),].length > 0 ? `?${query.toString()}` : "";
|
|
199
|
-
return this.client.stream(`${this.savedModelPath(savedModelId, projectKey)}/versions/${version}/scoring-jar${suffix}`);
|
|
191
|
+
return this.version(savedModelId, versionId, projectKey).downloadScoringJar(options);
|
|
200
192
|
}
|
|
201
193
|
/** GET the scoring PMML of a version as a binary/XML stream. */
|
|
202
194
|
async downloadScoringPmml(savedModelId, versionId, projectKey) {
|
|
203
|
-
|
|
204
|
-
return this.client.stream(`${this.savedModelPath(savedModelId, projectKey)}/versions/${version}/scoring-pmml`);
|
|
195
|
+
return this.version(savedModelId, versionId, projectKey).downloadScoringPmml();
|
|
205
196
|
}
|
|
206
197
|
/**
|
|
207
198
|
* Update the user metadata of a model version. The body must only be the
|
|
208
199
|
* "userMeta" field of a previously-retrieved version-details object.
|
|
209
200
|
*/
|
|
210
201
|
async setUserMeta(savedModelId, versionId, userMeta, projectKey) {
|
|
202
|
+
await this.version(savedModelId, versionId, projectKey).setUserMeta(userMeta);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* One saved-model version as a {@link TrainedModel}: diagnostics
|
|
206
|
+
* (subpopulation analyses, partial dependencies, timeseries residuals and
|
|
207
|
+
* per-series results), documentation, scoring exports, user metadata.
|
|
208
|
+
*/
|
|
209
|
+
version(savedModelId, versionId, projectKey) {
|
|
211
210
|
const version = encodeURIComponent(requireNonEmpty(versionId, "versionId"));
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
return new TrainedModel(this.client, `${this.savedModelPath(savedModelId, projectKey)}/versions/${version}`);
|
|
212
|
+
}
|
|
213
|
+
/** GET a generated saved-model version documentation (docx) by the `exportId` of its finished future. */
|
|
214
|
+
async downloadDocumentation(exportId, projectKey) {
|
|
215
|
+
return this.client.stream(`/public/api/projects/${this.enc(projectKey)}/savedmodels/documentations/${encodeURIComponent(requireNonEmpty(exportId, "exportId"))}`);
|
|
214
216
|
}
|
|
215
217
|
/** Delete a saved model and all of its versions. */
|
|
216
218
|
async delete(savedModelId, projectKey) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID, } from "node:crypto";
|
|
2
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
3
2
|
import { ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, } from "../schemas.js";
|
|
4
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
@@ -56,20 +55,15 @@ function scenarioRunSummaryFromRaw(entry) {
|
|
|
56
55
|
if (!isRecord(entry))
|
|
57
56
|
return { runId: "unknown", };
|
|
58
57
|
const runId = typeof entry.runId === "string" ? entry.runId : "unknown";
|
|
59
|
-
const
|
|
58
|
+
const start = optionalNumber(entry.start);
|
|
59
|
+
const end = optionalNumber(entry.end);
|
|
60
60
|
return {
|
|
61
61
|
runId,
|
|
62
|
-
...(
|
|
63
|
-
...(
|
|
64
|
-
...(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...(optionalRecord(entry.variables) !== undefined
|
|
68
|
-
? { variables: optionalRecord(entry.variables), }
|
|
69
|
-
: {}),
|
|
70
|
-
...(optionalRecord(entry.result) !== undefined
|
|
71
|
-
? { result: optionalRecord(entry.result), }
|
|
72
|
-
: {}),
|
|
62
|
+
...(start !== undefined ? { start, } : {}),
|
|
63
|
+
...(end !== undefined ? { end, } : {}),
|
|
64
|
+
...(isRecord(entry.scenario) ? { scenario: entry.scenario, } : {}),
|
|
65
|
+
...(isRecord(entry.variables) ? { variables: entry.variables, } : {}),
|
|
66
|
+
...(isRecord(entry.result) ? { result: entry.result, } : {}),
|
|
73
67
|
};
|
|
74
68
|
}
|
|
75
69
|
/** Map a light-endpoint response to the documented ScenarioWithStatus shape. */
|
|
@@ -422,7 +416,7 @@ export class ScenariosResource extends BaseResource {
|
|
|
422
416
|
async runScript(script, opts) {
|
|
423
417
|
const pk = this.resolveProjectKey(opts?.projectKey);
|
|
424
418
|
const pkEnc = this.enc(opts?.projectKey);
|
|
425
|
-
const scenarioId = `dss_cli_code_run_${Date.now()}_${randomUUID().replace(/-/g, "")}`;
|
|
419
|
+
const scenarioId = `dss_cli_code_run_${Date.now()}_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
426
420
|
const base = `/public/api/projects/${pkEnc}/scenarios/${encodeURIComponent(scenarioId)}`;
|
|
427
421
|
const envSelection = opts?.envName
|
|
428
422
|
? { envMode: "EXPLICIT_ENV", envName: opts.envName, }
|
|
@@ -76,31 +76,70 @@ function sqlErrorDetailFromBody(body) {
|
|
|
76
76
|
const match = trimmed.match(/\b[A-Z_]+(?:_ERROR|_NOT_FOUND|_MISMATCH|_DENIED|_EXCEEDED)\b[:\s-]+[^\n\r]+/);
|
|
77
77
|
return match?.[0] ?? (isLikelySqlErrorDetail(trimmed) ? trimmed.slice(0, 500) : undefined);
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* JDBC/driver failures that mean the connection to the database broke or timed
|
|
81
|
+
* out mid-statement: a DML statement may already have committed, so these stay
|
|
82
|
+
* an unknown outcome even though they are SQL/JDBC-flavored.
|
|
83
|
+
*/
|
|
84
|
+
const SQL_CONNECTION_LOSS_TYPE = /transient|recoverable|timeout/i;
|
|
85
|
+
const SQL_CONNECTION_LOSS_TEXT = /i\/o error|communication (?:link|error|failure)|connection (?:reset|closed|refused|lost|abort)|broken pipe|socket|timed out|network/i;
|
|
86
|
+
/**
|
|
87
|
+
* Whether DSS answered with a definite refusal of the statement, as opposed to
|
|
88
|
+
* an unknown outcome (transport reset, timeout, bare gateway error). DSS runs the
|
|
89
|
+
* statement before answering the start POST, so a SQL-flavored error body (JDBC
|
|
90
|
+
* error, or an error type from a SQL/JDBC class) or a body DSS itself classifies
|
|
91
|
+
* as permission/not-found means the database rejected the statement — unless
|
|
92
|
+
* the error reports a lost or timed-out database connection.
|
|
93
|
+
*/
|
|
94
|
+
function isDefiniteSqlRejection(error, sqlDetail) {
|
|
95
|
+
if (error.status < 400)
|
|
96
|
+
return false;
|
|
97
|
+
let errorType;
|
|
98
|
+
try {
|
|
99
|
+
errorType = asRecord(JSON.parse(error.body))?.errorType;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
errorType = undefined;
|
|
103
|
+
}
|
|
104
|
+
if (typeof errorType === "string" && SQL_CONNECTION_LOSS_TYPE.test(errorType))
|
|
105
|
+
return false;
|
|
106
|
+
if (SQL_CONNECTION_LOSS_TEXT.test(error.body))
|
|
107
|
+
return false;
|
|
108
|
+
if (sqlDetail)
|
|
109
|
+
return true;
|
|
110
|
+
if (error.category === "forbidden" || error.category === "not_found")
|
|
111
|
+
return true;
|
|
112
|
+
return typeof errorType === "string" && /sql|jdbc/i.test(errorType);
|
|
113
|
+
}
|
|
79
114
|
function withSqlErrorContext(error) {
|
|
80
115
|
if (error instanceof DataikuError) {
|
|
81
116
|
const detail = sqlErrorDetailFromBody(error.body);
|
|
82
|
-
|
|
117
|
+
const definiteFailure = isDefiniteSqlRejection(error, detail);
|
|
118
|
+
if (detail || definiteFailure) {
|
|
83
119
|
let body = error.body;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
if (detail) {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(error.body);
|
|
123
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
124
|
+
body = JSON.stringify({
|
|
125
|
+
...parsed,
|
|
126
|
+
message: `SQL query failed: ${detail}`,
|
|
127
|
+
sqlError: detail,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
body = `SQL query failed: ${detail}\n${error.body}`;
|
|
132
|
+
}
|
|
92
133
|
}
|
|
93
|
-
|
|
134
|
+
catch {
|
|
94
135
|
body = `SQL query failed: ${detail}\n${error.body}`;
|
|
95
136
|
}
|
|
96
137
|
}
|
|
97
|
-
catch {
|
|
98
|
-
body = `SQL query failed: ${detail}\n${error.body}`;
|
|
99
|
-
}
|
|
100
138
|
throw new DataikuError(error.status, error.statusText, body, error.retry, error.requestId, {
|
|
101
139
|
target: error.trustedTarget,
|
|
102
140
|
elapsedMs: error.trustedElapsedMs,
|
|
103
141
|
bodyTruncated: error.bodyTruncated,
|
|
142
|
+
definiteFailure,
|
|
104
143
|
});
|
|
105
144
|
}
|
|
106
145
|
}
|
|
@@ -38,6 +38,18 @@ export declare class StreamingEndpointsResource extends BaseResource {
|
|
|
38
38
|
getSettings(id: string, projectKey?: string): Promise<StreamingEndpointSettings>;
|
|
39
39
|
/** Create a streaming endpoint with type-specific params. */
|
|
40
40
|
create(id: string, type: string, body: StreamingEndpointCreateParams, projectKey?: string): Promise<StreamingEndpointSettings>;
|
|
41
|
+
/**
|
|
42
|
+
* Create a managed streaming endpoint (POST /streamingendpoints/managed):
|
|
43
|
+
* DSS picks type and params from the connection and format option.
|
|
44
|
+
*/
|
|
45
|
+
createManaged(id: string, creationSettings: {
|
|
46
|
+
connectionId: string;
|
|
47
|
+
formatOptionId?: string;
|
|
48
|
+
}, projectKey?: string): Promise<void>;
|
|
49
|
+
/** Get a streaming endpoint's schema (`{columns: [{name, type}]}`). */
|
|
50
|
+
getSchema(id: string, projectKey?: string): Promise<Record<string, unknown>>;
|
|
51
|
+
/** Replace a streaming endpoint's schema with an object obtained from `getSchema`, edited. */
|
|
52
|
+
setSchema(id: string, schema: Record<string, unknown>, projectKey?: string): Promise<void>;
|
|
41
53
|
/** Update one streaming endpoint's raw settings. */
|
|
42
54
|
updateSettings(id: string, body: Record<string, unknown>, projectKey?: string): Promise<void>;
|
|
43
55
|
/** Delete a streaming endpoint from the flow without deleting underlying streaming data. */
|
|
@@ -23,6 +23,21 @@ export class StreamingEndpointsResource extends BaseResource {
|
|
|
23
23
|
params: body,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a managed streaming endpoint (POST /streamingendpoints/managed):
|
|
28
|
+
* DSS picks type and params from the connection and format option.
|
|
29
|
+
*/
|
|
30
|
+
async createManaged(id, creationSettings, projectKey) {
|
|
31
|
+
await this.client.post(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/managed`, { id, creationSettings, });
|
|
32
|
+
}
|
|
33
|
+
/** Get a streaming endpoint's schema (`{columns: [{name, type}]}`). */
|
|
34
|
+
async getSchema(id, projectKey) {
|
|
35
|
+
return this.client.get(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/${encodeURIComponent(id)}/schema`);
|
|
36
|
+
}
|
|
37
|
+
/** Replace a streaming endpoint's schema with an object obtained from `getSchema`, edited. */
|
|
38
|
+
async setSchema(id, schema, projectKey) {
|
|
39
|
+
await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/${encodeURIComponent(id)}/schema`, schema);
|
|
40
|
+
}
|
|
26
41
|
/** Update one streaming endpoint's raw settings. */
|
|
27
42
|
async updateSettings(id, body, projectKey) {
|
|
28
43
|
const endpointId = encodeURIComponent(id);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DataikuClient } from "../client.js";
|
|
2
|
+
import { type DocumentationTemplate } from "./documentation.js";
|
|
3
|
+
import { type DssTask } from "./futures.js";
|
|
4
|
+
/** Sampling and execution settings of a diagnostics computation (dataikuapi `computationParams`). */
|
|
5
|
+
export interface ModelDiagnosticsComputation {
|
|
6
|
+
/** Records sampled from the test set (DSS default 10000). */
|
|
7
|
+
sampleSize?: number;
|
|
8
|
+
randomState?: number;
|
|
9
|
+
nJobs?: number;
|
|
10
|
+
debugMode?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Diagnostics a trained model computes on demand, then serves as results. */
|
|
13
|
+
export type ComputedModelDiagnostic = "subpopulation-analyses" | "partial-dependencies" | "timeseries-residuals";
|
|
14
|
+
/** Every diagnostics result a trained model serves. */
|
|
15
|
+
export type ModelDiagnostic = ComputedModelDiagnostic | "per-timeseries-metrics" | "per-timeseries-evaluation-forecasts";
|
|
16
|
+
/** Diagnostics that are started with POST and return a DSS future. */
|
|
17
|
+
export declare const COMPUTED_MODEL_DIAGNOSTICS: Record<string, true>;
|
|
18
|
+
/** Diagnostics results served by GET (computed ones plus per-series results of forecasting models). */
|
|
19
|
+
export declare const MODEL_DIAGNOSTICS: Record<string, true>;
|
|
20
|
+
/**
|
|
21
|
+
* Request body that starts a diagnostics computation. Subpopulation analyses
|
|
22
|
+
* and partial dependencies take the features to analyse; timeseries
|
|
23
|
+
* residuals take no body.
|
|
24
|
+
*/
|
|
25
|
+
export declare function modelDiagnosticsBody(kind: ComputedModelDiagnostic, features: string[], computation?: ModelDiagnosticsComputation): Record<string, unknown> | undefined;
|
|
26
|
+
/** Query options for the scoring JAR export (GET .../scoring-jar). */
|
|
27
|
+
export interface ScoringJarOptions {
|
|
28
|
+
fullClassName?: string;
|
|
29
|
+
includeLibs?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One trained model: a visual-ML lab model (`mlTasks.model(...)`) or a
|
|
33
|
+
* saved-model version (`savedModels.version(...)`). Both expose the same
|
|
34
|
+
* routes (user meta, scoring exports, diagnostics, documentation) under
|
|
35
|
+
* different bases. Generated documents are downloaded from the owning
|
|
36
|
+
* resource (`mlTasks.downloadModelDocumentation`, `savedModels.downloadDocumentation`).
|
|
37
|
+
*/
|
|
38
|
+
export declare class TrainedModel {
|
|
39
|
+
private readonly client;
|
|
40
|
+
/** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
|
|
41
|
+
private readonly modelPath;
|
|
42
|
+
constructor(client: DataikuClient,
|
|
43
|
+
/** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
|
|
44
|
+
modelPath: string);
|
|
45
|
+
/** Replace the user metadata: only the `userMeta` field of the model details, edited. */
|
|
46
|
+
setUserMeta(userMeta: Record<string, unknown>): Promise<void>;
|
|
47
|
+
/** GET the Java scoring JAR (license-gated, optimized-scoring models only) as a binary stream. */
|
|
48
|
+
downloadScoringJar(options?: ScoringJarOptions): Promise<Response>;
|
|
49
|
+
/** GET the PMML export (license-gated, PMML-compatible models only) as a stream. */
|
|
50
|
+
downloadScoringPmml(): Promise<Response>;
|
|
51
|
+
/**
|
|
52
|
+
* Start computing subpopulation analyses or partial dependencies over
|
|
53
|
+
* `features`, or timeseries residuals. Returns the DSS future.
|
|
54
|
+
*/
|
|
55
|
+
compute(kind: ComputedModelDiagnostic, features?: string[], computation?: ModelDiagnosticsComputation): Promise<DssTask>;
|
|
56
|
+
/** Results computed so far (subpopulation analyses, partial dependencies, residuals, per-series metrics or forecasts). */
|
|
57
|
+
get(kind: ModelDiagnostic): Promise<unknown>;
|
|
58
|
+
/** Start generating the model documentation (docx); the finished result carries `exportId`. */
|
|
59
|
+
generateDocumentation(template?: DocumentationTemplate): Promise<DssTask>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
|
+
import { requireObject, } from "./base.js";
|
|
3
|
+
import { startDocumentation, } from "./documentation.js";
|
|
4
|
+
import { dssTask, } from "./futures.js";
|
|
5
|
+
/** Diagnostics that are started with POST and return a DSS future. */
|
|
6
|
+
export const COMPUTED_MODEL_DIAGNOSTICS = {
|
|
7
|
+
"subpopulation-analyses": true,
|
|
8
|
+
"partial-dependencies": true,
|
|
9
|
+
"timeseries-residuals": true,
|
|
10
|
+
};
|
|
11
|
+
/** Diagnostics results served by GET (computed ones plus per-series results of forecasting models). */
|
|
12
|
+
export const MODEL_DIAGNOSTICS = {
|
|
13
|
+
...COMPUTED_MODEL_DIAGNOSTICS,
|
|
14
|
+
"per-timeseries-metrics": true,
|
|
15
|
+
"per-timeseries-evaluation-forecasts": true,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Request body that starts a diagnostics computation. Subpopulation analyses
|
|
19
|
+
* and partial dependencies take the features to analyse; timeseries
|
|
20
|
+
* residuals take no body.
|
|
21
|
+
*/
|
|
22
|
+
export function modelDiagnosticsBody(kind, features, computation = {}) {
|
|
23
|
+
if (kind === "timeseries-residuals")
|
|
24
|
+
return undefined;
|
|
25
|
+
if (features.length === 0) {
|
|
26
|
+
throw new ClientValidationError(`${kind} needs at least one feature.`, "missing_required_arg");
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
features,
|
|
30
|
+
computationParams: {
|
|
31
|
+
...(computation.sampleSize !== undefined ? { sample_size: computation.sampleSize, } : {}),
|
|
32
|
+
...(computation.randomState !== undefined ? { random_state: computation.randomState, } : {}),
|
|
33
|
+
...(computation.nJobs !== undefined ? { n_jobs: computation.nJobs, } : {}),
|
|
34
|
+
...(computation.debugMode !== undefined ? { debug_mode: computation.debugMode, } : {}),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One trained model: a visual-ML lab model (`mlTasks.model(...)`) or a
|
|
40
|
+
* saved-model version (`savedModels.version(...)`). Both expose the same
|
|
41
|
+
* routes (user meta, scoring exports, diagnostics, documentation) under
|
|
42
|
+
* different bases. Generated documents are downloaded from the owning
|
|
43
|
+
* resource (`mlTasks.downloadModelDocumentation`, `savedModels.downloadDocumentation`).
|
|
44
|
+
*/
|
|
45
|
+
export class TrainedModel {
|
|
46
|
+
client;
|
|
47
|
+
modelPath;
|
|
48
|
+
constructor(client,
|
|
49
|
+
/** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
|
|
50
|
+
modelPath) {
|
|
51
|
+
this.client = client;
|
|
52
|
+
this.modelPath = modelPath;
|
|
53
|
+
}
|
|
54
|
+
/** Replace the user metadata: only the `userMeta` field of the model details, edited. */
|
|
55
|
+
async setUserMeta(userMeta) {
|
|
56
|
+
await this.client.putVoid(`${this.modelPath}/user-meta`, requireObject(userMeta, "userMeta"));
|
|
57
|
+
}
|
|
58
|
+
/** GET the Java scoring JAR (license-gated, optimized-scoring models only) as a binary stream. */
|
|
59
|
+
async downloadScoringJar(options = {}) {
|
|
60
|
+
const query = new URLSearchParams();
|
|
61
|
+
if (options.fullClassName !== undefined)
|
|
62
|
+
query.set("fullClassName", options.fullClassName);
|
|
63
|
+
if (options.includeLibs !== undefined)
|
|
64
|
+
query.set("includeLibs", String(options.includeLibs));
|
|
65
|
+
return this.client.stream(`${this.modelPath}/scoring-jar${query.size > 0 ? `?${query}` : ""}`);
|
|
66
|
+
}
|
|
67
|
+
/** GET the PMML export (license-gated, PMML-compatible models only) as a stream. */
|
|
68
|
+
async downloadScoringPmml() {
|
|
69
|
+
return this.client.stream(`${this.modelPath}/scoring-pmml`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start computing subpopulation analyses or partial dependencies over
|
|
73
|
+
* `features`, or timeseries residuals. Returns the DSS future.
|
|
74
|
+
*/
|
|
75
|
+
async compute(kind, features = [], computation = {}) {
|
|
76
|
+
if (COMPUTED_MODEL_DIAGNOSTICS[kind] !== true) {
|
|
77
|
+
throw new ClientValidationError(`Computed diagnostic must be one of ${Object.keys(COMPUTED_MODEL_DIAGNOSTICS).join(", ")}.`, "invalid_enum");
|
|
78
|
+
}
|
|
79
|
+
const raw = await this.client.post(`${this.modelPath}/${kind}`, modelDiagnosticsBody(kind, features, computation));
|
|
80
|
+
return dssTask(raw, `Computing ${kind}`);
|
|
81
|
+
}
|
|
82
|
+
/** Results computed so far (subpopulation analyses, partial dependencies, residuals, per-series metrics or forecasts). */
|
|
83
|
+
async get(kind) {
|
|
84
|
+
if (MODEL_DIAGNOSTICS[kind] !== true) {
|
|
85
|
+
throw new ClientValidationError(`Diagnostic must be one of ${Object.keys(MODEL_DIAGNOSTICS).join(", ")}.`, "invalid_enum");
|
|
86
|
+
}
|
|
87
|
+
return this.client.get(`${this.modelPath}/${kind}`);
|
|
88
|
+
}
|
|
89
|
+
/** Start generating the model documentation (docx); the finished result carries `exportId`. */
|
|
90
|
+
async generateDocumentation(template = { kind: "default", }) {
|
|
91
|
+
const base = `${this.modelPath}/generate-documentation-from`;
|
|
92
|
+
return startDocumentation(this.client, {
|
|
93
|
+
default: `${base}-default-template`,
|
|
94
|
+
file: `${base}-custom-template`,
|
|
95
|
+
folder: `${base}-template-in-folder`,
|
|
96
|
+
}, template, "Model documentation generation");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -68,4 +68,12 @@ export declare class WebappsResource extends BaseResource {
|
|
|
68
68
|
restartBackendAndWait(webappId: string, projectKey?: string, options?: WebappWaitOptions): Promise<FutureWaitResult>;
|
|
69
69
|
/** Get a webapp backend's state. */
|
|
70
70
|
getBackendState(webappId: string, projectKey?: string): Promise<Record<string, unknown>>;
|
|
71
|
+
/**
|
|
72
|
+
* Trust a webapp's code (POST /actions/trust): for the caller, or for every
|
|
73
|
+
* user with `trustForEverybody` (admin). Returns `{canAccess, trustedCodeStatus}`.
|
|
74
|
+
*/
|
|
75
|
+
trust(webappId: string, opts?: {
|
|
76
|
+
projectKey?: string;
|
|
77
|
+
trustForEverybody?: boolean;
|
|
78
|
+
}): Promise<Record<string, unknown>>;
|
|
71
79
|
}
|
|
@@ -95,4 +95,14 @@ export class WebappsResource extends BaseResource {
|
|
|
95
95
|
const id = encodeURIComponent(webappId);
|
|
96
96
|
return this.client.get(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}/backend/state`);
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Trust a webapp's code (POST /actions/trust): for the caller, or for every
|
|
100
|
+
* user with `trustForEverybody` (admin). Returns `{canAccess, trustedCodeStatus}`.
|
|
101
|
+
*/
|
|
102
|
+
async trust(webappId, opts = {}) {
|
|
103
|
+
const query = opts.trustForEverybody === undefined
|
|
104
|
+
? ""
|
|
105
|
+
: `?trustForEverybody=${opts.trustForEverybody}`;
|
|
106
|
+
return this.client.post(`/public/api/projects/${this.enc(opts.projectKey)}/webapps/${encodeURIComponent(webappId)}/actions/trust${query}`);
|
|
107
|
+
}
|
|
98
108
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ClientValidationError } from "../errors.js";
|
|
2
|
+
import type { WikiArticleAttachment, WikiArticleData, WikiSettings } from "../schemas.js";
|
|
2
3
|
import { BaseResource } from "./base.js";
|
|
3
4
|
export interface WikiArticleCreateOptions {
|
|
4
5
|
name: string;
|
|
@@ -12,11 +13,61 @@ export interface WikiArticleUpdateOptions {
|
|
|
12
13
|
data?: Record<string, unknown>;
|
|
13
14
|
projectKey?: string;
|
|
14
15
|
}
|
|
16
|
+
export interface WikiAttachResult {
|
|
17
|
+
articleId: string;
|
|
18
|
+
projectKey: string;
|
|
19
|
+
fileName: string;
|
|
20
|
+
bytes: number;
|
|
21
|
+
/** The attachment DSS created, as re-read from the article. */
|
|
22
|
+
attachment: WikiArticleAttachment;
|
|
23
|
+
/** All attachments of the article after the upload. */
|
|
24
|
+
attachments: WikiArticleAttachment[];
|
|
25
|
+
}
|
|
26
|
+
export interface WikiDetachResult {
|
|
27
|
+
articleId: string;
|
|
28
|
+
projectKey: string;
|
|
29
|
+
/** The attachment that was removed. */
|
|
30
|
+
detached: WikiArticleAttachment;
|
|
31
|
+
/** Attachments left on the article. */
|
|
32
|
+
attachments: WikiArticleAttachment[];
|
|
33
|
+
}
|
|
34
|
+
export interface LocalWikiAttachmentFile {
|
|
35
|
+
path: string;
|
|
36
|
+
fileName: string;
|
|
37
|
+
bytes: number;
|
|
38
|
+
}
|
|
39
|
+
/** Attachments of an article; DSS omits the field when there are none. */
|
|
40
|
+
export declare function wikiAttachments(data: WikiArticleData): WikiArticleAttachment[];
|
|
41
|
+
/** The attachment with exactly this smart id, or undefined. */
|
|
42
|
+
export declare function findWikiAttachment(data: WikiArticleData, smartId: string): WikiArticleAttachment | undefined;
|
|
43
|
+
/** Coded not_found error for a smart id that is not attached to the article. */
|
|
44
|
+
export declare function wikiAttachmentNotFound(data: WikiArticleData, smartId: string): ClientValidationError;
|
|
45
|
+
/** Check that a local path is a readable regular file before any DSS call. */
|
|
46
|
+
export declare function statWikiAttachmentFile(localPath: string): Promise<LocalWikiAttachmentFile>;
|
|
15
47
|
export declare class WikiResource extends BaseResource {
|
|
16
48
|
settings(projectKey?: string): Promise<WikiSettings>;
|
|
49
|
+
/**
|
|
50
|
+
* Replace the wiki properties (home article and taxonomy, PUT /wiki/) with
|
|
51
|
+
* an object obtained from `settings`, edited.
|
|
52
|
+
*/
|
|
53
|
+
updateSettings(settings: Record<string, unknown>, projectKey?: string): Promise<void>;
|
|
17
54
|
list(projectKey?: string): Promise<WikiArticleData[]>;
|
|
18
55
|
get(articleIdOrName: string, projectKey?: string): Promise<WikiArticleData>;
|
|
19
56
|
create(opts: WikiArticleCreateOptions): Promise<WikiArticleData>;
|
|
20
57
|
update(articleIdOrName: string, opts: WikiArticleUpdateOptions): Promise<WikiArticleData>;
|
|
21
58
|
delete(articleIdOrName: string, projectKey?: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Upload a local file and attach it to an article (multipart POST to
|
|
61
|
+
* /wiki/{id}/upload). DSS names the attachment after the file's basename and
|
|
62
|
+
* types it from the extension. The article is re-read to identify the new
|
|
63
|
+
* attachment.
|
|
64
|
+
*/
|
|
65
|
+
attach(articleIdOrName: string, localPath: string, projectKey?: string): Promise<WikiAttachResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Remove exactly one attachment (by smart id) from an article: the article is
|
|
68
|
+
* read, the entry filtered out of `article.attachments`, and the whole
|
|
69
|
+
* article PUT back. Throws a coded `not_found` error when the smart id is not
|
|
70
|
+
* attached.
|
|
71
|
+
*/
|
|
72
|
+
detach(articleIdOrName: string, smartId: string, projectKey?: string): Promise<WikiDetachResult>;
|
|
22
73
|
}
|
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ClientValidationError, } from "../errors.js";
|
|
1
4
|
import { WikiArticleDataArraySchema, WikiArticleDataSchema, WikiSettingsSchema, } from "../schemas.js";
|
|
2
5
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
3
6
|
import { BaseResource, } from "./base.js";
|
|
7
|
+
/** Attachments of an article; DSS omits the field when there are none. */
|
|
8
|
+
export function wikiAttachments(data) {
|
|
9
|
+
return data.article.attachments ?? [];
|
|
10
|
+
}
|
|
11
|
+
/** The attachment with exactly this smart id, or undefined. */
|
|
12
|
+
export function findWikiAttachment(data, smartId) {
|
|
13
|
+
return wikiAttachments(data).find((attachment) => attachment.smartId === smartId);
|
|
14
|
+
}
|
|
15
|
+
/** Coded not_found error for a smart id that is not attached to the article. */
|
|
16
|
+
export function wikiAttachmentNotFound(data, smartId) {
|
|
17
|
+
const available = wikiAttachments(data).flatMap((attachment) => attachment.smartId === undefined ? [] : [attachment.smartId,]);
|
|
18
|
+
return new ClientValidationError(`Attachment ${smartId} not found on wiki article ${data.article.id}.`, "not_found", "List attachments with `dss wiki get ARTICLE_ID` (article.attachments[].smartId), or pass --if-exists to treat absence as success.", { articleId: data.article.id, smartId, availableSmartIds: available, });
|
|
19
|
+
}
|
|
20
|
+
/** Check that a local path is a readable regular file before any DSS call. */
|
|
21
|
+
export async function statWikiAttachmentFile(localPath) {
|
|
22
|
+
let stat;
|
|
23
|
+
try {
|
|
24
|
+
stat = await fs.promises.stat(localPath);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new ClientValidationError(`Could not read attachment file ${localPath}: ${error instanceof Error ? error.message : String(error)}.`, "not_found", "Verify the file path and that it is readable.");
|
|
28
|
+
}
|
|
29
|
+
if (!stat.isFile()) {
|
|
30
|
+
throw new ClientValidationError(`${localPath} is not a regular file.`);
|
|
31
|
+
}
|
|
32
|
+
return { path: localPath, fileName: path.basename(localPath), bytes: stat.size, };
|
|
33
|
+
}
|
|
4
34
|
const WIKI_LIST_CONCURRENCY = 4;
|
|
5
35
|
function taxonomyIds(nodes) {
|
|
6
36
|
const ids = [];
|
|
@@ -33,6 +63,13 @@ export class WikiResource extends BaseResource {
|
|
|
33
63
|
const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/wiki/`);
|
|
34
64
|
return this.client.safeParse(WikiSettingsSchema, raw, "wiki.settings");
|
|
35
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Replace the wiki properties (home article and taxonomy, PUT /wiki/) with
|
|
68
|
+
* an object obtained from `settings`, edited.
|
|
69
|
+
*/
|
|
70
|
+
async updateSettings(settings, projectKey) {
|
|
71
|
+
await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/wiki/`, settings);
|
|
72
|
+
}
|
|
36
73
|
async list(projectKey) {
|
|
37
74
|
const settings = await this.settings(projectKey);
|
|
38
75
|
const ids = taxonomyIds(settings.taxonomy);
|
|
@@ -70,4 +107,63 @@ export class WikiResource extends BaseResource {
|
|
|
70
107
|
const current = await this.get(articleIdOrName, projectKey);
|
|
71
108
|
await this.client.del(`/public/api/projects/${this.enc(projectKey)}/wiki/${encodeURIComponent(current.article.id)}`);
|
|
72
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Upload a local file and attach it to an article (multipart POST to
|
|
112
|
+
* /wiki/{id}/upload). DSS names the attachment after the file's basename and
|
|
113
|
+
* types it from the extension. The article is re-read to identify the new
|
|
114
|
+
* attachment.
|
|
115
|
+
*/
|
|
116
|
+
async attach(articleIdOrName, localPath, projectKey) {
|
|
117
|
+
const file = await statWikiAttachmentFile(localPath);
|
|
118
|
+
const pk = this.resolveProjectKey(projectKey);
|
|
119
|
+
const before = await this.get(articleIdOrName, pk);
|
|
120
|
+
const articleId = before.article.id;
|
|
121
|
+
await this.client.uploadJson(`/public/api/projects/${encodeURIComponent(pk)}/wiki/${encodeURIComponent(articleId)}/upload`, localPath, file.fileName);
|
|
122
|
+
const after = await this.get(articleId, pk);
|
|
123
|
+
const knownIds = new Set(wikiAttachments(before).map((attachment) => attachment.smartId));
|
|
124
|
+
let created = wikiAttachments(after).filter((attachment) => !knownIds.has(attachment.smartId));
|
|
125
|
+
if (created.length > 1) {
|
|
126
|
+
created = created.filter((attachment) => attachment.details?.["objectDisplayName"] === file.fileName
|
|
127
|
+
&& attachment.details?.["size"] === file.bytes);
|
|
128
|
+
}
|
|
129
|
+
const attachment = created[0];
|
|
130
|
+
if (created.length !== 1 || attachment === undefined || attachment.smartId === undefined) {
|
|
131
|
+
throw new ClientValidationError(`DSS did not verify upload of ${file.fileName} to wiki article ${articleId}.`, "ambiguous_outcome", "The upload returned successfully, but the article did not show exactly one new attachment. Inspect the article (dss wiki get) before retrying.", { articleId, expectedBytes: file.bytes, newAttachments: created, });
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
articleId,
|
|
135
|
+
projectKey: pk,
|
|
136
|
+
fileName: file.fileName,
|
|
137
|
+
bytes: file.bytes,
|
|
138
|
+
attachment,
|
|
139
|
+
attachments: wikiAttachments(after),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Remove exactly one attachment (by smart id) from an article: the article is
|
|
144
|
+
* read, the entry filtered out of `article.attachments`, and the whole
|
|
145
|
+
* article PUT back. Throws a coded `not_found` error when the smart id is not
|
|
146
|
+
* attached.
|
|
147
|
+
*/
|
|
148
|
+
async detach(articleIdOrName, smartId, projectKey) {
|
|
149
|
+
const pk = this.resolveProjectKey(projectKey);
|
|
150
|
+
const current = await this.get(articleIdOrName, pk);
|
|
151
|
+
const detached = findWikiAttachment(current, smartId);
|
|
152
|
+
if (detached === undefined)
|
|
153
|
+
throw wikiAttachmentNotFound(current, smartId);
|
|
154
|
+
const remaining = wikiAttachments(current).filter((attachment) => attachment.smartId !== smartId);
|
|
155
|
+
const raw = await this.client.put(`/public/api/projects/${encodeURIComponent(pk)}/wiki/${encodeURIComponent(current.article.id)}`, { ...current, article: { ...current.article, attachments: remaining, }, });
|
|
156
|
+
const saved = this.client.safeParse(WikiArticleDataSchema, raw, "wiki.detach");
|
|
157
|
+
const after = wikiAttachments(saved);
|
|
158
|
+
if (findWikiAttachment(saved, smartId) !== undefined
|
|
159
|
+
|| after.length !== remaining.length) {
|
|
160
|
+
throw new ClientValidationError(`DSS did not verify removal of attachment ${smartId} from wiki article ${current.article.id}.`, "ambiguous_outcome", "The save returned successfully, but the article's attachments are not the expected remainder. Inspect the article (dss wiki get) before retrying.", {
|
|
161
|
+
articleId: current.article.id,
|
|
162
|
+
smartId,
|
|
163
|
+
expectedRemaining: remaining.length,
|
|
164
|
+
actualRemaining: after.length,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return { articleId: current.article.id, projectKey: pk, detached, attachments: after, };
|
|
168
|
+
}
|
|
73
169
|
}
|
|
@@ -56,4 +56,6 @@ export declare class WorkspacesResource extends BaseResource {
|
|
|
56
56
|
listObjects(workspaceKey: string): Promise<WorkspaceObject[]>;
|
|
57
57
|
/** Add an object to a workspace. */
|
|
58
58
|
addObject(workspaceKey: string, object: WorkspaceObjectInput): Promise<WorkspaceObject>;
|
|
59
|
+
/** Remove an object from a workspace by its workspace object id (from `listObjects`). */
|
|
60
|
+
removeObject(workspaceKey: string, workspaceObjectId: string): Promise<void>;
|
|
59
61
|
}
|
|
@@ -28,4 +28,8 @@ export class WorkspacesResource extends BaseResource {
|
|
|
28
28
|
async addObject(workspaceKey, object) {
|
|
29
29
|
return this.client.post(`/public/api/workspaces/${encodeURIComponent(workspaceKey)}/objects`, object);
|
|
30
30
|
}
|
|
31
|
+
/** Remove an object from a workspace by its workspace object id (from `listObjects`). */
|
|
32
|
+
async removeObject(workspaceKey, workspaceObjectId) {
|
|
33
|
+
await this.client.del(`/public/api/workspaces/${encodeURIComponent(workspaceKey)}/objects/${encodeURIComponent(workspaceObjectId)}`);
|
|
34
|
+
}
|
|
31
35
|
}
|
|
@@ -345,6 +345,10 @@ function renderMermaid(map) {
|
|
|
345
345
|
lines.push(` ${idByNode.get(nodeId)}["${mermaidLabel(readableNode(node))}"]`);
|
|
346
346
|
}
|
|
347
347
|
lines.push(" end");
|
|
348
|
+
// Zone colors come from DSS; only plain #RRGGBB values reach the style line.
|
|
349
|
+
if (zone.color && /^#[0-9a-fA-F]{6}$/.test(zone.color)) {
|
|
350
|
+
lines.push(` style z${zoneIndex} fill:${zone.color}22,stroke:${zone.color}`);
|
|
351
|
+
}
|
|
348
352
|
}
|
|
349
353
|
for (const edge of map.edges) {
|
|
350
354
|
const from = idByNode.get(edge.from);
|