@lightdash/cli 2.215.3 → 2.217.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/projectResources.d.ts +2 -1
- package/dist/handlers/contentAsCode/projectResources.d.ts.map +1 -1
- package/dist/handlers/contentAsCode/projectResources.js +19 -1
- package/dist/handlers/contentAsCode/registry.d.ts.map +1 -1
- package/dist/handlers/contentAsCode/registry.js +1 -0
- package/dist/handlers/contentAsCode/resource.test.js +32 -0
- package/dist/handlers/download.d.ts +5 -1
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +32 -1
- package/dist/handlers/download.test.js +5 -1
- package/dist/handlers/homepagesAsCode.d.ts +3 -0
- package/dist/handlers/homepagesAsCode.d.ts.map +1 -0
- package/dist/handlers/homepagesAsCode.js +79 -0
- package/dist/handlers/homepagesAsCode.test.d.ts +2 -0
- package/dist/handlers/homepagesAsCode.test.d.ts.map +1 -0
- package/dist/handlers/homepagesAsCode.test.js +72 -0
- package/dist/index.js +5 -0
- package/dist/vendor/template/.claude/skills/reusable-visualization/SKILL.md +10 -25
- package/dist/vendor/template/.claude/skills/sdk-features/SKILL.md +10 -5
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type AgentAsCode, type AlertAsCode, type ExternalConnectionAsCode, type GoogleSheetsSyncAsCode, type ScheduledDeliveryAsCode, type VirtualViewAsCode } from '@lightdash/common';
|
|
1
|
+
import { type AgentAsCode, type AlertAsCode, type ExternalConnectionAsCode, type GoogleSheetsSyncAsCode, type HomepageAsCode, type ScheduledDeliveryAsCode, type VirtualViewAsCode } from '@lightdash/common';
|
|
2
2
|
import type { CodeResourceDefinition } from './resource';
|
|
3
|
+
export declare const HOMEPAGE_CODE_RESOURCE: CodeResourceDefinition<HomepageAsCode>;
|
|
3
4
|
export declare const VIRTUAL_VIEW_CODE_RESOURCE: CodeResourceDefinition<VirtualViewAsCode>;
|
|
4
5
|
export declare const AI_AGENT_CODE_RESOURCE: CodeResourceDefinition<AgentAsCode>;
|
|
5
6
|
export declare const EXTERNAL_CONNECTION_CODE_RESOURCE: CodeResourceDefinition<ExternalConnectionAsCode>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectResources.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/projectResources.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"projectResources.d.ts","sourceRoot":"","sources":["../../../src/handlers/contentAsCode/projectResources.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,eAAO,MAAM,sBAAsB,EAAE,sBAAsB,CAAC,cAAc,CAiBzE,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,sBAAsB,CAAC,iBAAiB,CAwB5E,CAAC;AAEN,eAAO,MAAM,sBAAsB,EAAE,sBAAsB,CAAC,WAAW,CAyBtE,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,sBAAsB,CAAC,wBAAwB,CAwB1F,CAAC;AA4CN,eAAO,MAAM,gCAAgC,iDAKvC,CAAC;AAEP,eAAO,MAAM,mBAAmB,qCAI9B,CAAC;AAEH,eAAO,MAAM,2BAA2B,gDAKlC,CAAC"}
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GOOGLE_SHEETS_CODE_RESOURCE = exports.ALERT_CODE_RESOURCE = exports.SCHEDULED_DELIVERY_CODE_RESOURCE = exports.EXTERNAL_CONNECTION_CODE_RESOURCE = exports.AI_AGENT_CODE_RESOURCE = exports.VIRTUAL_VIEW_CODE_RESOURCE = void 0;
|
|
3
|
+
exports.GOOGLE_SHEETS_CODE_RESOURCE = exports.ALERT_CODE_RESOURCE = exports.SCHEDULED_DELIVERY_CODE_RESOURCE = exports.EXTERNAL_CONNECTION_CODE_RESOURCE = exports.AI_AGENT_CODE_RESOURCE = exports.VIRTUAL_VIEW_CODE_RESOURCE = exports.HOMEPAGE_CODE_RESOURCE = void 0;
|
|
4
4
|
const common_1 = require("@lightdash/common");
|
|
5
|
+
exports.HOMEPAGE_CODE_RESOURCE = {
|
|
6
|
+
kind: common_1.ContentAsCodeType.HOMEPAGE,
|
|
7
|
+
displayLabel: 'homepage',
|
|
8
|
+
identityLabel: 'name',
|
|
9
|
+
scope: 'project',
|
|
10
|
+
folderName: 'homepages',
|
|
11
|
+
acceptedExtensions: ['.yml', '.yaml'],
|
|
12
|
+
fileName: {
|
|
13
|
+
strategy: 'uriEncodedIdentity',
|
|
14
|
+
fallbackPrefix: 'homepage',
|
|
15
|
+
extension: '.yml',
|
|
16
|
+
},
|
|
17
|
+
dependencies: ['space', 'chart', 'dashboard'],
|
|
18
|
+
identity: ({ name }) => name,
|
|
19
|
+
displayName: ({ name }) => name,
|
|
20
|
+
parse: common_1.parseHomepageAsCode,
|
|
21
|
+
sort: (a, b) => a.name.localeCompare(b.name),
|
|
22
|
+
};
|
|
5
23
|
exports.VIRTUAL_VIEW_CODE_RESOURCE = {
|
|
6
24
|
kind: common_1.ContentAsCodeType.VIRTUAL_VIEW,
|
|
7
25
|
displayLabel: 'virtual view',
|
|
@@ -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;AAe3B,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,EAyBrE,CAAC;AAEF,eAAO,MAAM,iCAAiC,cAC/B,SAAS,wBAAwB,EAAE,KAC/C,IAeF,CAAC"}
|
|
@@ -37,6 +37,7 @@ exports.PROJECT_CODE_RESOURCES = [
|
|
|
37
37
|
projectResources_1.ALERT_CODE_RESOURCE,
|
|
38
38
|
projectResources_1.GOOGLE_SHEETS_CODE_RESOURCE,
|
|
39
39
|
projectResources_1.EXTERNAL_CONNECTION_CODE_RESOURCE,
|
|
40
|
+
projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
40
41
|
];
|
|
41
42
|
const assertCodeResourceDependencyOrder = (resources) => {
|
|
42
43
|
const completed = new Set();
|
|
@@ -32,6 +32,38 @@ const agent = (slug) => ({
|
|
|
32
32
|
})));
|
|
33
33
|
});
|
|
34
34
|
(0, vitest_1.describe)('content-as-code resource files', () => {
|
|
35
|
+
(0, vitest_1.it)('round trips exact homepage names safely and rejects duplicate local identities', async () => {
|
|
36
|
+
const basePath = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'lightdash-homepage-code-'));
|
|
37
|
+
temporaryDirectories.push(basePath);
|
|
38
|
+
const document = {
|
|
39
|
+
contentType: common_1.ContentAsCodeType.HOMEPAGE,
|
|
40
|
+
version: 1,
|
|
41
|
+
name: '../Home / 100%',
|
|
42
|
+
config: { version: 1, rows: [] },
|
|
43
|
+
publication: null,
|
|
44
|
+
};
|
|
45
|
+
await (0, resource_1.writeCodeResourceDocuments)({
|
|
46
|
+
definition: projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
47
|
+
basePath,
|
|
48
|
+
documents: [document],
|
|
49
|
+
pruneOtherDocuments: true,
|
|
50
|
+
});
|
|
51
|
+
const fileNames = await fs_1.promises.readdir(path_1.default.join(basePath, 'homepages'));
|
|
52
|
+
(0, vitest_1.expect)(fileNames).toHaveLength(1);
|
|
53
|
+
(0, vitest_1.expect)(fileNames[0]).not.toContain('/');
|
|
54
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
55
|
+
definition: projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
56
|
+
basePath,
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.expect)(result.files[0].document).toEqual(document);
|
|
59
|
+
await fs_1.promises.copyFile(path_1.default.join(basePath, 'homepages', fileNames[0]), path_1.default.join(basePath, 'homepages', 'duplicate.yaml'));
|
|
60
|
+
const duplicates = await (0, resource_1.readCodeResourceFiles)({
|
|
61
|
+
definition: projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
62
|
+
basePath,
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.expect)(duplicates.files).toEqual([]);
|
|
65
|
+
(0, vitest_1.expect)(duplicates.failures[0].message).toContain('Duplicate');
|
|
66
|
+
});
|
|
35
67
|
(0, vitest_1.it)('preserves other documents when writing a filtered download', async () => {
|
|
36
68
|
const basePath = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'lightdash-content-as-code-'));
|
|
37
69
|
temporaryDirectories.push(basePath);
|
|
@@ -10,6 +10,10 @@ export type DownloadHandlerOptions = {
|
|
|
10
10
|
googleSheets: string[];
|
|
11
11
|
scheduledDeliveries: string[];
|
|
12
12
|
virtualViews: string[];
|
|
13
|
+
homepages?: string[];
|
|
14
|
+
includeHomepages?: boolean;
|
|
15
|
+
skipHomepages?: boolean;
|
|
16
|
+
publish?: boolean;
|
|
13
17
|
externalConnections: string[];
|
|
14
18
|
apps?: string[];
|
|
15
19
|
chartTypes?: string[];
|
|
@@ -57,7 +61,7 @@ export type DownloadHandlerOptions = {
|
|
|
57
61
|
sendInvites?: boolean;
|
|
58
62
|
};
|
|
59
63
|
declare const shouldDownloadAiAgents: ({ includeAll, includeAgents, agents, appsOnly, }: Pick<DownloadHandlerOptions, 'includeAll' | 'includeAgents' | 'agents' | 'appsOnly'>) => boolean;
|
|
60
|
-
declare const hasContentFilters: ({ spacesOnly, charts, dashboards, agents, alerts, googleSheets, scheduledDeliveries, virtualViews, externalConnections, apps, chartTypes, }: Pick<DownloadHandlerOptions, 'spacesOnly' | 'charts' | 'dashboards' | 'agents' | 'alerts' | 'googleSheets' | 'scheduledDeliveries' | 'virtualViews' | 'externalConnections' | 'apps' | 'chartTypes'>) => boolean;
|
|
64
|
+
declare const hasContentFilters: ({ spacesOnly, charts, dashboards, agents, alerts, googleSheets, scheduledDeliveries, virtualViews, externalConnections, homepages, apps, chartTypes, }: Pick<DownloadHandlerOptions, 'spacesOnly' | 'charts' | 'dashboards' | 'agents' | 'alerts' | 'googleSheets' | 'scheduledDeliveries' | 'virtualViews' | 'externalConnections' | 'homepages' | 'apps' | 'chartTypes'>) => boolean;
|
|
61
65
|
declare const parseContentFilters: (items: string[]) => string;
|
|
62
66
|
type MetadataEntry = {
|
|
63
67
|
slug: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,WAAW,EA0BX,WAAW,EAIX,eAAe,EAGf,wBAAwB,EAoBxB,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,EAIH,KAAK,0BAA0B,EAClC,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,13 +28,14 @@ const projectResources_1 = require("./contentAsCode/projectResources");
|
|
|
28
28
|
const resource_1 = require("./contentAsCode/resource");
|
|
29
29
|
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
30
30
|
const apiClient_1 = require("./dbt/apiClient");
|
|
31
|
+
const homepagesAsCode_1 = require("./homepagesAsCode");
|
|
31
32
|
const metadataFile_1 = require("./metadataFile");
|
|
32
33
|
const organizationContent_1 = require("./organizationContent");
|
|
33
34
|
const selectProject_1 = require("./selectProject");
|
|
34
35
|
const spacesAsCode_1 = require("./spacesAsCode");
|
|
35
36
|
const shouldDownloadAiAgents = ({ includeAll, includeAgents, agents, appsOnly, }) => appsOnly !== true &&
|
|
36
37
|
(includeAll === true || includeAgents === true || agents.length > 0);
|
|
37
|
-
const hasContentFilters = ({ spacesOnly, charts, dashboards, agents, alerts, googleSheets, scheduledDeliveries, virtualViews, externalConnections, apps, chartTypes, }) => !spacesOnly &&
|
|
38
|
+
const hasContentFilters = ({ spacesOnly, charts, dashboards, agents, alerts, googleSheets, scheduledDeliveries, virtualViews, externalConnections, homepages, apps, chartTypes, }) => !spacesOnly &&
|
|
38
39
|
[
|
|
39
40
|
charts,
|
|
40
41
|
dashboards,
|
|
@@ -44,6 +45,7 @@ const hasContentFilters = ({ spacesOnly, charts, dashboards, agents, alerts, goo
|
|
|
44
45
|
scheduledDeliveries,
|
|
45
46
|
virtualViews,
|
|
46
47
|
externalConnections,
|
|
48
|
+
homepages ?? [],
|
|
47
49
|
apps ?? [],
|
|
48
50
|
chartTypes ?? [],
|
|
49
51
|
].some((filters) => filters.length > 0);
|
|
@@ -1154,6 +1156,20 @@ const downloadHandler = async (options) => {
|
|
|
1154
1156
|
// chart types step.
|
|
1155
1157
|
let downloadedChartVizRefs = [];
|
|
1156
1158
|
const explicitChartTypeRefs = new Set((Array.isArray(options.chartTypes) ? options.chartTypes : []).map(appsDownload_1.getDataAppReference));
|
|
1159
|
+
if (!options.appsOnly &&
|
|
1160
|
+
!options.chartTypesOnly &&
|
|
1161
|
+
!options.spacesOnly &&
|
|
1162
|
+
(options.includeHomepages ||
|
|
1163
|
+
options.homepages?.length ||
|
|
1164
|
+
includeAllOptionalContent)) {
|
|
1165
|
+
await output.runItem({
|
|
1166
|
+
label: 'Homepages',
|
|
1167
|
+
action: () => (0, homepagesAsCode_1.downloadHomepages)(projectId, options.homepages ?? [], options.path, includeAllOptionalContent &&
|
|
1168
|
+
!options.includeHomepages &&
|
|
1169
|
+
!options.homepages?.length),
|
|
1170
|
+
detail: (count) => `${count} downloaded`,
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1157
1173
|
if (shouldDownloadSpaces) {
|
|
1158
1174
|
output.startItem('Spaces');
|
|
1159
1175
|
try {
|
|
@@ -2806,6 +2822,21 @@ const uploadHandler = async (options) => {
|
|
|
2806
2822
|
});
|
|
2807
2823
|
}
|
|
2808
2824
|
}
|
|
2825
|
+
if (!options.skipHomepages &&
|
|
2826
|
+
!options.appsOnly &&
|
|
2827
|
+
!options.chartTypesOnly &&
|
|
2828
|
+
(!hasFilters || options.homepages?.length)) {
|
|
2829
|
+
changes = await runUploadChangesPhase({
|
|
2830
|
+
output,
|
|
2831
|
+
label: 'Homepages',
|
|
2832
|
+
changes,
|
|
2833
|
+
action: () => {
|
|
2834
|
+
if (hasUploadFailures(changes))
|
|
2835
|
+
throw new common_1.ParameterError('Homepage upload blocked because prerequisite content uploads failed');
|
|
2836
|
+
return (0, homepagesAsCode_1.uploadHomepages)(projectId, options.homepages ?? [], changes, options.publish === true, contentPathOption);
|
|
2837
|
+
},
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2809
2840
|
const end = Date.now();
|
|
2810
2841
|
await analytics_1.LightdashAnalytics.track({
|
|
2811
2842
|
event: 'upload.completed',
|
|
@@ -130,6 +130,9 @@ const makeContentFilterOptions = (overrides = {}) => ({
|
|
|
130
130
|
...overrides,
|
|
131
131
|
});
|
|
132
132
|
describe('hasContentFilters', () => {
|
|
133
|
+
it('treats explicit homepage names as a filtered download and upload', () => {
|
|
134
|
+
expect(hasContentFilters(makeContentFilterOptions({ homepages: ['Team homepage'] }))).toBe(true);
|
|
135
|
+
});
|
|
133
136
|
it('treats explicit apps as a filtered download and upload', () => {
|
|
134
137
|
expect(hasContentFilters(makeContentFilterOptions({ apps: ['app-reference'] }))).toBe(true);
|
|
135
138
|
});
|
|
@@ -1026,7 +1029,8 @@ describe('downloadHandler failures', () => {
|
|
|
1026
1029
|
missingSlugs: [],
|
|
1027
1030
|
};
|
|
1028
1031
|
}
|
|
1029
|
-
if (url.includes('/code/aiAgents')
|
|
1032
|
+
if (url.includes('/code/aiAgents') ||
|
|
1033
|
+
url.includes('/code/homepages')) {
|
|
1030
1034
|
throw unavailableError;
|
|
1031
1035
|
}
|
|
1032
1036
|
if (url.includes('/code/alerts')) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const downloadHomepages: (projectUuid: string, names: string[], customPath?: string, implicit?: boolean) => Promise<number>;
|
|
2
|
+
export declare const uploadHomepages: (projectUuid: string, names: string[], changes: Record<string, number>, publish: boolean, customPath?: string) => Promise<Record<string, number>>;
|
|
3
|
+
//# sourceMappingURL=homepagesAsCode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homepagesAsCode.d.ts","sourceRoot":"","sources":["../../src/handlers/homepagesAsCode.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,iBAAiB,gBACb,MAAM,SACZ,MAAM,EAAE,eACF,MAAM,aACT,OAAO,KAClB,OAAO,CAAC,MAAM,CAqChB,CAAC;AAEF,eAAO,MAAM,eAAe,gBACX,MAAM,SACZ,MAAM,EAAE,WACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WACtB,OAAO,eACH,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8ChC,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uploadHomepages = exports.downloadHomepages = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/* eslint-disable no-await-in-loop */
|
|
6
|
+
const common_1 = require("@lightdash/common");
|
|
7
|
+
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
8
|
+
const styles = tslib_1.__importStar(require("../styles"));
|
|
9
|
+
const projectResources_1 = require("./contentAsCode/projectResources");
|
|
10
|
+
const resource_1 = require("./contentAsCode/resource");
|
|
11
|
+
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
12
|
+
const apiClient_1 = require("./dbt/apiClient");
|
|
13
|
+
const downloadHomepages = async (projectUuid, names, customPath, implicit = false) => {
|
|
14
|
+
const query = new URLSearchParams(names.map((name) => ['names', name]));
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = await (0, apiClient_1.lightdashApi)({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
url: `/api/v1/projects/${projectUuid}/code/homepages?${query}`,
|
|
20
|
+
body: undefined,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (implicit &&
|
|
25
|
+
error instanceof common_1.LightdashError &&
|
|
26
|
+
[403, 404, 422].includes(error.statusCode)) {
|
|
27
|
+
globalState_1.default.log(styles.warning('Skipping homepages: the homepage builder and homepage management permission are required.'));
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
if (result.missingNames.length)
|
|
33
|
+
throw new common_1.ParameterError(`Homepages not found or unpublished: ${result.missingNames.join(', ')}`);
|
|
34
|
+
await (0, resource_1.writeCodeResourceDocuments)({
|
|
35
|
+
definition: projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
36
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
37
|
+
documents: result.homepages,
|
|
38
|
+
pruneOtherDocuments: names.length === 0,
|
|
39
|
+
});
|
|
40
|
+
return result.homepages.length;
|
|
41
|
+
};
|
|
42
|
+
exports.downloadHomepages = downloadHomepages;
|
|
43
|
+
const uploadHomepages = async (projectUuid, names, changes, publish, customPath) => {
|
|
44
|
+
const uploadChanges = { ...changes };
|
|
45
|
+
const files = await (0, resource_1.readCodeResourceFiles)({
|
|
46
|
+
definition: projectResources_1.HOMEPAGE_CODE_RESOURCE,
|
|
47
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
48
|
+
});
|
|
49
|
+
(0, resource_1.assertCodeResourceFilesValid)(files);
|
|
50
|
+
const documents = files.files
|
|
51
|
+
.map((file) => file.document)
|
|
52
|
+
.filter((document) => names.length === 0 || names.includes(document.name));
|
|
53
|
+
const missing = names.filter((name) => !documents.some((document) => document.name === name));
|
|
54
|
+
if (missing.length)
|
|
55
|
+
throw new common_1.ParameterError(`Homepages not found locally: ${missing.join(', ')}`);
|
|
56
|
+
for (const document of documents) {
|
|
57
|
+
try {
|
|
58
|
+
const result = await (0, apiClient_1.lightdashApi)({
|
|
59
|
+
method: 'POST',
|
|
60
|
+
url: `/api/v1/projects/${projectUuid}/code/homepages/${encodeURIComponent(document.name)}?publish=${publish}`,
|
|
61
|
+
body: JSON.stringify(document),
|
|
62
|
+
});
|
|
63
|
+
const actionLabels = {
|
|
64
|
+
[common_1.PromotionAction.CREATE]: 'created',
|
|
65
|
+
[common_1.PromotionAction.UPDATE]: 'updated',
|
|
66
|
+
[common_1.PromotionAction.NO_CHANGES]: 'unchanged',
|
|
67
|
+
};
|
|
68
|
+
const key = `homepages ${actionLabels[result.action]}`;
|
|
69
|
+
uploadChanges[key] = (uploadChanges[key] ?? 0) + 1;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
uploadChanges['homepages with errors'] =
|
|
73
|
+
(uploadChanges['homepages with errors'] ?? 0) + 1;
|
|
74
|
+
globalState_1.default.log(styles.error(`Error uploading homepage "${document.name}": ${(0, common_1.getErrorMessage)(error)}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return uploadChanges;
|
|
78
|
+
};
|
|
79
|
+
exports.uploadHomepages = uploadHomepages;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homepagesAsCode.test.d.ts","sourceRoot":"","sources":["../../src/handlers/homepagesAsCode.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const common_1 = require("@lightdash/common");
|
|
4
|
+
const vitest_1 = require("vitest");
|
|
5
|
+
const resource_1 = require("./contentAsCode/resource");
|
|
6
|
+
const apiClient_1 = require("./dbt/apiClient");
|
|
7
|
+
const homepagesAsCode_1 = require("./homepagesAsCode");
|
|
8
|
+
vitest_1.vi.mock('./dbt/apiClient', () => ({ lightdashApi: vitest_1.vi.fn() }));
|
|
9
|
+
vitest_1.vi.mock('./contentAsCode/resource', () => ({
|
|
10
|
+
readCodeResourceFiles: vitest_1.vi.fn(),
|
|
11
|
+
writeCodeResourceDocuments: vitest_1.vi.fn(),
|
|
12
|
+
assertCodeResourceFilesValid: vitest_1.vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
vitest_1.vi.mock('../globalState', () => ({ default: { log: vitest_1.vi.fn() } }));
|
|
15
|
+
const document = {
|
|
16
|
+
contentType: common_1.ContentAsCodeType.HOMEPAGE,
|
|
17
|
+
version: 1,
|
|
18
|
+
name: 'Home / Team',
|
|
19
|
+
config: { version: 1, rows: [] },
|
|
20
|
+
publication: null,
|
|
21
|
+
};
|
|
22
|
+
(0, vitest_1.describe)('homepage CLI download/upload', () => {
|
|
23
|
+
(0, vitest_1.beforeEach)(() => vitest_1.vi.clearAllMocks());
|
|
24
|
+
(0, vitest_1.it)('requests exact names and preserves other files during a filtered download', async () => {
|
|
25
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockResolvedValue({
|
|
26
|
+
homepages: [document],
|
|
27
|
+
missingNames: [],
|
|
28
|
+
});
|
|
29
|
+
await (0, homepagesAsCode_1.downloadHomepages)('project', [document.name], '/tmp/homepage-code');
|
|
30
|
+
(0, vitest_1.expect)(apiClient_1.lightdashApi).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
31
|
+
url: '/api/v1/projects/project/code/homepages?names=Home+%2F+Team',
|
|
32
|
+
}));
|
|
33
|
+
(0, vitest_1.expect)(resource_1.writeCodeResourceDocuments).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
34
|
+
documents: [document],
|
|
35
|
+
pruneOtherDocuments: false,
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)('skips unavailable homepages only for implicit include-all downloads', async () => {
|
|
39
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValue(new common_1.ForbiddenError());
|
|
40
|
+
await (0, vitest_1.expect)((0, homepagesAsCode_1.downloadHomepages)('project', [], undefined, true)).resolves.toBe(0);
|
|
41
|
+
await (0, vitest_1.expect)((0, homepagesAsCode_1.downloadHomepages)('project', [], undefined, false)).rejects.toThrow();
|
|
42
|
+
(0, vitest_1.expect)(resource_1.writeCodeResourceDocuments).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.it)('passes the publication option explicitly and reports unchanged uploads', async () => {
|
|
45
|
+
vitest_1.vi.mocked(resource_1.readCodeResourceFiles).mockResolvedValue({
|
|
46
|
+
files: [{ filePath: 'home.yml', document }],
|
|
47
|
+
failures: [],
|
|
48
|
+
});
|
|
49
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockResolvedValue({
|
|
50
|
+
action: common_1.PromotionAction.NO_CHANGES,
|
|
51
|
+
});
|
|
52
|
+
await (0, vitest_1.expect)((0, homepagesAsCode_1.uploadHomepages)('project', [], {}, false)).resolves.toEqual({ 'homepages unchanged': 1 });
|
|
53
|
+
(0, vitest_1.expect)(apiClient_1.lightdashApi).toHaveBeenLastCalledWith(vitest_1.expect.objectContaining({
|
|
54
|
+
method: 'POST',
|
|
55
|
+
url: '/api/v1/projects/project/code/homepages/Home%20%2F%20Team?publish=false',
|
|
56
|
+
}));
|
|
57
|
+
await (0, homepagesAsCode_1.uploadHomepages)('project', [], {}, true);
|
|
58
|
+
(0, vitest_1.expect)(apiClient_1.lightdashApi).toHaveBeenLastCalledWith(vitest_1.expect.objectContaining({
|
|
59
|
+
url: '/api/v1/projects/project/code/homepages/Home%20%2F%20Team?publish=true',
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)('fails missing selections and records API failures for a nonzero CLI exit', async () => {
|
|
63
|
+
vitest_1.vi.mocked(resource_1.readCodeResourceFiles).mockResolvedValue({
|
|
64
|
+
files: [{ filePath: 'home.yml', document }],
|
|
65
|
+
failures: [],
|
|
66
|
+
});
|
|
67
|
+
await (0, vitest_1.expect)((0, homepagesAsCode_1.uploadHomepages)('project', ['Missing'], {}, false)).rejects.toThrow('not found locally');
|
|
68
|
+
(0, vitest_1.expect)(apiClient_1.lightdashApi).not.toHaveBeenCalled();
|
|
69
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockRejectedValue(new Error('Missing reference'));
|
|
70
|
+
await (0, vitest_1.expect)((0, homepagesAsCode_1.uploadHomepages)('project', [], {}, false)).resolves.toEqual({ 'homepages with errors': 1 });
|
|
71
|
+
});
|
|
72
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -368,6 +368,8 @@ const downloadCommand = commander_1.program
|
|
|
368
368
|
.option('--virtual-views <slugs...>', 'specify virtual view slugs to download', refsArgument_1.parseRefsArgument, [])
|
|
369
369
|
.option('--google-sheets <slugs...>', 'specify Google Sheets sync slugs to download', refsArgument_1.parseRefsArgument, [])
|
|
370
370
|
.option('--scheduled-deliveries <slugs...>', 'specify scheduled delivery slugs to download', refsArgument_1.parseRefsArgument, [])
|
|
371
|
+
.option('--homepages <names...>', 'specify exact homepage names to download', [])
|
|
372
|
+
.option('--include-homepages', 'include all published homepages (enterprise)', false)
|
|
371
373
|
.option('--external-connections <slugs...>', 'specify external connection slugs to download (enterprise)', refsArgument_1.parseRefsArgument, [])
|
|
372
374
|
.option('--include-external-connections', "include all of the project's data-app external connections (enterprise)", false)
|
|
373
375
|
.option('-l, --language-map', 'generate a language maps for the downloaded charts and dashboards', false)
|
|
@@ -406,6 +408,9 @@ const uploadCommand = commander_1.program
|
|
|
406
408
|
.option('--virtual-views <slugs...>', 'specify virtual view slugs to upload', refsArgument_1.parseRefsArgument, [])
|
|
407
409
|
.option('--google-sheets <slugs...>', 'specify Google Sheets sync slugs to upload', refsArgument_1.parseRefsArgument, [])
|
|
408
410
|
.option('--scheduled-deliveries <slugs...>', 'specify scheduled delivery slugs to upload', refsArgument_1.parseRefsArgument, [])
|
|
411
|
+
.option('--homepages <names...>', 'specify exact homepage names to upload', [])
|
|
412
|
+
.option('--skip-homepages', 'skip uploading homepages', false)
|
|
413
|
+
.option('--publish', 'publish uploaded homepages and apply their audience assignments', false)
|
|
409
414
|
.option('--external-connections <slugs...>', 'specify external connection slugs to upload (enterprise)', refsArgument_1.parseRefsArgument, [])
|
|
410
415
|
.option('--force', 'Force upload unchanged files and allow destructive virtual-view column changes', false)
|
|
411
416
|
.option('-p, --path <path>', 'specify a custom path to upload content from', undefined)
|
|
@@ -176,9 +176,10 @@ action menu. This is part of the component contract, not an optional nicety —
|
|
|
176
176
|
item shows; you always write the wiring, and it costs nothing visually when disabled.
|
|
177
177
|
|
|
178
178
|
When `underlyingData.enabled` is true, viewers get the standard Lightdash action on
|
|
179
|
-
chart marks: click a data point → small action menu → "View underlying data" →
|
|
180
|
-
listing the raw rows behind that point, with
|
|
181
|
-
too old, viewer lacks permission, embed), render no menu
|
|
179
|
+
chart marks: click a data point → small action menu → "View underlying data" → the
|
|
180
|
+
standard Lightdash dialog listing the raw rows behind that point, with its Download
|
|
181
|
+
button. When it is false (host too old, viewer lacks permission, embed), render no menu
|
|
182
|
+
item — never a disabled one.
|
|
182
183
|
|
|
183
184
|
Provenance is the contract: **every interactive datum keeps a reference to its
|
|
184
185
|
untransformed source row.** When mapping `rows` into chart data, carry the row:
|
|
@@ -195,33 +196,17 @@ Only attach the action where one mark maps to exactly ONE source row and ONE met
|
|
|
195
196
|
field. A mark that aggregates several rows (a binned bucket, a "top N + other" slice)
|
|
196
197
|
gets no underlying-data item.
|
|
197
198
|
|
|
198
|
-
On click,
|
|
199
|
+
On click, send the intent and render nothing else. Lightdash opens the dialog outside
|
|
200
|
+
the viz and owns its table, loading, error, sorting, and download controls:
|
|
199
201
|
|
|
200
202
|
```tsx
|
|
201
|
-
|
|
202
|
-
// result.rows / result.columns / result.format — render as a table
|
|
203
|
-
// Download button:
|
|
204
|
-
// await underlyingData.download({ row: datum.sourceRow, metric: 'value', fileType: 'csv' });
|
|
203
|
+
underlyingData.open({ row: datum.sourceRow, metric: 'value' }).catch(() => {});
|
|
205
204
|
```
|
|
206
205
|
|
|
207
206
|
`metric` is the declared field NAME from your `fields` (the same key you read from
|
|
208
|
-
`fieldMapping`), not a query field id.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Keep the dialog table dense — underlying data is often wide and long. Compact cell
|
|
212
|
-
padding (the table is for scanning, not presenting). If the header is sticky: pin it
|
|
213
|
-
flush at `top: 0` with an opaque background, and put NO padding on the scroll
|
|
214
|
-
container itself — pad the cells, not the scroller. A sticky header pins to the
|
|
215
|
-
container's content edge, so any container padding becomes a strip that rows
|
|
216
|
-
visibly scroll through above the header.
|
|
217
|
-
|
|
218
|
-
The table must scroll BOTH ways: `overflow: auto` on ONE wrapper directly around the
|
|
219
|
-
`<table>`, and every element between that wrapper and the dialog body a plain block
|
|
220
|
-
that can shrink (`min-width: 0`; no `display: table`, no `w-max`/`width:
|
|
221
|
-
max-content`). A wrapper that sizes to the table's intrinsic width makes the
|
|
222
|
-
overflow container's horizontal scroll dead — the table looks clipped and viewers
|
|
223
|
-
cannot reach the right-hand columns. Verify by scrolling right in the rendered
|
|
224
|
-
dialog with more columns than fit.
|
|
207
|
+
`fieldMapping`), not a query field id. The custom chart type owns only its data-point
|
|
208
|
+
action menu. It never renders an underlying-data dialog, table, loading state, error
|
|
209
|
+
state, or download control inside the iframe.
|
|
225
210
|
|
|
226
211
|
### Drill into a data point
|
|
227
212
|
|
|
@@ -28,6 +28,7 @@ Users describe features by what they see in the Lightdash editor. Translate:
|
|
|
28
28
|
| runs inside a dashboard tile | `viz-context` | required — see below |
|
|
29
29
|
| reusable chart/table with pivoted results | `viz-pivoted-results` | required — see below |
|
|
30
30
|
| "view underlying data", raw rows behind a point (viz only) | `viz-underlying-data` | app code opt-in |
|
|
31
|
+
| Lightdash-owned "View underlying data" dialog (viz only) | `viz-host-underlying-data` | app code opt-in |
|
|
31
32
|
| light/dark mode, "matches my Lightdash theme" | `follow-host-theme` | CSS tokens — see below |
|
|
32
33
|
|
|
33
34
|
## Automatic (zero wiring — active on any current-SDK bundle)
|
|
@@ -119,11 +120,11 @@ order, `sortBy` describes result ordering, `totalColumnCount` exposes truncation
|
|
|
119
120
|
every tab's queries execute during capture while only the active tab stays
|
|
120
121
|
visible — never mount all tabs unconditionally (interactive loads must stay
|
|
121
122
|
lazy).
|
|
122
|
-
- `viz-underlying-data` (vizs only): keep the
|
|
123
|
-
interactive datum, gate a data-point action menu on
|
|
124
|
-
`useVizContext().underlyingData.enabled`,
|
|
125
|
-
`underlyingData.
|
|
126
|
-
|
|
123
|
+
- `viz-underlying-data` and `viz-host-underlying-data` (vizs only): keep the
|
|
124
|
+
untransformed `sourceRow` on each interactive datum, gate a data-point action menu on
|
|
125
|
+
`useVizContext().underlyingData.enabled`, and call
|
|
126
|
+
`underlyingData.open({ row, metric })`. Lightdash renders the dialog and
|
|
127
|
+
owns its table, loading, error, and download controls. Full contract in the
|
|
127
128
|
`reusable-visualization` skill.
|
|
128
129
|
|
|
129
130
|
## After a template upgrade
|
|
@@ -132,3 +133,7 @@ An upgrade rebuilds the app on the current SDK, which turns on the automatic
|
|
|
132
133
|
capabilities but does NOT add wiring — that is a deliberate rail. When you
|
|
133
134
|
offer newly available features, offer wiring-required ones too (their registry
|
|
134
135
|
entries carry a `wiring` note); implement only when the user asks.
|
|
136
|
+
|
|
137
|
+
Existing published visualization bundles are not rewritten by this registry.
|
|
138
|
+
Bundles built before `viz-host-underlying-data` keep their iframe-owned
|
|
139
|
+
`get`/`download` dialog until the user explicitly upgrades that feature.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.217.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@actions/core": "1.11.1",
|
|
23
23
|
"@aws-sdk/credential-providers": "3.995.0",
|
|
24
24
|
"@casl/ability": "6.8.0",
|
|
25
|
-
"@lightdash/common": "2.
|
|
26
|
-
"@lightdash/warehouses": "2.
|
|
25
|
+
"@lightdash/common": "2.217.0",
|
|
26
|
+
"@lightdash/warehouses": "2.217.0",
|
|
27
27
|
"@sidvind/better-ajv-errors": "7.0.0",
|
|
28
28
|
"@types/columnify": "1.5.1",
|
|
29
29
|
"ajv": "8.18.0",
|