@lightdash/cli 1.48.0 → 1.49.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/dist/.tsbuildinfo +1 -1
- package/dist/handlers/contentAsCode/resource.d.ts +3 -1
- package/dist/handlers/contentAsCode/resource.d.ts.map +1 -1
- package/dist/handlers/contentAsCode/resource.js +9 -4
- package/dist/handlers/download.d.ts +6 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +61 -34
- package/dist/handlers/download.test.js +223 -1
- package/dist/handlers/organizationContent/customRoles.d.ts +1 -0
- package/dist/handlers/organizationContent/customRoles.d.ts.map +1 -1
- package/dist/handlers/organizationContent/customRoles.js +11 -1
- package/dist/handlers/organizationContent/customRoles.test.js +18 -0
- package/dist/handlers/organizationContent/index.d.ts.map +1 -1
- package/dist/handlers/organizationContent/index.js +4 -1
- package/package.json +3 -3
|
@@ -36,6 +36,7 @@ export type CodeResourceUploadSummary = {
|
|
|
36
36
|
created: number;
|
|
37
37
|
updated: number;
|
|
38
38
|
unchanged: number;
|
|
39
|
+
skipped?: number;
|
|
39
40
|
failed: number;
|
|
40
41
|
failures: CodeFileFailure[];
|
|
41
42
|
};
|
|
@@ -59,10 +60,11 @@ export declare const downloadCodeResource: <Document>({ definition, basePath, li
|
|
|
59
60
|
basePath: string;
|
|
60
61
|
list: () => Promise<Document[]>;
|
|
61
62
|
}) => Promise<number>;
|
|
62
|
-
export declare const uploadCodeResource: <Document>({ definition, basePath, upsert, }: {
|
|
63
|
+
export declare const uploadCodeResource: <Document>({ definition, basePath, upsert, skipError, }: {
|
|
63
64
|
definition: CodeResourceDefinition<Document>;
|
|
64
65
|
basePath: string;
|
|
65
66
|
upsert: (document: Document) => Promise<ContentAsCodeUpsertAction>;
|
|
67
|
+
skipError?: (error: unknown) => boolean;
|
|
66
68
|
}) => Promise<CodeResourceUploadSummary>;
|
|
67
69
|
export declare const assertCodeResourceFilesValid: <Document>(result: CodeResourceFiles<Document>) => void;
|
|
68
70
|
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/resource.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/resource.ts"],"names":[],"mappings":"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"}
|
|
@@ -144,7 +144,7 @@ const getActionCountKey = (action) => {
|
|
|
144
144
|
return (0, common_1.assertUnreachable)(action, 'Unsupported content-as-code action');
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
|
-
const uploadCodeResource = async ({ definition, basePath, upsert, }) => {
|
|
147
|
+
const uploadCodeResource = async ({ definition, basePath, upsert, skipError, }) => {
|
|
148
148
|
const { files, failures } = await (0, exports.readCodeResourceFiles)({
|
|
149
149
|
definition,
|
|
150
150
|
basePath,
|
|
@@ -161,9 +161,14 @@ const uploadCodeResource = async ({ definition, basePath, upsert, }) => {
|
|
|
161
161
|
summary[getActionCountKey(await upsert(document))] += 1;
|
|
162
162
|
}
|
|
163
163
|
catch (error) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
164
|
+
if (skipError?.(error)) {
|
|
165
|
+
summary.skipped = (summary.skipped ?? 0) + 1;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
summary.failures.push({
|
|
169
|
+
message: `Invalid ${definition.displayLabel} file "${filePath}": ${(0, common_1.getErrorMessage)(error)}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
summary.failed = summary.failures.length;
|
|
@@ -70,9 +70,12 @@ export declare const getExternalConnectionSecretEnvVar: (slug: string) => string
|
|
|
70
70
|
* no EE coder service provider (MissingConfigError).
|
|
71
71
|
*/
|
|
72
72
|
declare const isExternalConnectionsUnavailableError: (error: unknown) => boolean;
|
|
73
|
+
declare const isAiAgentsUnavailableError: (error: unknown) => boolean;
|
|
73
74
|
declare const readExternalConnectionFiles: (customPath?: string) => Promise<ExternalConnectionAsCode[]>;
|
|
74
75
|
declare const upsertExternalConnections: (projectId: string, slugs: string[], changes: Record<string, number>, force: boolean, canUpload: boolean, customPath?: string) => Promise<Record<string, number>>;
|
|
76
|
+
declare const downloadAiAgents: (projectId: string, ids: string[], implicit: boolean, customPath?: string) => Promise<number>;
|
|
75
77
|
declare const readAiAgentFiles: (customPath?: string) => Promise<AgentAsCode[]>;
|
|
78
|
+
declare const upsertAiAgents: (projectId: string, slugs: string[], changes: Record<string, number>, force: boolean, customPath?: string, implicit?: boolean) => Promise<Record<string, number>>;
|
|
76
79
|
export declare const downloadHandler: (options: DownloadHandlerOptions) => Promise<void>;
|
|
77
80
|
declare const summarizeUploadChanges: (before: Record<string, number>, after: Record<string, number>) => {
|
|
78
81
|
detail: string;
|
|
@@ -88,7 +91,9 @@ export declare const testHelpers: {
|
|
|
88
91
|
getFlatSpaceFileNames: typeof getFlatSpaceFileNames;
|
|
89
92
|
getDashboardChartSlugs: typeof getDashboardChartSlugs;
|
|
90
93
|
hasContentFilters: typeof hasContentFilters;
|
|
94
|
+
isAiAgentsUnavailableError: typeof isAiAgentsUnavailableError;
|
|
91
95
|
isExternalConnectionsUnavailableError: typeof isExternalConnectionsUnavailableError;
|
|
96
|
+
downloadAiAgents: typeof downloadAiAgents;
|
|
92
97
|
readAiAgentFiles: typeof readAiAgentFiles;
|
|
93
98
|
readExternalConnectionFiles: typeof readExternalConnectionFiles;
|
|
94
99
|
readSpaceFiles: typeof readSpaceFiles;
|
|
@@ -98,6 +103,7 @@ export declare const testHelpers: {
|
|
|
98
103
|
shouldDownloadAiAgents: typeof shouldDownloadAiAgents;
|
|
99
104
|
sortSpaceFilesParentFirst: typeof sortSpaceFilesParentFirst;
|
|
100
105
|
summarizeUploadChanges: typeof summarizeUploadChanges;
|
|
106
|
+
upsertAiAgents: typeof upsertAiAgents;
|
|
101
107
|
upsertExternalConnections: typeof upsertExternalConnections;
|
|
102
108
|
upsertSpaces: typeof upsertSpaces;
|
|
103
109
|
upsertVirtualViews: typeof upsertVirtualViews;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,WAAW,EAyBX,WAAW,EAGX,eAAe,EACf,wBAAwB,EAexB,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAIH,KAAK,0BAA0B,EAClC,MAAM,iCAAiC,CAAC;AAkEzC,OAAO,EACH,sBAAsB,EAGtB,cAAc,EACd,qBAAqB,
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"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"}
|
|
@@ -521,6 +521,8 @@ const EXTERNAL_CONNECTION_SECRET_TYPES = new Set([
|
|
|
521
521
|
*/
|
|
522
522
|
const isExternalConnectionsUnavailableError = (error) => error instanceof common_1.LightdashError &&
|
|
523
523
|
[403, 404, 422].includes(error.statusCode);
|
|
524
|
+
const isAiAgentsUnavailableError = (error) => error instanceof common_1.LightdashError &&
|
|
525
|
+
[403, 404, 422].includes(error.statusCode);
|
|
524
526
|
const downloadExternalConnections = async (projectId, slugs, implicit, customPath) => {
|
|
525
527
|
const slugQuery = slugs.map((slug) => ['slugs', slug]);
|
|
526
528
|
let offset = 0;
|
|
@@ -656,34 +658,44 @@ const getScheduledContentConfig = (contentType, customPath) => {
|
|
|
656
658
|
return (0, common_1.assertUnreachable)(contentType, 'Unknown scheduled content type');
|
|
657
659
|
}
|
|
658
660
|
};
|
|
659
|
-
const downloadAiAgents = async (projectId, ids, customPath) => {
|
|
661
|
+
const downloadAiAgents = async (projectId, ids, implicit, customPath) => {
|
|
660
662
|
const idQuery = ids.map((id) => ['ids', id]);
|
|
661
663
|
let offset = 0;
|
|
662
664
|
let total = 0;
|
|
663
665
|
let downloaded = 0;
|
|
664
666
|
const agents = [];
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
667
|
+
try {
|
|
668
|
+
do {
|
|
669
|
+
const query = new URLSearchParams([
|
|
670
|
+
...idQuery,
|
|
671
|
+
['offset', String(offset)],
|
|
672
|
+
]).toString();
|
|
673
|
+
const results = await (0, apiClient_1.lightdashApi)({
|
|
674
|
+
method: 'GET',
|
|
675
|
+
url: `/api/v1/projects/${projectId}/code/aiAgents?${query}`,
|
|
676
|
+
body: undefined,
|
|
677
|
+
});
|
|
678
|
+
agents.push(...results.agents);
|
|
679
|
+
results.missingIds.forEach((id) => globalState_1.default.debug(`No AI agent with id "${id}"`));
|
|
680
|
+
downloaded += results.agents.length;
|
|
681
|
+
offset = results.offset;
|
|
682
|
+
total = results.total;
|
|
683
|
+
} while (offset < total);
|
|
684
|
+
await (0, resource_1.writeCodeResourceDocuments)({
|
|
685
|
+
definition: projectResources_1.AI_AGENT_CODE_RESOURCE,
|
|
686
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
687
|
+
documents: agents,
|
|
688
|
+
pruneOtherDocuments: ids.length === 0,
|
|
674
689
|
});
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
documents: agents,
|
|
685
|
-
pruneOtherDocuments: ids.length === 0,
|
|
686
|
-
});
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
if (implicit && isAiAgentsUnavailableError(error)) {
|
|
693
|
+
globalState_1.default.log(styles.warning('Skipping AI agents: they require Lightdash Enterprise and AI agent access.'));
|
|
694
|
+
globalState_1.default.debug(`Could not download AI agents: ${(0, common_1.getErrorMessage)(error)}`);
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
687
699
|
return downloaded;
|
|
688
700
|
};
|
|
689
701
|
const readAiAgentFiles = async (customPath) => {
|
|
@@ -703,7 +715,7 @@ class AiAgentAsCodeUploadError extends Error {
|
|
|
703
715
|
this.originalError = originalError;
|
|
704
716
|
}
|
|
705
717
|
}
|
|
706
|
-
const upsertAiAgents = async (projectId, slugs, changes, force, customPath) => {
|
|
718
|
+
const upsertAiAgents = async (projectId, slugs, changes, force, customPath, implicit = false) => {
|
|
707
719
|
const agents = await readAiAgentFiles(customPath);
|
|
708
720
|
const filteredAgents = slugs.length
|
|
709
721
|
? agents.filter((agent) => slugs.includes(agent.slug))
|
|
@@ -715,11 +727,22 @@ const upsertAiAgents = async (projectId, slugs, changes, force, customPath) => {
|
|
|
715
727
|
return changes;
|
|
716
728
|
}
|
|
717
729
|
(0, contentAsCodeOutput_1.logContentAsCodeDiscovery)(`Found ${filteredAgents.length} AI agent files`);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
730
|
+
let results;
|
|
731
|
+
try {
|
|
732
|
+
results = await (0, apiClient_1.lightdashApi)({
|
|
733
|
+
method: 'POST',
|
|
734
|
+
url: `/api/v1/projects/${projectId}/code/aiAgents?force=${force}`,
|
|
735
|
+
body: JSON.stringify({ agents: filteredAgents }),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
if (implicit && isAiAgentsUnavailableError(error)) {
|
|
740
|
+
globalState_1.default.log(styles.warning('Skipping AI agents: they require Lightdash Enterprise and AI agent access.'));
|
|
741
|
+
globalState_1.default.debug(`Could not upload AI agents: ${(0, common_1.getErrorMessage)(error)}`);
|
|
742
|
+
return changes;
|
|
743
|
+
}
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
723
746
|
const counts = {
|
|
724
747
|
'AI agents created': results.created.length,
|
|
725
748
|
'AI agents updated': results.updated.length,
|
|
@@ -1028,9 +1051,12 @@ const downloadHandler = async (options) => {
|
|
|
1028
1051
|
}
|
|
1029
1052
|
}
|
|
1030
1053
|
if (!options.spacesOnly && shouldDownloadAiAgents(options)) {
|
|
1054
|
+
const implicit = includeAllOptionalContent &&
|
|
1055
|
+
options.includeAgents !== true &&
|
|
1056
|
+
options.agents.length === 0;
|
|
1031
1057
|
await output.runItem({
|
|
1032
1058
|
label: 'AI agents',
|
|
1033
|
-
action: () => downloadAiAgents(projectId, options.agents, options.path),
|
|
1059
|
+
action: () => downloadAiAgents(projectId, options.agents, implicit, options.path),
|
|
1034
1060
|
detail: (total) => `${total} downloaded`,
|
|
1035
1061
|
});
|
|
1036
1062
|
}
|
|
@@ -1249,8 +1275,7 @@ const downloadHandler = async (options) => {
|
|
|
1249
1275
|
error: `${error}`,
|
|
1250
1276
|
},
|
|
1251
1277
|
});
|
|
1252
|
-
|
|
1253
|
-
throw error;
|
|
1278
|
+
throw error;
|
|
1254
1279
|
}
|
|
1255
1280
|
};
|
|
1256
1281
|
exports.downloadHandler = downloadHandler;
|
|
@@ -1690,7 +1715,7 @@ const uploadHandler = async (options) => {
|
|
|
1690
1715
|
output,
|
|
1691
1716
|
label: 'AI agents',
|
|
1692
1717
|
changes,
|
|
1693
|
-
action: () => upsertAiAgents(projectId, options.agents, changes, options.force, options.path),
|
|
1718
|
+
action: () => upsertAiAgents(projectId, options.agents, changes, options.force, options.path, options.agents.length === 0),
|
|
1694
1719
|
});
|
|
1695
1720
|
}
|
|
1696
1721
|
catch (error) {
|
|
@@ -1965,10 +1990,9 @@ const uploadHandler = async (options) => {
|
|
|
1965
1990
|
error: (0, common_1.getErrorMessage)(error),
|
|
1966
1991
|
},
|
|
1967
1992
|
});
|
|
1968
|
-
if ((0, spacesAsCode_1.isSpaceAsCodeUploadError)(error))
|
|
1969
|
-
throw error;
|
|
1970
1993
|
if (error instanceof AiAgentAsCodeUploadError)
|
|
1971
1994
|
throw error.originalError;
|
|
1995
|
+
throw error;
|
|
1972
1996
|
}
|
|
1973
1997
|
};
|
|
1974
1998
|
exports.uploadHandler = uploadHandler;
|
|
@@ -1978,7 +2002,9 @@ exports.testHelpers = {
|
|
|
1978
2002
|
getFlatSpaceFileNames: spacesAsCode_1.getFlatSpaceFileNames,
|
|
1979
2003
|
getDashboardChartSlugs,
|
|
1980
2004
|
hasContentFilters,
|
|
2005
|
+
isAiAgentsUnavailableError,
|
|
1981
2006
|
isExternalConnectionsUnavailableError,
|
|
2007
|
+
downloadAiAgents,
|
|
1982
2008
|
readAiAgentFiles,
|
|
1983
2009
|
readExternalConnectionFiles,
|
|
1984
2010
|
readSpaceFiles: spacesAsCode_1.readSpaceFiles,
|
|
@@ -1988,6 +2014,7 @@ exports.testHelpers = {
|
|
|
1988
2014
|
shouldDownloadAiAgents,
|
|
1989
2015
|
sortSpaceFilesParentFirst: spacesAsCode_1.sortSpaceFilesParentFirst,
|
|
1990
2016
|
summarizeUploadChanges,
|
|
2017
|
+
upsertAiAgents,
|
|
1991
2018
|
upsertExternalConnections,
|
|
1992
2019
|
upsertSpaces: spacesAsCode_1.upsertSpaces,
|
|
1993
2020
|
upsertVirtualViews,
|
|
@@ -10,11 +10,75 @@ const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
|
10
10
|
const appsDownload_1 = require("./apps/appsDownload");
|
|
11
11
|
const apiClient_1 = require("./dbt/apiClient");
|
|
12
12
|
const download_1 = require("./download");
|
|
13
|
+
vitest_1.vi.mock('../analytics/analytics', () => ({
|
|
14
|
+
LightdashAnalytics: {
|
|
15
|
+
track: vitest_1.vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
vitest_1.vi.mock('../config', () => ({
|
|
19
|
+
getConfig: vitest_1.vi.fn().mockResolvedValue({
|
|
20
|
+
user: {
|
|
21
|
+
userUuid: 'user-uuid',
|
|
22
|
+
organizationUuid: 'organization-uuid',
|
|
23
|
+
},
|
|
24
|
+
context: {
|
|
25
|
+
apiKey: 'api-key',
|
|
26
|
+
serverUrl: 'http://lightdash.test',
|
|
27
|
+
project: 'project-uuid',
|
|
28
|
+
projectName: 'Test project',
|
|
29
|
+
},
|
|
30
|
+
answers: {
|
|
31
|
+
metadataFileGitignoreNoticeShown: true,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
setAnswer: vitest_1.vi.fn(),
|
|
35
|
+
}));
|
|
13
36
|
vitest_1.vi.mock('./dbt/apiClient', async (importOriginal) => ({
|
|
14
37
|
...(await importOriginal()),
|
|
38
|
+
getContentAsCodeUploadPermissions: vitest_1.vi.fn(),
|
|
15
39
|
lightdashApi: vitest_1.vi.fn(),
|
|
16
40
|
}));
|
|
17
|
-
const { assertUniqueSpacePaths, downloadSpaces, getDashboardChartSlugs, getFlatSpaceFileNames, hasContentFilters, isExternalConnectionsUnavailableError, readAiAgentFiles, readSpaceFiles, readSpaceNames, sanitizeChartForDownload, shouldFallBackToEmbeddedSpaces, shouldDownloadAiAgents, summarizeUploadChanges, upsertExternalConnections, upsertSpaces, upsertVirtualViews, validateSpaceIdentity, writeSpaceFiles, } = download_1.testHelpers;
|
|
41
|
+
const { assertUniqueSpacePaths, downloadSpaces, getDashboardChartSlugs, getFlatSpaceFileNames, hasContentFilters, isAiAgentsUnavailableError, isExternalConnectionsUnavailableError, downloadAiAgents, readAiAgentFiles, readSpaceFiles, readSpaceNames, sanitizeChartForDownload, shouldFallBackToEmbeddedSpaces, shouldDownloadAiAgents, summarizeUploadChanges, upsertAiAgents, upsertExternalConnections, upsertSpaces, upsertVirtualViews, validateSpaceIdentity, writeSpaceFiles, } = download_1.testHelpers;
|
|
42
|
+
const makeDownloadHandlerOptions = (overrides = {}) => ({
|
|
43
|
+
verbose: false,
|
|
44
|
+
charts: [],
|
|
45
|
+
dashboards: [],
|
|
46
|
+
alerts: [],
|
|
47
|
+
agents: [],
|
|
48
|
+
googleSheets: [],
|
|
49
|
+
scheduledDeliveries: [],
|
|
50
|
+
virtualViews: [],
|
|
51
|
+
externalConnections: [],
|
|
52
|
+
apps: [],
|
|
53
|
+
includeAgents: false,
|
|
54
|
+
includeApps: false,
|
|
55
|
+
force: false,
|
|
56
|
+
languageMap: false,
|
|
57
|
+
skipSpaceCreate: false,
|
|
58
|
+
public: false,
|
|
59
|
+
includeCharts: false,
|
|
60
|
+
nested: false,
|
|
61
|
+
rootSpaces: false,
|
|
62
|
+
skipSpaces: true,
|
|
63
|
+
skipCharts: true,
|
|
64
|
+
skipDashboards: true,
|
|
65
|
+
skipAlerts: false,
|
|
66
|
+
skipAgents: false,
|
|
67
|
+
skipGoogleSheets: false,
|
|
68
|
+
skipScheduledDeliveries: false,
|
|
69
|
+
skipVirtualViews: false,
|
|
70
|
+
skipExternalConnections: false,
|
|
71
|
+
includeAlerts: false,
|
|
72
|
+
includeGoogleSheets: false,
|
|
73
|
+
includeScheduledDeliveries: false,
|
|
74
|
+
includeVirtualViews: false,
|
|
75
|
+
includeExternalConnections: false,
|
|
76
|
+
includeAll: false,
|
|
77
|
+
stripPivotSeries: false,
|
|
78
|
+
concurrency: 1,
|
|
79
|
+
organization: false,
|
|
80
|
+
...overrides,
|
|
81
|
+
});
|
|
18
82
|
const makeContentFilterOptions = (overrides = {}) => ({
|
|
19
83
|
spacesOnly: false,
|
|
20
84
|
charts: [],
|
|
@@ -442,6 +506,130 @@ describe('isExternalConnectionsUnavailableError', () => {
|
|
|
442
506
|
expect(isExternalConnectionsUnavailableError(new Error('network down'))).toBe(false);
|
|
443
507
|
});
|
|
444
508
|
});
|
|
509
|
+
describe('AI agent downloads', () => {
|
|
510
|
+
const unavailableError = new common_1.LightdashError({
|
|
511
|
+
message: "Unable to initialize service 'aiAgentCoderService' - no factory or provider.",
|
|
512
|
+
name: 'MissingConfigError',
|
|
513
|
+
statusCode: 422,
|
|
514
|
+
data: {},
|
|
515
|
+
});
|
|
516
|
+
beforeEach(() => {
|
|
517
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockReset();
|
|
518
|
+
});
|
|
519
|
+
it('classifies permission, missing-route and non-EE errors as unavailable', () => {
|
|
520
|
+
[403, 404, 422].forEach((statusCode) => {
|
|
521
|
+
expect(isAiAgentsUnavailableError(new common_1.LightdashError({
|
|
522
|
+
message: 'AI agents unavailable',
|
|
523
|
+
name: 'TestError',
|
|
524
|
+
statusCode,
|
|
525
|
+
data: {},
|
|
526
|
+
}))).toBe(true);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
it('skips unavailable AI agents when selected implicitly', async () => {
|
|
530
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValueOnce(unavailableError);
|
|
531
|
+
await expect(downloadAiAgents('project-uuid', [], true)).resolves.toBe(0);
|
|
532
|
+
});
|
|
533
|
+
it('keeps explicit AI agent downloads strict', async () => {
|
|
534
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValueOnce(unavailableError);
|
|
535
|
+
await expect(downloadAiAgents('project-uuid', [], false)).rejects.toBe(unavailableError);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
describe('downloadHandler failures', () => {
|
|
539
|
+
const unavailableError = new common_1.LightdashError({
|
|
540
|
+
message: "Unable to initialize service 'aiAgentCoderService' - no factory or provider.",
|
|
541
|
+
name: 'MissingConfigError',
|
|
542
|
+
statusCode: 422,
|
|
543
|
+
data: {},
|
|
544
|
+
});
|
|
545
|
+
beforeEach(() => {
|
|
546
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockReset();
|
|
547
|
+
});
|
|
548
|
+
it('continues to scheduled content after implicitly skipping unavailable AI agents', async () => {
|
|
549
|
+
const tmpDir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'include-all-download-test-'));
|
|
550
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockImplementation(async ({ url }) => {
|
|
551
|
+
if (url.startsWith('/api/v1/health')) {
|
|
552
|
+
return { version: '0.0.0' };
|
|
553
|
+
}
|
|
554
|
+
if (url === '/api/v1/projects/project-uuid') {
|
|
555
|
+
return { name: 'Test project' };
|
|
556
|
+
}
|
|
557
|
+
if (url.includes('/code/virtualViews')) {
|
|
558
|
+
return {
|
|
559
|
+
virtualViews: [],
|
|
560
|
+
skipped: [],
|
|
561
|
+
missingSlugs: [],
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (url.includes('/code/aiAgents')) {
|
|
565
|
+
throw unavailableError;
|
|
566
|
+
}
|
|
567
|
+
if (url.includes('/code/alerts')) {
|
|
568
|
+
return { alerts: [], skipped: [] };
|
|
569
|
+
}
|
|
570
|
+
if (url.includes('/code/scheduledDeliveries')) {
|
|
571
|
+
return { scheduledDeliveries: [], skipped: [] };
|
|
572
|
+
}
|
|
573
|
+
if (url.includes('/code/googleSheets')) {
|
|
574
|
+
return { googleSheetsSyncs: [], skipped: [] };
|
|
575
|
+
}
|
|
576
|
+
if (url.includes('/code/externalConnections')) {
|
|
577
|
+
throw unavailableError;
|
|
578
|
+
}
|
|
579
|
+
if (url.includes('/ee/projects/project-uuid/apps')) {
|
|
580
|
+
throw unavailableError;
|
|
581
|
+
}
|
|
582
|
+
throw new Error(`Unexpected request: ${url}`);
|
|
583
|
+
});
|
|
584
|
+
try {
|
|
585
|
+
await expect((0, download_1.downloadHandler)(makeDownloadHandlerOptions({
|
|
586
|
+
includeAll: true,
|
|
587
|
+
path: tmpDir,
|
|
588
|
+
project: 'project-uuid',
|
|
589
|
+
}))).resolves.toBeUndefined();
|
|
590
|
+
expect(vitest_1.vi.mocked(apiClient_1.lightdashApi)).toHaveBeenCalledWith(expect.objectContaining({
|
|
591
|
+
url: expect.stringContaining('/code/scheduledDeliveries'),
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
await fs_1.promises.rm(tmpDir, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
it('rethrows fatal project download errors', async () => {
|
|
599
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockImplementation(async ({ url }) => {
|
|
600
|
+
if (url.startsWith('/api/v1/health')) {
|
|
601
|
+
return { version: '0.0.0' };
|
|
602
|
+
}
|
|
603
|
+
if (url === '/api/v1/projects/project-uuid') {
|
|
604
|
+
return { name: 'Test project' };
|
|
605
|
+
}
|
|
606
|
+
if (url.includes('/code/aiAgents')) {
|
|
607
|
+
throw unavailableError;
|
|
608
|
+
}
|
|
609
|
+
throw new Error(`Unexpected request: ${url}`);
|
|
610
|
+
});
|
|
611
|
+
await expect((0, download_1.downloadHandler)(makeDownloadHandlerOptions({
|
|
612
|
+
agents: ['sales-agent'],
|
|
613
|
+
project: 'project-uuid',
|
|
614
|
+
}))).rejects.toBe(unavailableError);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
describe('uploadHandler failures', () => {
|
|
618
|
+
beforeEach(() => {
|
|
619
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockReset();
|
|
620
|
+
vitest_1.vi.mocked(apiClient_1.getContentAsCodeUploadPermissions).mockReset();
|
|
621
|
+
});
|
|
622
|
+
it('rethrows fatal project upload errors', async () => {
|
|
623
|
+
const fatalError = new Error('Failed to load dbt credentials');
|
|
624
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockResolvedValue({
|
|
625
|
+
version: '0.0.0',
|
|
626
|
+
});
|
|
627
|
+
vitest_1.vi.mocked(apiClient_1.getContentAsCodeUploadPermissions).mockRejectedValueOnce(fatalError);
|
|
628
|
+
await expect((0, download_1.uploadHandler)(makeDownloadHandlerOptions({
|
|
629
|
+
project: 'project-uuid',
|
|
630
|
+
}))).rejects.toBe(fatalError);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
445
633
|
describe('readAiAgentFiles', () => {
|
|
446
634
|
let tmpDir;
|
|
447
635
|
beforeEach(async () => {
|
|
@@ -481,6 +669,40 @@ describe('readAiAgentFiles', () => {
|
|
|
481
669
|
]);
|
|
482
670
|
});
|
|
483
671
|
});
|
|
672
|
+
describe('upsertAiAgents', () => {
|
|
673
|
+
let tmpDir;
|
|
674
|
+
const unavailableError = new common_1.LightdashError({
|
|
675
|
+
message: "Unable to initialize service 'aiAgentCoderService' - no factory or provider.",
|
|
676
|
+
name: 'MissingConfigError',
|
|
677
|
+
statusCode: 422,
|
|
678
|
+
data: {},
|
|
679
|
+
});
|
|
680
|
+
beforeEach(async () => {
|
|
681
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockReset();
|
|
682
|
+
vitest_1.vi.spyOn(globalState_1.default, 'log').mockImplementation(() => undefined);
|
|
683
|
+
tmpDir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'agent-upload-test-'));
|
|
684
|
+
await fs_1.promises.mkdir(path.join(tmpDir, 'ai-agents'));
|
|
685
|
+
await fs_1.promises.writeFile(path.join(tmpDir, 'ai-agents', 'revenue-agent.yml'), [
|
|
686
|
+
'contentType: ai_agent',
|
|
687
|
+
'version: 1',
|
|
688
|
+
'slug: revenue-agent',
|
|
689
|
+
'',
|
|
690
|
+
].join('\n'));
|
|
691
|
+
});
|
|
692
|
+
afterEach(async () => {
|
|
693
|
+
vitest_1.vi.restoreAllMocks();
|
|
694
|
+
await fs_1.promises.rm(tmpDir, { recursive: true, force: true });
|
|
695
|
+
});
|
|
696
|
+
it('skips unavailable AI agents when their upload is implicit', async () => {
|
|
697
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValueOnce(unavailableError);
|
|
698
|
+
await expect(upsertAiAgents('project-uuid', [], {}, false, tmpDir, true)).resolves.toStrictEqual({});
|
|
699
|
+
expect(globalState_1.default.log).toHaveBeenCalledWith(expect.stringContaining('Skipping AI agents'));
|
|
700
|
+
});
|
|
701
|
+
it('fails when unavailable AI agents were selected explicitly', async () => {
|
|
702
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValueOnce(unavailableError);
|
|
703
|
+
await expect(upsertAiAgents('project-uuid', ['revenue-agent'], {}, false, tmpDir, false)).rejects.toBe(unavailableError);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
484
706
|
describe('shouldDownloadAiAgents', () => {
|
|
485
707
|
it('does not download AI agents by default', () => {
|
|
486
708
|
expect(shouldDownloadAiAgents({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customRoles.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/customRoles.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"customRoles.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/customRoles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,gBAAgB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAKH,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC9B,MAAM,2BAA2B,CAAC;AAGnC,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAC;AAEtD,MAAM,MAAM,uBAAuB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,sBAAsB,CAAC,gBAAgB,CAkB1E,CAAC;AAEN,eAAO,MAAM,oBAAoB,4BAA6B,MAAM,KAAG,MACK,CAAC;AAE7E,eAAO,MAAM,6BAA6B,YAC7B,uBAAuB,KACjC,MAMF,CAAC;AAUF,eAAO,MAAM,mBAAmB,4BACH,MAAM,KAChC,OAAO,CAAC,OAAO,EAAE,CAOnB,CAAC;AAEF,eAAO,MAAM,iBAAiB,qBACR,MAAM,2BACC,MAAM,KAChC,OAAO,CAAC,uBAAuB,CAe5B,CAAC;AAEP,eAAO,MAAM,mBAAmB,qBACV,MAAM,2BACC,MAAM,KAChC,OAAO,CAAC,MAAM,CAcX,CAAC"}
|
|
@@ -26,8 +26,17 @@ exports.CUSTOM_ROLE_CODE_RESOURCE = {
|
|
|
26
26
|
};
|
|
27
27
|
const getCustomRolesFolder = (organizationContentPath) => path.join(organizationContentPath, exports.CUSTOM_ROLE_CODE_RESOURCE.folderName);
|
|
28
28
|
exports.getCustomRolesFolder = getCustomRolesFolder;
|
|
29
|
-
const formatCustomRoleUploadSummary = (summary) =>
|
|
29
|
+
const formatCustomRoleUploadSummary = (summary) => {
|
|
30
|
+
const skipped = summary.skipped && summary.skipped > 0
|
|
31
|
+
? `, ${summary.skipped} skipped`
|
|
32
|
+
: '';
|
|
33
|
+
return `${summary.created} created, ${summary.updated} updated, ${summary.unchanged} unchanged${skipped}, ${summary.failed} failed`;
|
|
34
|
+
};
|
|
30
35
|
exports.formatCustomRoleUploadSummary = formatCustomRoleUploadSummary;
|
|
36
|
+
const isCustomRolesUnavailableError = (error) => error instanceof common_1.LightdashError &&
|
|
37
|
+
([404, 422].includes(error.statusCode) ||
|
|
38
|
+
(error.statusCode === 403 &&
|
|
39
|
+
error.message.includes('Custom roles require a Lightdash Enterprise license')));
|
|
31
40
|
const readCustomRoleFiles = async (organizationContentPath) => {
|
|
32
41
|
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
33
42
|
definition: exports.CUSTOM_ROLE_CODE_RESOURCE,
|
|
@@ -48,6 +57,7 @@ const uploadCustomRoles = async (organizationUuid, organizationContentPath) => (
|
|
|
48
57
|
});
|
|
49
58
|
return result.action;
|
|
50
59
|
},
|
|
60
|
+
skipError: isCustomRolesUnavailableError,
|
|
51
61
|
});
|
|
52
62
|
exports.uploadCustomRoles = uploadCustomRoles;
|
|
53
63
|
const downloadCustomRoles = async (organizationUuid, organizationContentPath) => (0, resource_1.downloadCodeResource)({
|
|
@@ -158,4 +158,22 @@ describe('custom roles as code', () => {
|
|
|
158
158
|
expect(summary.failures[0].message).toContain('Unknown custom role scopes: delete:VirtualViewsss');
|
|
159
159
|
expect(apiClient_1.lightdashApi).toHaveBeenCalledTimes(2);
|
|
160
160
|
});
|
|
161
|
+
it('skips custom roles when the target has no Enterprise license', async () => {
|
|
162
|
+
await writeCustomRole('role.yml', customRole('Enterprise role'));
|
|
163
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValueOnce(new common_1.LightdashError({
|
|
164
|
+
message: 'Custom roles require a Lightdash Enterprise license',
|
|
165
|
+
name: 'ForbiddenError',
|
|
166
|
+
statusCode: 403,
|
|
167
|
+
data: {},
|
|
168
|
+
}));
|
|
169
|
+
const summary = await (0, customRoles_1.uploadCustomRoles)('organization-uuid', tmpDir);
|
|
170
|
+
expect(summary).toMatchObject({
|
|
171
|
+
created: 0,
|
|
172
|
+
updated: 0,
|
|
173
|
+
unchanged: 0,
|
|
174
|
+
skipped: 1,
|
|
175
|
+
failed: 0,
|
|
176
|
+
failures: [],
|
|
177
|
+
});
|
|
178
|
+
});
|
|
161
179
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AA0B3C,KAAK,0BAA0B,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAkBF,eAAO,MAAM,4BAA4B,gBAAiB,MAAM,KAAG,MAClC,CAAC;AAElC,eAAO,MAAM,2BAA2B,4BAGrC,0BAA0B,KAAG,OAAO,CAAC,IAAI,CA2F3C,CAAC;AAEF,eAAO,MAAM,yBAAyB,yCAInC,0BAA0B,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AA0B3C,KAAK,0BAA0B,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAkBF,eAAO,MAAM,4BAA4B,gBAAiB,MAAM,KAAG,MAClC,CAAC;AAElC,eAAO,MAAM,2BAA2B,4BAGrC,0BAA0B,KAAG,OAAO,CAAC,IAAI,CA2F3C,CAAC;AAEF,eAAO,MAAM,yBAAyB,yCAInC,0BAA0B,KAAG,OAAO,CAAC,IAAI,CAoJ3C,CAAC"}
|
|
@@ -128,7 +128,10 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
128
128
|
globalState_1.default.log(styles.warning(`Skipped users and ${skippedGroups} groups because custom roles failed`));
|
|
129
129
|
throw createCustomRolePartialUploadError(`Processed custom roles: ${summaryMessage}`);
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
if (summary.skipped && summary.skipped > 0) {
|
|
132
|
+
globalState_1.default.log(styles.warning('Skipped custom roles because they require a Lightdash Enterprise license.'));
|
|
133
|
+
}
|
|
134
|
+
output.completeItem(summaryMessage, summary.skipped && summary.skipped > 0 ? 'warning' : undefined);
|
|
132
135
|
output.startItem('Users');
|
|
133
136
|
const userSummary = await (0, users_1.uploadUsers)(organizationUuid, organizationContentPath, sendInvites);
|
|
134
137
|
const userSummaryMessage = (0, users_1.formatUserUploadSummary)(userSummary);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"unique-names-generator": "4.7.1",
|
|
42
42
|
"uuid": "11.1.1",
|
|
43
43
|
"yaml": "2.8.3",
|
|
44
|
-
"@lightdash/common": "1.
|
|
45
|
-
"@lightdash/warehouses": "1.
|
|
44
|
+
"@lightdash/common": "1.49.0",
|
|
45
|
+
"@lightdash/warehouses": "1.49.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|