dataiku-sdk 0.12.6 → 1.0.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 +60 -14
- package/bin/dss.js +1 -1
- package/dist/packages/types/src/index.d.ts +128 -3
- package/dist/packages/types/src/index.js +106 -3
- package/dist/src/cli/coerce.d.ts +2 -1
- package/dist/src/cli/coerce.js +84 -26
- package/dist/src/cli/commands/app.js +6 -6
- package/dist/src/cli/commands/code.js +2 -2
- package/dist/src/cli/commands/dashboard.js +102 -13
- package/dist/src/cli/commands/data-quality.js +8 -8
- package/dist/src/cli/commands/dataset.js +11 -7
- package/dist/src/cli/commands/folder.js +5 -4
- package/dist/src/cli/commands/future.js +2 -2
- package/dist/src/cli/commands/index.js +2 -0
- package/dist/src/cli/commands/job.js +11 -11
- package/dist/src/cli/commands/notebook.js +1 -1
- package/dist/src/cli/commands/project-git.d.ts +2 -0
- package/dist/src/cli/commands/project-git.js +563 -0
- package/dist/src/cli/commands/project.js +2 -2
- package/dist/src/cli/commands/recipe.js +14 -17
- package/dist/src/cli/commands/scenario.js +4 -4
- package/dist/src/cli/contract.d.ts +5 -4
- package/dist/src/cli/contract.js +432 -62
- package/dist/src/cli/doctor.js +6 -6
- package/dist/src/cli/flags.js +25 -2
- package/dist/src/cli/helpers/dataset.js +1 -1
- package/dist/src/cli/output.d.ts +2 -1
- package/dist/src/cli/output.js +2 -6
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +2 -1
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +2 -2
- package/dist/src/cli.js +43 -26
- package/dist/src/client.d.ts +4 -0
- package/dist/src/client.js +12 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +2 -1
- package/dist/src/resources/connections.d.ts +3 -1
- package/dist/src/resources/connections.js +82 -21
- package/dist/src/resources/dashboards.d.ts +14 -0
- package/dist/src/resources/dashboards.js +156 -4
- package/dist/src/resources/datasets.js +42 -2
- package/dist/src/resources/folders.js +7 -3
- package/dist/src/resources/project-git.d.ts +154 -0
- package/dist/src/resources/project-git.js +427 -0
- package/dist/src/resources/recipes.js +4 -4
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +9 -211
- package/package.json +7 -4
- package/packages/types/dist/index.d.ts +128 -3
- package/packages/types/dist/index.js +106 -3
- package/plugin.json +15 -0
- package/skills/dataiku-dss/SKILL.md +220 -0
|
@@ -9,27 +9,89 @@ function normalizeConnectionNames(value) {
|
|
|
9
9
|
.filter((v) => typeof v === "string" && v.length > 0)
|
|
10
10
|
.sort();
|
|
11
11
|
}
|
|
12
|
-
async function
|
|
13
|
-
|
|
12
|
+
export async function resolveAdminManagedStorageConnection(client, capability, preferredName) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await client.get("/public/api/admin/connections/");
|
|
15
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
16
|
+
return undefined;
|
|
17
|
+
const candidates = [];
|
|
18
|
+
for (const [name, value,] of Object.entries(raw)) {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
20
|
+
continue;
|
|
21
|
+
const details = value;
|
|
22
|
+
if (details["allowWrite"] === true && details[capability] === true) {
|
|
23
|
+
candidates.push(name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
candidates.sort();
|
|
27
|
+
return preferredName && candidates.includes(preferredName) ? preferredName : candidates[0];
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function inferRichConnections(client, projectEnc) {
|
|
34
|
+
const datasetsRaw = await client.get(`/public/api/projects/${projectEnc}/datasets/`);
|
|
35
|
+
const datasets = Array.isArray(datasetsRaw) ? datasetsRaw : [];
|
|
14
36
|
const map = new Map();
|
|
15
|
-
|
|
16
|
-
const d = ds;
|
|
17
|
-
const params = d["params"];
|
|
18
|
-
const connection = params?.["connection"];
|
|
37
|
+
const recordConnection = (connection, type, managed, schema) => {
|
|
19
38
|
if (typeof connection !== "string" || connection.length === 0)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
return;
|
|
40
|
+
let entry = map.get(connection);
|
|
41
|
+
if (!entry) {
|
|
42
|
+
entry = { types: new Set(), managed: false, dbSchemas: new Set(), };
|
|
43
|
+
map.set(connection, entry);
|
|
23
44
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
entry.types.add(dsType);
|
|
28
|
-
if (d["managed"] === true)
|
|
45
|
+
if (typeof type === "string" && type.length > 0)
|
|
46
|
+
entry.types.add(type);
|
|
47
|
+
if (managed)
|
|
29
48
|
entry.managed = true;
|
|
30
|
-
const schema = params?.["schema"];
|
|
31
49
|
if (typeof schema === "string" && schema.length > 0)
|
|
32
50
|
entry.dbSchemas.add(schema);
|
|
51
|
+
};
|
|
52
|
+
for (const dataset of datasets) {
|
|
53
|
+
if (!dataset || typeof dataset !== "object" || Array.isArray(dataset))
|
|
54
|
+
continue;
|
|
55
|
+
const details = dataset;
|
|
56
|
+
const params = details["params"];
|
|
57
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
58
|
+
continue;
|
|
59
|
+
const datasetParams = params;
|
|
60
|
+
recordConnection(datasetParams["connection"], details["type"], details["managed"] === true, datasetParams["schema"]);
|
|
61
|
+
recordConnection(datasetParams["uploadConnection"], details["type"], false);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const foldersRaw = await client.get(`/public/api/projects/${projectEnc}/managedfolders/`);
|
|
65
|
+
const folders = Array.isArray(foldersRaw) ? foldersRaw : [];
|
|
66
|
+
const folderDetails = await Promise.all(folders.map(async (folder) => {
|
|
67
|
+
if (!folder || typeof folder !== "object" || Array.isArray(folder))
|
|
68
|
+
return folder;
|
|
69
|
+
const summary = folder;
|
|
70
|
+
const params = summary["params"];
|
|
71
|
+
if (params && typeof params === "object" && !Array.isArray(params))
|
|
72
|
+
return summary;
|
|
73
|
+
const id = summary["id"];
|
|
74
|
+
if (typeof id !== "string" || id.length === 0)
|
|
75
|
+
return summary;
|
|
76
|
+
try {
|
|
77
|
+
return await client.get(`/public/api/projects/${projectEnc}/managedfolders/${encodeURIComponent(id)}`);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return summary;
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
for (const folder of folderDetails) {
|
|
84
|
+
if (!folder || typeof folder !== "object" || Array.isArray(folder))
|
|
85
|
+
continue;
|
|
86
|
+
const details = folder;
|
|
87
|
+
const params = details["params"];
|
|
88
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
89
|
+
continue;
|
|
90
|
+
recordConnection(params["connection"], details["type"], true);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Dataset-derived inference remains useful when managed folders are inaccessible.
|
|
33
95
|
}
|
|
34
96
|
return [...map.entries(),]
|
|
35
97
|
.sort(([a,], [b,]) => a.localeCompare(b))
|
|
@@ -48,9 +110,8 @@ export class ConnectionsResource extends BaseResource {
|
|
|
48
110
|
* Returns sorted list of all connection names visible to the current user.
|
|
49
111
|
*/
|
|
50
112
|
async list(opts) {
|
|
51
|
-
const type = opts?.type?.trim();
|
|
52
|
-
const
|
|
53
|
-
const raw = await this.client.get(`/public/api/connections/get-names/${query}`);
|
|
113
|
+
const type = opts?.type?.trim() || "all";
|
|
114
|
+
const raw = await this.client.get(`/public/api/connections/get-names/?type=${encodeURIComponent(type)}`);
|
|
54
115
|
return normalizeConnectionNames(raw);
|
|
55
116
|
}
|
|
56
117
|
/**
|
|
@@ -58,14 +119,14 @@ export class ConnectionsResource extends BaseResource {
|
|
|
58
119
|
*
|
|
59
120
|
* - fast (default): fetches the connection name list and maps to ConnectionSummary.
|
|
60
121
|
* Falls back to rich mode on any failure or empty result set.
|
|
61
|
-
* - rich: inspects project datasets to derive connection metadata
|
|
122
|
+
* - rich: inspects project datasets and managed folders to derive connection metadata
|
|
62
123
|
* (types, managed flag, db schemas).
|
|
63
124
|
*/
|
|
64
125
|
async infer(opts) {
|
|
65
126
|
const mode = opts?.mode ?? "fast";
|
|
66
127
|
const projectEnc = this.enc(opts?.projectKey);
|
|
67
128
|
if (mode === "rich") {
|
|
68
|
-
return
|
|
129
|
+
return inferRichConnections(this.client, projectEnc);
|
|
69
130
|
}
|
|
70
131
|
// fast — attempt name list, fall back to rich on any error or empty result
|
|
71
132
|
try {
|
|
@@ -77,7 +138,7 @@ export class ConnectionsResource extends BaseResource {
|
|
|
77
138
|
catch {
|
|
78
139
|
// Fall through to rich inference.
|
|
79
140
|
}
|
|
80
|
-
return
|
|
141
|
+
return inferRichConnections(this.client, projectEnc);
|
|
81
142
|
}
|
|
82
143
|
async schemas(opts) {
|
|
83
144
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
@@ -2,18 +2,32 @@ import type { DashboardDetails, DashboardSummary } from "../schemas.js";
|
|
|
2
2
|
import { BaseResource } from "./base.js";
|
|
3
3
|
export interface DashboardCreateOptions {
|
|
4
4
|
name: string;
|
|
5
|
+
listed?: boolean;
|
|
5
6
|
settings?: Record<string, unknown>;
|
|
6
7
|
projectKey?: string;
|
|
7
8
|
}
|
|
8
9
|
export interface DashboardUpdateOptions {
|
|
9
10
|
name?: string;
|
|
11
|
+
listed?: boolean;
|
|
10
12
|
data?: Record<string, unknown>;
|
|
11
13
|
projectKey?: string;
|
|
12
14
|
}
|
|
15
|
+
export type DashboardExportOrientation = "PORTRAIT" | "LANDSCAPE";
|
|
16
|
+
export type DashboardExportFileType = "PDF";
|
|
17
|
+
export interface DashboardExportOptions {
|
|
18
|
+
paperSize?: string;
|
|
19
|
+
orientation?: DashboardExportOrientation;
|
|
20
|
+
fileType?: DashboardExportFileType;
|
|
21
|
+
slideIndex?: number;
|
|
22
|
+
projectKey?: string;
|
|
23
|
+
}
|
|
13
24
|
export declare class DashboardsResource extends BaseResource {
|
|
14
25
|
list(projectKey?: string): Promise<DashboardSummary[]>;
|
|
15
26
|
get(dashboardId: string, projectKey?: string): Promise<DashboardDetails>;
|
|
27
|
+
private referencedInsightIds;
|
|
28
|
+
private validateReferences;
|
|
16
29
|
create(opts: DashboardCreateOptions): Promise<DashboardDetails>;
|
|
17
30
|
update(dashboardId: string, opts: DashboardUpdateOptions): Promise<DashboardDetails>;
|
|
31
|
+
export(dashboardId: string, opts?: DashboardExportOptions): Promise<Response>;
|
|
18
32
|
delete(dashboardId: string, projectKey?: string): Promise<void>;
|
|
19
33
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
|
+
import { DashboardDetailsSchema, DashboardSummaryArraySchema, InsightDetailsSchema, } from "../schemas.js";
|
|
2
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
3
4
|
import { BaseResource, } from "./base.js";
|
|
5
|
+
const REFERENCE_VALIDATION_CONCURRENCY = 8;
|
|
6
|
+
async function validateInBatches(items, validate) {
|
|
7
|
+
for (let offset = 0; offset < items.length; offset += REFERENCE_VALIDATION_CONCURRENCY) {
|
|
8
|
+
const batch = items.slice(offset, offset + REFERENCE_VALIDATION_CONCURRENCY);
|
|
9
|
+
const results = await Promise.allSettled(batch.map(validate));
|
|
10
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
11
|
+
if (failure)
|
|
12
|
+
throw failure.reason;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
export class DashboardsResource extends BaseResource {
|
|
5
16
|
async list(projectKey) {
|
|
6
17
|
const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/dashboards/`);
|
|
@@ -10,19 +21,160 @@ export class DashboardsResource extends BaseResource {
|
|
|
10
21
|
const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/dashboards/${encodeURIComponent(dashboardId)}/`);
|
|
11
22
|
return this.client.safeParse(DashboardDetailsSchema, raw, "dashboards.get");
|
|
12
23
|
}
|
|
24
|
+
referencedInsightIds(settings) {
|
|
25
|
+
const pages = settings.pages;
|
|
26
|
+
if (pages === undefined)
|
|
27
|
+
return [];
|
|
28
|
+
if (!Array.isArray(pages)) {
|
|
29
|
+
throw new ClientValidationError("Dashboard pages must be an array.");
|
|
30
|
+
}
|
|
31
|
+
const insightIds = new Set();
|
|
32
|
+
for (const [pageIndex, page,] of pages.entries()) {
|
|
33
|
+
if (!page || typeof page !== "object" || Array.isArray(page)) {
|
|
34
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} must be an object.`);
|
|
35
|
+
}
|
|
36
|
+
const grid = page.grid;
|
|
37
|
+
if (grid === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
if (!grid || typeof grid !== "object" || Array.isArray(grid)) {
|
|
40
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} grid must be an object.`);
|
|
41
|
+
}
|
|
42
|
+
const tiles = grid.tiles;
|
|
43
|
+
if (tiles === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (!Array.isArray(tiles)) {
|
|
46
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} grid tiles must be an array.`);
|
|
47
|
+
}
|
|
48
|
+
for (const [tileIndex, tile,] of tiles.entries()) {
|
|
49
|
+
if (!tile || typeof tile !== "object" || Array.isArray(tile)) {
|
|
50
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} must be an object.`);
|
|
51
|
+
}
|
|
52
|
+
const record = tile;
|
|
53
|
+
const insightId = record.insightId ?? undefined;
|
|
54
|
+
const targetInsightId = record.targetInsightId ?? undefined;
|
|
55
|
+
const isInsight = record.tileType === "INSIGHT"
|
|
56
|
+
|| insightId !== undefined
|
|
57
|
+
|| targetInsightId !== undefined;
|
|
58
|
+
if (!isInsight)
|
|
59
|
+
continue;
|
|
60
|
+
if (insightId !== undefined
|
|
61
|
+
&& (typeof insightId !== "string" || insightId.trim().length === 0)) {
|
|
62
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} insightId must be a non-empty string.`);
|
|
63
|
+
}
|
|
64
|
+
if (targetInsightId !== undefined
|
|
65
|
+
&& (typeof targetInsightId !== "string" || targetInsightId.trim().length === 0)) {
|
|
66
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} targetInsightId must be a non-empty string.`);
|
|
67
|
+
}
|
|
68
|
+
const normalizedInsightId = typeof insightId === "string" ? insightId.trim() : undefined;
|
|
69
|
+
const normalizedTargetId = typeof targetInsightId === "string"
|
|
70
|
+
? targetInsightId.trim()
|
|
71
|
+
: undefined;
|
|
72
|
+
if (normalizedInsightId !== undefined
|
|
73
|
+
&& normalizedTargetId !== undefined
|
|
74
|
+
&& normalizedInsightId !== normalizedTargetId) {
|
|
75
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} has mismatched insightId and targetInsightId.`);
|
|
76
|
+
}
|
|
77
|
+
const reference = normalizedInsightId ?? normalizedTargetId;
|
|
78
|
+
if (!reference) {
|
|
79
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} requires insightId or targetInsightId.`);
|
|
80
|
+
}
|
|
81
|
+
insightIds.add(reference);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...insightIds,];
|
|
85
|
+
}
|
|
86
|
+
async validateReferences(settings, projectKey) {
|
|
87
|
+
const insightIds = this.referencedInsightIds(settings);
|
|
88
|
+
const datasetNames = new Set();
|
|
89
|
+
await validateInBatches(insightIds, async (insightId) => {
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = await this.client.get(`/public/api/projects/${encodeURIComponent(projectKey)}/insights/${encodeURIComponent(insightId)}/`);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof DataikuError && error.status === 404) {
|
|
96
|
+
throw new ClientValidationError(`Dashboard references missing insight ${insightId}.`, "validation_failed", "Create or restore the insight, then update the tile reference before saving the dashboard.", { insightId, });
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const insight = this.client.safeParse(InsightDetailsSchema, raw, `dashboards.validateReferences.insight.${insightId}`);
|
|
101
|
+
const params = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
102
|
+
? raw.params
|
|
103
|
+
: undefined;
|
|
104
|
+
const datasetSmartName = params && typeof params === "object" && !Array.isArray(params)
|
|
105
|
+
? params.datasetSmartName
|
|
106
|
+
: undefined;
|
|
107
|
+
if (insight.type === "dataset_table" && (typeof datasetSmartName !== "string" || datasetSmartName.trim().length === 0)) {
|
|
108
|
+
throw new ClientValidationError(`Dashboard insight ${insightId} does not name a backing dataset.`, "validation_failed", "Retarget the dataset-table insight before saving the dashboard.", { insightId, });
|
|
109
|
+
}
|
|
110
|
+
if (typeof datasetSmartName === "string" && datasetSmartName.trim().length > 0) {
|
|
111
|
+
datasetNames.add(datasetSmartName.trim());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
await validateInBatches([...datasetNames,], async (datasetSmartName) => {
|
|
115
|
+
const separatorIndex = datasetSmartName.indexOf(".");
|
|
116
|
+
if (separatorIndex === 0 || separatorIndex === datasetSmartName.length - 1) {
|
|
117
|
+
throw new ClientValidationError(`Dashboard insight has invalid dataset smart name ${datasetSmartName}.`, "validation_failed", "Use DATASET for the current project or PROJECT.DATASET for a cross-project dataset.", { datasetSmartName, });
|
|
118
|
+
}
|
|
119
|
+
const datasetProjectKey = separatorIndex > 0
|
|
120
|
+
? datasetSmartName.slice(0, separatorIndex)
|
|
121
|
+
: projectKey;
|
|
122
|
+
const datasetName = separatorIndex > 0
|
|
123
|
+
? datasetSmartName.slice(separatorIndex + 1)
|
|
124
|
+
: datasetSmartName;
|
|
125
|
+
try {
|
|
126
|
+
await this.client.get(`/public/api/projects/${encodeURIComponent(datasetProjectKey)}/datasets/${encodeURIComponent(datasetName)}/`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof DataikuError && error.status === 403) {
|
|
130
|
+
throw new ClientValidationError(`Dashboard cannot validate inaccessible dataset ${datasetSmartName}.`, "validation_failed", "Grant the API key read access to the dataset project before saving the dashboard.", { datasetSmartName, datasetProjectKey, datasetName, });
|
|
131
|
+
}
|
|
132
|
+
if (error instanceof DataikuError && error.status === 404) {
|
|
133
|
+
throw new ClientValidationError(`Dashboard insight references missing dataset ${datasetSmartName}.`, "validation_failed", "Create or restore the dataset, or retarget the insight before saving the dashboard.", { datasetSmartName, datasetProjectKey, datasetName, });
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
13
139
|
async create(opts) {
|
|
14
140
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
15
141
|
const body = { ...(opts.settings ?? { pages: [], }), name: opts.name, };
|
|
142
|
+
if (opts.listed !== undefined)
|
|
143
|
+
body.listed = opts.listed;
|
|
144
|
+
await this.validateReferences(body, pk);
|
|
16
145
|
const created = await this.client.post(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/`, body);
|
|
17
146
|
return this.get(created.id, pk);
|
|
18
147
|
}
|
|
19
148
|
async update(dashboardId, opts) {
|
|
20
|
-
const
|
|
149
|
+
const pk = this.resolveProjectKey(opts.projectKey);
|
|
150
|
+
const current = await this.get(dashboardId, pk);
|
|
21
151
|
const next = deepMerge(current, opts.data ?? {});
|
|
22
152
|
if (opts.name !== undefined)
|
|
23
153
|
next.name = opts.name;
|
|
24
|
-
|
|
25
|
-
|
|
154
|
+
if (opts.listed !== undefined)
|
|
155
|
+
next.listed = opts.listed;
|
|
156
|
+
await this.validateReferences(next, pk);
|
|
157
|
+
await this.client.put(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/${encodeURIComponent(dashboardId)}/`, next);
|
|
158
|
+
return this.get(dashboardId, pk);
|
|
159
|
+
}
|
|
160
|
+
async export(dashboardId, opts = {}) {
|
|
161
|
+
const pk = this.resolveProjectKey(opts.projectKey);
|
|
162
|
+
const paperSize = (opts.paperSize ?? "A4").trim();
|
|
163
|
+
if (!paperSize)
|
|
164
|
+
throw new ClientValidationError("Dashboard export paperSize must not be empty.");
|
|
165
|
+
const orientation = (opts.orientation ?? "LANDSCAPE").trim().toUpperCase();
|
|
166
|
+
if (orientation !== "PORTRAIT" && orientation !== "LANDSCAPE") {
|
|
167
|
+
throw new ClientValidationError("Dashboard export orientation must be PORTRAIT or LANDSCAPE.");
|
|
168
|
+
}
|
|
169
|
+
const fileType = (opts.fileType ?? "PDF").trim().toUpperCase();
|
|
170
|
+
if (fileType !== "PDF") {
|
|
171
|
+
throw new ClientValidationError("Dashboard export fileType must be PDF.");
|
|
172
|
+
}
|
|
173
|
+
const slideIndex = opts.slideIndex ?? 0;
|
|
174
|
+
if (!Number.isInteger(slideIndex) || slideIndex < 0) {
|
|
175
|
+
throw new ClientValidationError("Dashboard export slideIndex must be a non-negative integer.");
|
|
176
|
+
}
|
|
177
|
+
return this.client.postStream(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/${encodeURIComponent(dashboardId)}/action/export`, { paperSize, orientation, fileType, slideIndex, });
|
|
26
178
|
}
|
|
27
179
|
async delete(dashboardId, projectKey) {
|
|
28
180
|
await this.client.del(`/public/api/projects/${this.enc(projectKey)}/dashboards/${encodeURIComponent(dashboardId)}/`);
|
|
@@ -3,11 +3,12 @@ import * as nodePath from "node:path";
|
|
|
3
3
|
import { Readable, Transform, } from "node:stream";
|
|
4
4
|
import { pipeline, } from "node:stream/promises";
|
|
5
5
|
import { createGzip, } from "node:zlib";
|
|
6
|
-
import { DataikuError, } from "../errors.js";
|
|
6
|
+
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
7
7
|
import { DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, } from "../schemas.js";
|
|
8
8
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
9
9
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
10
10
|
import { BaseResource, } from "./base.js";
|
|
11
|
+
import { resolveAdminManagedStorageConnection, } from "./connections.js";
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// Helpers: TSV → CSV streaming conversion
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
@@ -253,15 +254,25 @@ function shouldRetryWithConnectionInferredType(error) {
|
|
|
253
254
|
|| detail.includes("invalid type")
|
|
254
255
|
|| detail.includes("illegal argument"));
|
|
255
256
|
}
|
|
257
|
+
function isMissingUploadedFilesTargetConnection(error) {
|
|
258
|
+
if (!(error instanceof DataikuError))
|
|
259
|
+
return false;
|
|
260
|
+
const detail = error.body.toLowerCase();
|
|
261
|
+
return (detail.includes("without a target connection")
|
|
262
|
+
|| detail.includes("target connection is required"));
|
|
263
|
+
}
|
|
256
264
|
function buildDatasetCreateBody(opts) {
|
|
257
265
|
if (opts.dsType.toLowerCase() === "uploadedfiles") {
|
|
258
266
|
return {
|
|
259
267
|
projectKey: opts.projectKey,
|
|
260
268
|
name: opts.datasetName,
|
|
261
269
|
type: opts.dsType,
|
|
262
|
-
params: { uploadConnection: opts.connection, },
|
|
270
|
+
params: opts.connection ? { uploadConnection: opts.connection, } : {},
|
|
263
271
|
};
|
|
264
272
|
}
|
|
273
|
+
if (!opts.connection) {
|
|
274
|
+
throw new ClientValidationError("connection is required unless dsType is UploadedFiles.");
|
|
275
|
+
}
|
|
265
276
|
if (opts.table) {
|
|
266
277
|
const params = {
|
|
267
278
|
connection: opts.connection,
|
|
@@ -481,6 +492,35 @@ export class DatasetsResource extends BaseResource {
|
|
|
481
492
|
return await this.client.post(`/public/api/projects/${enc}/datasets/`, body);
|
|
482
493
|
}
|
|
483
494
|
catch (error) {
|
|
495
|
+
if (explicitType?.toLowerCase() === "uploadedfiles"
|
|
496
|
+
&& !opts.connection
|
|
497
|
+
&& isMissingUploadedFilesTargetConnection(error)) {
|
|
498
|
+
let uploadConnection = await resolveAdminManagedStorageConnection(this.client, "allowManagedDatasets", "filesystem_managed");
|
|
499
|
+
if (!uploadConnection) {
|
|
500
|
+
const existing = await this.list(pk);
|
|
501
|
+
uploadConnection = [
|
|
502
|
+
...new Set(existing
|
|
503
|
+
.filter((dataset) => dataset.managed === true)
|
|
504
|
+
.map((dataset) => dataset.params?.connection)
|
|
505
|
+
.filter((connection) => typeof connection === "string" && connection.length > 0)),
|
|
506
|
+
].sort()[0];
|
|
507
|
+
}
|
|
508
|
+
if (!uploadConnection)
|
|
509
|
+
throw error;
|
|
510
|
+
body = buildDatasetCreateBody({
|
|
511
|
+
projectKey: pk,
|
|
512
|
+
datasetName: opts.datasetName,
|
|
513
|
+
connection: uploadConnection,
|
|
514
|
+
dsType,
|
|
515
|
+
table: opts.table,
|
|
516
|
+
dbSchema: opts.dbSchema,
|
|
517
|
+
catalog: opts.catalog,
|
|
518
|
+
formatType: opts.formatType,
|
|
519
|
+
formatParams: opts.formatParams,
|
|
520
|
+
managed: opts.managed,
|
|
521
|
+
});
|
|
522
|
+
return this.client.post(`/public/api/projects/${enc}/datasets/`, body);
|
|
523
|
+
}
|
|
484
524
|
if (explicitType || !shouldRetryWithConnectionInferredType(error)) {
|
|
485
525
|
throw error;
|
|
486
526
|
}
|
|
@@ -6,6 +6,7 @@ import { FolderDetailsSchema, FolderItemArraySchema, FolderSummaryArraySchema, }
|
|
|
6
6
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
7
7
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
8
8
|
import { BaseResource, } from "./base.js";
|
|
9
|
+
import { resolveAdminManagedStorageConnection, } from "./connections.js";
|
|
9
10
|
function normalizeRemotePath(path) {
|
|
10
11
|
return path.replace(/\\/g, "/");
|
|
11
12
|
}
|
|
@@ -20,14 +21,17 @@ function inferDownloadFileName(remotePath) {
|
|
|
20
21
|
export class FoldersResource extends BaseResource {
|
|
21
22
|
async create(opts) {
|
|
22
23
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
23
|
-
const path = opts.path?.trim() ||
|
|
24
|
+
const path = opts.path?.trim() || "/${projectKey}/${odbId}";
|
|
25
|
+
const connection = opts.connection
|
|
26
|
+
?? await resolveAdminManagedStorageConnection(this.client, "allowManagedFolders", "filesystem_folders")
|
|
27
|
+
?? "filesystem_folders";
|
|
24
28
|
const raw = await this.client.post(`/public/api/projects/${encodeURIComponent(pk)}/managedfolders/`, {
|
|
25
29
|
name: opts.name,
|
|
26
30
|
projectKey: pk,
|
|
27
|
-
type: opts.type,
|
|
31
|
+
type: opts.type ?? null,
|
|
28
32
|
params: {
|
|
29
33
|
...opts.params,
|
|
30
|
-
connection:
|
|
34
|
+
connection: connection,
|
|
31
35
|
path,
|
|
32
36
|
},
|
|
33
37
|
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { ProjectGitActionResult, ProjectGitDiffResult, ProjectGitFutureResponse, ProjectGitFutureState, ProjectGitLibraries, ProjectGitLogResult, ProjectGitRemote, ProjectGitStatus, ProjectGitTags } from "../schemas.js";
|
|
2
|
+
import { BaseResource } from "./base.js";
|
|
3
|
+
export interface ProjectGitSetRemoteOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProjectGitCreateBranchOptions {
|
|
8
|
+
commit?: string;
|
|
9
|
+
duplicateProject?: boolean;
|
|
10
|
+
targetProjectKey?: string;
|
|
11
|
+
targetProjectFolderId?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ProjectGitDeleteBranchOptions {
|
|
14
|
+
remote?: boolean;
|
|
15
|
+
deleteRemotely?: boolean;
|
|
16
|
+
forceDelete?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ProjectGitCreateTagOptions {
|
|
19
|
+
reference?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ProjectGitPullOptions {
|
|
23
|
+
branchName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ProjectGitPushOptions {
|
|
26
|
+
branchName?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ProjectGitLogOptions {
|
|
29
|
+
path?: string;
|
|
30
|
+
startCommit?: string;
|
|
31
|
+
count?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ProjectGitDiffOptions {
|
|
34
|
+
commitFrom?: string;
|
|
35
|
+
commitTo?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ProjectGitCommitOptions {
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `dropAndRebuild` permanently destroys the project's Git history, so the
|
|
42
|
+
* caller must acknowledge it explicitly before the request is issued.
|
|
43
|
+
*/
|
|
44
|
+
export interface ProjectGitDropAndRebuildOptions {
|
|
45
|
+
confirmed?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface ProjectGitAddLibraryOptions {
|
|
48
|
+
repository: string;
|
|
49
|
+
localTargetPath: string;
|
|
50
|
+
checkout: string;
|
|
51
|
+
pathInGitRepository?: string;
|
|
52
|
+
addToPythonPath?: boolean;
|
|
53
|
+
login?: string;
|
|
54
|
+
password?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ProjectGitSetLibraryOptions {
|
|
57
|
+
repository: string;
|
|
58
|
+
pathInGitRepository?: string;
|
|
59
|
+
checkout: string;
|
|
60
|
+
login?: string;
|
|
61
|
+
password?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ProjectGitFutureStateOptions {
|
|
64
|
+
peek?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface ProjectGitFutureWaitOptions {
|
|
67
|
+
pollIntervalMs?: number;
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
}
|
|
70
|
+
export declare class ProjectGitResource extends BaseResource {
|
|
71
|
+
/** Working-copy status: current branch, remotes, tracking counts, changes. */
|
|
72
|
+
status(projectKey: string): Promise<ProjectGitStatus>;
|
|
73
|
+
/**
|
|
74
|
+
* Read a Git remote. DSS answers `{}` when the remote is not configured.
|
|
75
|
+
* The remote name defaults to `origin` like the Python client.
|
|
76
|
+
*/
|
|
77
|
+
getRemote(projectKey: string, name?: string): Promise<ProjectGitRemote>;
|
|
78
|
+
/** Create or replace a Git remote. */
|
|
79
|
+
setRemote(projectKey: string, options: ProjectGitSetRemoteOptions): Promise<ProjectGitActionResult>;
|
|
80
|
+
/** Delete a Git remote. */
|
|
81
|
+
removeRemote(projectKey: string, name?: string): Promise<void>;
|
|
82
|
+
/** Plain list of branch names, local by default. */
|
|
83
|
+
listBranches(projectKey: string, remote?: boolean): Promise<string[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Create a branch and switch to it. With `duplicateProject`, DSS creates a
|
|
86
|
+
* duplicate project for the branch instead of moving this one.
|
|
87
|
+
*/
|
|
88
|
+
createBranch(projectKey: string, name: string, options?: ProjectGitCreateBranchOptions): Promise<ProjectGitActionResult>;
|
|
89
|
+
/** Delete a branch locally and, with `deleteRemotely`, on the remote too. */
|
|
90
|
+
deleteBranch(projectKey: string, name: string, options?: ProjectGitDeleteBranchOptions): Promise<ProjectGitActionResult>;
|
|
91
|
+
/** Current branch name, or null when DSS reports none. */
|
|
92
|
+
currentBranch(projectKey: string): Promise<string | null>;
|
|
93
|
+
/** Switch the project to another branch. */
|
|
94
|
+
switchBranch(projectKey: string, branchName: string): Promise<ProjectGitActionResult>;
|
|
95
|
+
listTags(projectKey: string): Promise<ProjectGitTags>;
|
|
96
|
+
createTag(projectKey: string, name: string, options?: ProjectGitCreateTagOptions): Promise<ProjectGitActionResult>;
|
|
97
|
+
deleteTag(projectKey: string, name: string): Promise<ProjectGitActionResult>;
|
|
98
|
+
/** Fetch from the remote. Failures arrive as `{success: false}` with HTTP 200. */
|
|
99
|
+
fetch(projectKey: string): Promise<ProjectGitActionResult>;
|
|
100
|
+
/** Pull. DSS only ever exposes a rebase pull; there is no merge variant. */
|
|
101
|
+
pull(projectKey: string, options?: ProjectGitPullOptions): Promise<ProjectGitActionResult>;
|
|
102
|
+
push(projectKey: string, options?: ProjectGitPushOptions): Promise<ProjectGitActionResult>;
|
|
103
|
+
/** Commit log page. Paginate by passing the previous `nextCommit` as `startCommit`. */
|
|
104
|
+
log(projectKey: string, options?: ProjectGitLogOptions): Promise<ProjectGitLogResult>;
|
|
105
|
+
diff(projectKey: string, options?: ProjectGitDiffOptions): Promise<ProjectGitDiffResult>;
|
|
106
|
+
/** Commit the project. DSS stages untracked files before committing. */
|
|
107
|
+
commit(projectKey: string, options: ProjectGitCommitOptions): Promise<ProjectGitActionResult>;
|
|
108
|
+
/** Restore the project to the state of a past commit. */
|
|
109
|
+
revertToRevision(projectKey: string, commit: string): Promise<ProjectGitActionResult>;
|
|
110
|
+
/** Revert one commit, keeping later history. */
|
|
111
|
+
revertCommit(projectKey: string, commit: string): Promise<ProjectGitActionResult>;
|
|
112
|
+
/** Hard reset to local HEAD, dropping uncommitted changes. */
|
|
113
|
+
resetToHead(projectKey: string): Promise<ProjectGitActionResult>;
|
|
114
|
+
/** Hard reset to the tracked upstream branch, dropping local commits. */
|
|
115
|
+
resetToUpstream(projectKey: string): Promise<ProjectGitActionResult>;
|
|
116
|
+
/**
|
|
117
|
+
* Wipe the project's entire Git history and rebuild a fresh repository.
|
|
118
|
+
* Irreversible, so nothing is sent unless the caller acknowledges it.
|
|
119
|
+
*/
|
|
120
|
+
dropAndRebuild(projectKey: string, options?: ProjectGitDropAndRebuildOptions): Promise<ProjectGitActionResult>;
|
|
121
|
+
listLibraries(projectKey: string): Promise<ProjectGitLibraries>;
|
|
122
|
+
/** Attach an external Git library. Runs as a DSS future. */
|
|
123
|
+
addLibrary(projectKey: string, options: ProjectGitAddLibraryOptions): Promise<ProjectGitFutureResponse>;
|
|
124
|
+
/** Update an attached library's repository, sub-path, or checkout. */
|
|
125
|
+
setLibrary(projectKey: string, gitReferencePath: string, options: ProjectGitSetLibraryOptions): Promise<string>;
|
|
126
|
+
/** Detach a library, optionally deleting its local directory. */
|
|
127
|
+
removeLibrary(projectKey: string, gitReferencePath: string, deleteDirectory?: boolean): Promise<void>;
|
|
128
|
+
/** Reset one library to its remote state. Runs as a DSS future. */
|
|
129
|
+
resetLibrary(projectKey: string, gitReferencePath: string): Promise<ProjectGitFutureResponse>;
|
|
130
|
+
/** Commit and push one library. Runs as a DSS future. */
|
|
131
|
+
pushLibrary(projectKey: string, gitReferencePath: string, commitMessage: string): Promise<ProjectGitFutureResponse>;
|
|
132
|
+
/** Commit and push every attached library. Runs as a DSS future. */
|
|
133
|
+
pushAllLibraries(projectKey: string, commitMessage: string): Promise<ProjectGitFutureResponse>;
|
|
134
|
+
/** Reset every attached library to its remote state. Runs as a DSS future. */
|
|
135
|
+
resetAllLibraries(projectKey: string): Promise<ProjectGitFutureResponse>;
|
|
136
|
+
/**
|
|
137
|
+
* Read a future's state. `peek` leaves the future's log unconsumed, which is
|
|
138
|
+
* what polling loops want; the default consumes it like the Python client.
|
|
139
|
+
*/
|
|
140
|
+
getFutureState(jobId: string, options?: ProjectGitFutureStateOptions): Promise<ProjectGitFutureState>;
|
|
141
|
+
/** Abort a running future. */
|
|
142
|
+
abortFuture(jobId: string): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Poll until the future carries a result and return that result. Throws on
|
|
145
|
+
* an aborted or failed future and on an exhausted budget, so a caller never
|
|
146
|
+
* mistakes an unfinished future for a successful one.
|
|
147
|
+
*/
|
|
148
|
+
waitForFuture(jobId: string, options?: ProjectGitFutureWaitOptions): Promise<unknown>;
|
|
149
|
+
private gitPath;
|
|
150
|
+
private actionPath;
|
|
151
|
+
private libraryPath;
|
|
152
|
+
private query;
|
|
153
|
+
private request;
|
|
154
|
+
}
|