@lightdash/cli 1.137.1 → 1.138.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/registry.d.ts.map +1 -1
- package/dist/handlers/contentAsCode/registry.js +7 -1
- package/dist/handlers/dbt/apiClient.d.ts +5 -1
- package/dist/handlers/dbt/apiClient.d.ts.map +1 -1
- package/dist/handlers/dbt/apiClient.js +27 -18
- package/dist/handlers/organizationContent/index.d.ts.map +1 -1
- package/dist/handlers/organizationContent/index.js +25 -0
- package/dist/handlers/organizationContent/index.test.js +48 -3
- package/dist/handlers/organizationContent/themePackage.d.ts +22 -0
- package/dist/handlers/organizationContent/themePackage.d.ts.map +1 -0
- package/dist/handlers/organizationContent/themePackage.js +408 -0
- package/dist/handlers/organizationContent/themes.d.ts +20 -0
- package/dist/handlers/organizationContent/themes.d.ts.map +1 -0
- package/dist/handlers/organizationContent/themes.js +148 -0
- package/dist/handlers/organizationContent/themes.test.d.ts +2 -0
- package/dist/handlers/organizationContent/themes.test.d.ts.map +1 -0
- package/dist/handlers/organizationContent/themes.test.js +180 -0
- package/package.json +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,yBAAyB,EACjC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,yBAAyB,EACjC,MAAM,mBAAmB,CAAC;AAc3B,MAAM,MAAM,wBAAwB,GAAG;IACnC,IAAI,EAAE,yBAAyB,CAAC;IAChC,YAAY,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACtD,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,SAAS,wBAAwB,EAMtE,CAAC;AAEN,eAAO,MAAM,sBAAsB,EAAE,SAAS,wBAAwB,EAwBrE,CAAC;AAEF,eAAO,MAAM,iCAAiC,cAC/B,SAAS,wBAAwB,EAAE,KAC/C,IAeF,CAAC"}
|
|
@@ -4,9 +4,15 @@ exports.assertCodeResourceDependencyOrder = exports.PROJECT_CODE_RESOURCES = exp
|
|
|
4
4
|
const common_1 = require("@lightdash/common");
|
|
5
5
|
const customRoles_1 = require("../organizationContent/customRoles");
|
|
6
6
|
const groups_1 = require("../organizationContent/groups");
|
|
7
|
+
const themes_1 = require("../organizationContent/themes");
|
|
7
8
|
const users_1 = require("../organizationContent/users");
|
|
8
9
|
const projectResources_1 = require("./projectResources");
|
|
9
|
-
exports.ORGANIZATION_CODE_RESOURCES = [
|
|
10
|
+
exports.ORGANIZATION_CODE_RESOURCES = [
|
|
11
|
+
customRoles_1.CUSTOM_ROLE_CODE_RESOURCE,
|
|
12
|
+
users_1.USER_CODE_RESOURCE,
|
|
13
|
+
groups_1.GROUP_CODE_RESOURCE,
|
|
14
|
+
themes_1.THEME_CODE_RESOURCE,
|
|
15
|
+
];
|
|
10
16
|
exports.PROJECT_CODE_RESOURCES = [
|
|
11
17
|
{ kind: common_1.ContentAsCodeType.SPACE, dependencies: [] },
|
|
12
18
|
projectResources_1.VIRTUAL_VIEW_CODE_RESOURCE,
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ApiResponse, ProjectType, type LightdashUserWithAbilityRules } from '@lightdash/common';
|
|
2
|
-
import { BodyInit } from 'node-fetch';
|
|
2
|
+
import { BodyInit, type Response } from 'node-fetch';
|
|
3
3
|
type LightdashApiProps = {
|
|
4
4
|
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
|
|
5
5
|
url: string;
|
|
6
6
|
body: BodyInit | undefined;
|
|
7
7
|
};
|
|
8
|
+
type LightdashRawApiProps = LightdashApiProps & {
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
};
|
|
8
11
|
export declare const setGzipEnabled: (enabled: boolean) => void;
|
|
12
|
+
export declare const lightdashRawApi: ({ method, url, body, headers: requestHeaders, }: LightdashRawApiProps) => Promise<Response>;
|
|
9
13
|
export declare const lightdashApi: <T extends ApiResponse['results']>({ method, url, body, }: LightdashApiProps) => Promise<T>;
|
|
10
14
|
export declare const getUserContext: () => Promise<LightdashUserWithAbilityRules>;
|
|
11
15
|
export type ContentAsCodeUploadPermissions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../../src/handlers/dbt/apiClient.ts"],"names":[],"mappings":"AACA,OAAO,EAGH,WAAW,EAIX,WAAW,EAEX,KAAK,6BAA6B,EAGrC,MAAM,mBAAmB,CAAC;AAC3B,OAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../../src/handlers/dbt/apiClient.ts"],"names":[],"mappings":"AACA,OAAO,EAGH,WAAW,EAIX,WAAW,EAEX,KAAK,6BAA6B,EAGrC,MAAM,mBAAmB,CAAC;AAC3B,OAAc,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAS5D,KAAK,iBAAiB,GAAG;IACrB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC9B,CAAC;AAEF,KAAK,oBAAoB,GAAG,iBAAiB,GAAG;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAAC;AAKF,eAAO,MAAM,cAAc,YAAa,OAAO,SAE9C,CAAC;AAEF,eAAO,MAAM,eAAe,oDAKzB,oBAAoB,KAAG,OAAO,CAAC,QAAQ,CA8BzC,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,CAAC,SAAS,WAAW,CAAC,SAAS,CAAC,0BAIhE,iBAAiB,KAAG,OAAO,CAAC,CAAC,CAgD/B,CAAC;AAEF,eAAO,MAAM,cAAc,QACb,OAAO,CAAC,6BAA6B,CAKzC,CAAC;AAEX,MAAM,MAAM,8BAA8B,GAAG;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,iCAAiC,gBAC7B,MAAM,KACpB,OAAO,CAAC,8BAA8B,CA0FxC,CAAC;AAEF,eAAO,MAAM,8BAA8B,wBAClB,MAAM,GAAG,SAAS,eAC1B,WAAW,KACzB,OAAO,CAAC,IAAI,CA0Gd,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,IAAI,CA2B1D,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkLightdashVersion = exports.checkProjectCreationPermission = exports.getContentAsCodeUploadPermissions = exports.getUserContext = exports.lightdashApi = exports.setGzipEnabled = void 0;
|
|
3
|
+
exports.checkLightdashVersion = exports.checkProjectCreationPermission = exports.getContentAsCodeUploadPermissions = exports.getUserContext = exports.lightdashApi = exports.lightdashRawApi = exports.setGzipEnabled = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const ability_1 = require("@casl/ability");
|
|
6
6
|
const common_1 = require("@lightdash/common");
|
|
@@ -18,14 +18,33 @@ const setGzipEnabled = (enabled) => {
|
|
|
18
18
|
gzipEnabled = enabled;
|
|
19
19
|
};
|
|
20
20
|
exports.setGzipEnabled = setGzipEnabled;
|
|
21
|
-
const
|
|
21
|
+
const lightdashRawApi = async ({ method, url, body, headers: requestHeaders = {}, }) => {
|
|
22
22
|
const config = await (0, config_1.getConfig)();
|
|
23
23
|
if (!(config.context?.apiKey && config.context.serverUrl)) {
|
|
24
24
|
throw new common_1.AuthorizationError(`Not logged in. Run 'lightdash login --help'`);
|
|
25
25
|
}
|
|
26
|
-
const headers =
|
|
26
|
+
const headers = {
|
|
27
|
+
...(0, utils_1.buildRequestHeaders)(config.context.apiKey),
|
|
28
|
+
...requestHeaders,
|
|
29
|
+
};
|
|
27
30
|
const fullUrl = new url_1.URL(url, config.context.serverUrl).href;
|
|
28
31
|
globalState_1.default.debug(`> Making HTTP ${method} request to: ${fullUrl}`);
|
|
32
|
+
const response = await (0, node_fetch_1.default)(fullUrl, { method, headers, body });
|
|
33
|
+
globalState_1.default.debug(`> HTTP request returned status: ${response.status}`);
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const contentType = response.headers.get('content-type');
|
|
36
|
+
if (contentType?.includes('application/json')) {
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
throw new common_1.LightdashError(data.error);
|
|
39
|
+
}
|
|
40
|
+
const responseText = await response.text();
|
|
41
|
+
throw new Error(`Received non-JSON response from server (status ${response.status}): ${responseText}`);
|
|
42
|
+
}
|
|
43
|
+
return response;
|
|
44
|
+
};
|
|
45
|
+
exports.lightdashRawApi = lightdashRawApi;
|
|
46
|
+
const lightdashApi = async ({ method, url, body, }) => {
|
|
47
|
+
const headers = {};
|
|
29
48
|
let requestBody = body;
|
|
30
49
|
if (gzipEnabled &&
|
|
31
50
|
body &&
|
|
@@ -42,21 +61,11 @@ const lightdashApi = async ({ method, url, body, }) => {
|
|
|
42
61
|
globalState_1.default.debug(`> Gzip compression failed, sending uncompressed: ${err}`);
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
|
-
return (0,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (contentType && contentType.includes('application/json')) {
|
|
51
|
-
return r.json().then((d) => {
|
|
52
|
-
throw new common_1.LightdashError(d.error);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return r.text().then((text) => {
|
|
56
|
-
throw new Error(`Received non-JSON response from server (status ${r.status}): ${text}`);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return r;
|
|
64
|
+
return (0, exports.lightdashRawApi)({
|
|
65
|
+
method,
|
|
66
|
+
url,
|
|
67
|
+
body: requestBody,
|
|
68
|
+
headers,
|
|
60
69
|
})
|
|
61
70
|
.then((r) => r.json())
|
|
62
71
|
.then((d) => {
|
|
@@ -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;
|
|
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;AAgC3C,KAAK,0BAA0B,GAAG;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAqBF,eAAO,MAAM,4BAA4B,gBAAiB,MAAM,KAAG,MAClC,CAAC;AAElC,eAAO,MAAM,2BAA2B,4BAGrC,0BAA0B,KAAG,OAAO,CAAC,IAAI,CAiG3C,CAAC;AAEF,eAAO,MAAM,yBAAyB,yCAInC,0BAA0B,KAAG,OAAO,CAAC,IAAI,CA+K3C,CAAC"}
|
|
@@ -12,10 +12,12 @@ const resource_1 = require("../contentAsCode/resource");
|
|
|
12
12
|
const contentAsCodePaths_1 = require("../contentAsCodePaths");
|
|
13
13
|
const customRoles_1 = require("./customRoles");
|
|
14
14
|
const groups_1 = require("./groups");
|
|
15
|
+
const themes_1 = require("./themes");
|
|
15
16
|
const users_1 = require("./users");
|
|
16
17
|
const createCustomRolePartialUploadError = (message) => new resource_1.CodeResourcePhaseError('custom_role', message);
|
|
17
18
|
const createUserPartialUploadError = (message) => new resource_1.CodeResourcePhaseError('user', message);
|
|
18
19
|
const createGroupPartialUploadError = (message) => new resource_1.CodeResourcePhaseError('group', message);
|
|
20
|
+
const createThemePartialUploadError = (message) => new resource_1.CodeResourcePhaseError('theme', message);
|
|
19
21
|
const isPartialUploadError = resource_1.isCodeResourcePhaseError;
|
|
20
22
|
const isGroupServiceDisabledError = (error) => error instanceof common_1.LightdashError &&
|
|
21
23
|
error.statusCode === 403 &&
|
|
@@ -65,6 +67,11 @@ const downloadOrganizationContent = async ({ customPath, config, }) => {
|
|
|
65
67
|
output.completeItem('service unavailable', 'warning');
|
|
66
68
|
globalState_1.default.debug('> Warning: groups were not downloaded because the group service is not enabled');
|
|
67
69
|
}
|
|
70
|
+
const themesTotal = await output.runItem({
|
|
71
|
+
label: 'Themes',
|
|
72
|
+
action: () => (0, themes_1.downloadThemes)(organizationContentPath),
|
|
73
|
+
detail: (total) => `${total} downloaded`,
|
|
74
|
+
});
|
|
68
75
|
const renderedSummary = output.complete(organizationContentPath, (Date.now() - start) / 1000);
|
|
69
76
|
if (!renderedSummary) {
|
|
70
77
|
globalState_1.default.log(styles.success(`Downloaded organization content saved to ${organizationContentPath}`));
|
|
@@ -78,6 +85,7 @@ const downloadOrganizationContent = async ({ customPath, config, }) => {
|
|
|
78
85
|
customRolesNum: customRolesTotal,
|
|
79
86
|
usersNum: usersTotal,
|
|
80
87
|
groupsNum: groupsTotal,
|
|
88
|
+
themesNum: themesTotal,
|
|
81
89
|
timeToCompleted: (Date.now() - start) / 1000,
|
|
82
90
|
},
|
|
83
91
|
});
|
|
@@ -118,6 +126,7 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
118
126
|
scope: 'organization',
|
|
119
127
|
});
|
|
120
128
|
try {
|
|
129
|
+
const preparedThemes = await (0, themes_1.prepareThemeUploads)(organizationContentPath);
|
|
121
130
|
output.startItem('Custom roles');
|
|
122
131
|
const summary = await (0, customRoles_1.uploadCustomRoles)(organizationUuid, organizationContentPath);
|
|
123
132
|
const summaryMessage = (0, customRoles_1.formatCustomRoleUploadSummary)(summary);
|
|
@@ -152,6 +161,18 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
152
161
|
throw createGroupPartialUploadError(`Processed groups: ${groupSummaryMessage}`);
|
|
153
162
|
}
|
|
154
163
|
output.completeItem(groupSummaryMessage);
|
|
164
|
+
output.startItem('Themes');
|
|
165
|
+
const themeSummary = await (0, themes_1.uploadThemes)(preparedThemes);
|
|
166
|
+
const themeSummaryMessage = (0, themes_1.formatThemeUploadSummary)(themeSummary);
|
|
167
|
+
if (themeSummary.failed > 0) {
|
|
168
|
+
themeSummary.failures.forEach(({ message }) => globalState_1.default.log(styles.error(message)));
|
|
169
|
+
if (themeSummary.completedSlugs.length > 0) {
|
|
170
|
+
globalState_1.default.log(styles.warning(`Processed themes before failure: ${themeSummary.completedSlugs.join(', ')}`));
|
|
171
|
+
}
|
|
172
|
+
output.prepareForFailureDetails();
|
|
173
|
+
throw createThemePartialUploadError(`Processed themes: ${themeSummaryMessage}`);
|
|
174
|
+
}
|
|
175
|
+
output.completeItem(themeSummaryMessage);
|
|
155
176
|
const renderedSummary = output.complete(organizationContentPath, (Date.now() - start) / 1000);
|
|
156
177
|
if (!renderedSummary) {
|
|
157
178
|
globalState_1.default.log(styles.success(`Uploaded organization content from ${organizationContentPath}`));
|
|
@@ -173,6 +194,10 @@ const uploadOrganizationContent = async ({ customPath, config, sendInvites = fal
|
|
|
173
194
|
groupsCreated: groupSummary.created,
|
|
174
195
|
groupsUpdated: groupSummary.updated,
|
|
175
196
|
groupsUnchanged: groupSummary.unchanged,
|
|
197
|
+
themesCreated: themeSummary.created,
|
|
198
|
+
themesUpdated: themeSummary.updated,
|
|
199
|
+
themesUnchanged: themeSummary.unchanged,
|
|
200
|
+
themesUploaded: themeSummary.created + themeSummary.updated,
|
|
176
201
|
timeToCompleted: (Date.now() - start) / 1000,
|
|
177
202
|
},
|
|
178
203
|
});
|
|
@@ -7,6 +7,7 @@ const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
|
|
|
7
7
|
const customRoles_1 = require("./customRoles");
|
|
8
8
|
const groups_1 = require("./groups");
|
|
9
9
|
const index_1 = require("./index");
|
|
10
|
+
const themes_1 = require("./themes");
|
|
10
11
|
const users_1 = require("./users");
|
|
11
12
|
vi.mock('./customRoles', async (importOriginal) => ({
|
|
12
13
|
...(await importOriginal()),
|
|
@@ -24,6 +25,12 @@ vi.mock('./groups', async (importOriginal) => ({
|
|
|
24
25
|
uploadGroups: vi.fn(),
|
|
25
26
|
countDependencySkippedGroups: vi.fn(),
|
|
26
27
|
}));
|
|
28
|
+
vi.mock('./themes', async (importOriginal) => ({
|
|
29
|
+
...(await importOriginal()),
|
|
30
|
+
downloadThemes: vi.fn(),
|
|
31
|
+
prepareThemeUploads: vi.fn(),
|
|
32
|
+
uploadThemes: vi.fn(),
|
|
33
|
+
}));
|
|
27
34
|
describe('organization content download', () => {
|
|
28
35
|
const spinner = {
|
|
29
36
|
start: vi.fn(),
|
|
@@ -47,6 +54,7 @@ describe('organization content download', () => {
|
|
|
47
54
|
vi.mocked(customRoles_1.downloadCustomRoles).mockResolvedValue(2);
|
|
48
55
|
vi.mocked(users_1.downloadUsers).mockResolvedValue(3);
|
|
49
56
|
vi.mocked(groups_1.downloadGroups).mockResolvedValue(4);
|
|
57
|
+
vi.mocked(themes_1.downloadThemes).mockResolvedValue(5);
|
|
50
58
|
});
|
|
51
59
|
it('skips groups when the group service is disabled', async () => {
|
|
52
60
|
vi.mocked(groups_1.downloadGroups).mockRejectedValue(new common_1.LightdashError({
|
|
@@ -62,7 +70,7 @@ describe('organization content download', () => {
|
|
|
62
70
|
});
|
|
63
71
|
it('reports a duration for every downloaded resource', async () => {
|
|
64
72
|
await (0, index_1.downloadOrganizationContent)({ config });
|
|
65
|
-
expect(spinner.succeed).toHaveBeenCalledTimes(
|
|
73
|
+
expect(spinner.succeed).toHaveBeenCalledTimes(4);
|
|
66
74
|
spinner.succeed.mock.calls.forEach(([message]) => expect(message).toMatch(/\(\d+ms\)$/));
|
|
67
75
|
});
|
|
68
76
|
it('still fails for other group download errors', async () => {
|
|
@@ -112,6 +120,14 @@ describe('organization content upload sequencing', () => {
|
|
|
112
120
|
dependencySkipped: 0,
|
|
113
121
|
failures: failed > 0 ? [{ message: 'group failure' }] : [],
|
|
114
122
|
});
|
|
123
|
+
const themeSummary = (failed = 0) => ({
|
|
124
|
+
created: failed > 0 ? 1 : 0,
|
|
125
|
+
updated: 0,
|
|
126
|
+
unchanged: 0,
|
|
127
|
+
failed,
|
|
128
|
+
completedSlugs: failed > 0 ? ['processed-theme'] : [],
|
|
129
|
+
failures: failed > 0 ? [{ message: 'theme failure' }] : [],
|
|
130
|
+
});
|
|
115
131
|
beforeEach(() => {
|
|
116
132
|
vi.clearAllMocks();
|
|
117
133
|
vi.spyOn(globalState_1.default, 'startSpinner').mockReturnValue(spinner);
|
|
@@ -120,10 +136,16 @@ describe('organization content upload sequencing', () => {
|
|
|
120
136
|
vi.mocked(customRoles_1.uploadCustomRoles).mockResolvedValue(customRoleSummary());
|
|
121
137
|
vi.mocked(users_1.uploadUsers).mockResolvedValue(userSummary());
|
|
122
138
|
vi.mocked(groups_1.uploadGroups).mockResolvedValue(groupSummary());
|
|
139
|
+
vi.mocked(themes_1.prepareThemeUploads).mockResolvedValue([]);
|
|
140
|
+
vi.mocked(themes_1.uploadThemes).mockResolvedValue(themeSummary());
|
|
123
141
|
vi.mocked(groups_1.countDependencySkippedGroups).mockResolvedValue(2);
|
|
124
142
|
});
|
|
125
|
-
it('
|
|
143
|
+
it('preflights themes, then awaits organization resource uploads in order', async () => {
|
|
126
144
|
const phaseOrder = [];
|
|
145
|
+
vi.mocked(themes_1.prepareThemeUploads).mockImplementation(async () => {
|
|
146
|
+
phaseOrder.push('themes:preflight:start', 'themes:preflight:end');
|
|
147
|
+
return [];
|
|
148
|
+
});
|
|
127
149
|
vi.mocked(customRoles_1.uploadCustomRoles).mockImplementation(async () => {
|
|
128
150
|
phaseOrder.push('roles:start', 'roles:end');
|
|
129
151
|
return customRoleSummary();
|
|
@@ -136,19 +158,27 @@ describe('organization content upload sequencing', () => {
|
|
|
136
158
|
phaseOrder.push('groups:start', 'groups:end');
|
|
137
159
|
return groupSummary();
|
|
138
160
|
});
|
|
161
|
+
vi.mocked(themes_1.uploadThemes).mockImplementation(async () => {
|
|
162
|
+
phaseOrder.push('themes:start', 'themes:end');
|
|
163
|
+
return themeSummary();
|
|
164
|
+
});
|
|
139
165
|
await (0, index_1.uploadOrganizationContent)({ config });
|
|
140
166
|
expect(phaseOrder).toStrictEqual([
|
|
167
|
+
'themes:preflight:start',
|
|
168
|
+
'themes:preflight:end',
|
|
141
169
|
'roles:start',
|
|
142
170
|
'roles:end',
|
|
143
171
|
'users:start',
|
|
144
172
|
'users:end',
|
|
145
173
|
'groups:start',
|
|
146
174
|
'groups:end',
|
|
175
|
+
'themes:start',
|
|
176
|
+
'themes:end',
|
|
147
177
|
]);
|
|
148
178
|
});
|
|
149
179
|
it('reports a duration for every uploaded resource', async () => {
|
|
150
180
|
await (0, index_1.uploadOrganizationContent)({ config });
|
|
151
|
-
expect(spinner.succeed).toHaveBeenCalledTimes(
|
|
181
|
+
expect(spinner.succeed).toHaveBeenCalledTimes(4);
|
|
152
182
|
spinner.succeed.mock.calls.forEach(([message]) => expect(message).toMatch(/\(\d+ms\)$/));
|
|
153
183
|
});
|
|
154
184
|
it('does not start users or groups when custom roles fail', async () => {
|
|
@@ -156,12 +186,27 @@ describe('organization content upload sequencing', () => {
|
|
|
156
186
|
await expect((0, index_1.uploadOrganizationContent)({ config })).rejects.toThrow('Processed custom roles');
|
|
157
187
|
expect(users_1.uploadUsers).not.toHaveBeenCalled();
|
|
158
188
|
expect(groups_1.uploadGroups).not.toHaveBeenCalled();
|
|
189
|
+
expect(themes_1.uploadThemes).not.toHaveBeenCalled();
|
|
159
190
|
expect(groups_1.countDependencySkippedGroups).toHaveBeenCalledOnce();
|
|
160
191
|
});
|
|
161
192
|
it('does not start groups when users fail', async () => {
|
|
162
193
|
vi.mocked(users_1.uploadUsers).mockResolvedValue(userSummary(1));
|
|
163
194
|
await expect((0, index_1.uploadOrganizationContent)({ config })).rejects.toThrow('Processed users');
|
|
164
195
|
expect(groups_1.uploadGroups).not.toHaveBeenCalled();
|
|
196
|
+
expect(themes_1.uploadThemes).not.toHaveBeenCalled();
|
|
165
197
|
expect(groups_1.countDependencySkippedGroups).toHaveBeenCalledOnce();
|
|
166
198
|
});
|
|
199
|
+
it('does not mutate organization resources when theme preflight fails', async () => {
|
|
200
|
+
vi.mocked(themes_1.prepareThemeUploads).mockRejectedValue(new Error('Invalid theme'));
|
|
201
|
+
await expect((0, index_1.uploadOrganizationContent)({ config })).rejects.toThrow('Invalid theme');
|
|
202
|
+
expect(customRoles_1.uploadCustomRoles).not.toHaveBeenCalled();
|
|
203
|
+
expect(users_1.uploadUsers).not.toHaveBeenCalled();
|
|
204
|
+
expect(groups_1.uploadGroups).not.toHaveBeenCalled();
|
|
205
|
+
expect(themes_1.uploadThemes).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
it('fails after reporting a partial theme upload', async () => {
|
|
208
|
+
vi.mocked(themes_1.uploadThemes).mockResolvedValue(themeSummary(1));
|
|
209
|
+
await expect((0, index_1.uploadOrganizationContent)({ config })).rejects.toThrow('Processed themes: 1 created, 0 updated, 0 unchanged, 1 failed');
|
|
210
|
+
expect(globalState_1.default.log).toHaveBeenCalledWith(expect.stringContaining('processed-theme'));
|
|
211
|
+
});
|
|
167
212
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type OrganizationDesignFileKind, type OrganizationDesignPackageManifest } from '@lightdash/common';
|
|
2
|
+
type ThemePackageFile = {
|
|
3
|
+
kind: OrganizationDesignFileKind;
|
|
4
|
+
filename: string;
|
|
5
|
+
contentType: string;
|
|
6
|
+
body: Buffer;
|
|
7
|
+
};
|
|
8
|
+
export type ParsedThemePackage = {
|
|
9
|
+
manifest: OrganizationDesignPackageManifest;
|
|
10
|
+
files: ThemePackageFile[];
|
|
11
|
+
};
|
|
12
|
+
export type PreparedThemeUpload = {
|
|
13
|
+
directory: string;
|
|
14
|
+
manifest: OrganizationDesignPackageManifest;
|
|
15
|
+
archive: Buffer;
|
|
16
|
+
};
|
|
17
|
+
export declare const getThemesFolder: (organizationContentPath: string) => string;
|
|
18
|
+
export declare const prepareThemeUploads: (organizationContentPath: string) => Promise<PreparedThemeUpload[]>;
|
|
19
|
+
export declare const parseThemeArchive: (archive: Buffer) => Promise<ParsedThemePackage>;
|
|
20
|
+
export declare const writeThemePackage: (organizationContentPath: string, parsed: ParsedThemePackage) => Promise<void>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=themePackage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themePackage.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/themePackage.ts"],"names":[],"mappings":"AACA,OAAO,EAkBH,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACzC,MAAM,mBAAmB,CAAC;AAgB3B,KAAK,gBAAgB,GAAG;IACpB,IAAI,EAAE,0BAA0B,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC7B,QAAQ,EAAE,iCAAiC,CAAC;IAC5C,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iCAAiC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,eAAe,4BAA6B,MAAM,KAAG,MAClB,CAAC;AAiRjD,eAAO,MAAM,mBAAmB,4BACH,MAAM,KAChC,OAAO,CAAC,mBAAmB,EAAE,CA4D/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,YACjB,MAAM,KAChB,OAAO,CAAC,kBAAkB,CAwJvB,CAAC;AA6BP,eAAO,MAAM,iBAAiB,4BACD,MAAM,UACvB,kBAAkB,KAC3B,OAAO,CAAC,IAAI,CA2Cd,CAAC"}
|