@lightdash/cli 0.1418.2 → 0.1419.1
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.
@@ -49,7 +49,13 @@ export declare const lightdashApi: <T extends string | boolean | unknown[] | imp
|
|
49
49
|
owner: string;
|
50
50
|
} | import("@lightdash/common").KnexPaginatedData<import("@lightdash/common").ApiMetricsCatalogResults> | import("@lightdash/common").MetricsExplorerQueryResults | import("@lightdash/common").KnexPaginatedData<import("@lightdash/common").Group[] | import("@lightdash/common").GroupWithMembers[]> | {
|
51
51
|
tagUuid: string;
|
52
|
-
} |
|
52
|
+
} | {
|
53
|
+
charts: import("@lightdash/common").ChartAsCode[];
|
54
|
+
missingIds: string[];
|
55
|
+
} | {
|
56
|
+
dashboards: import("@lightdash/common").DashboardAsCode[];
|
57
|
+
missingIds: string[];
|
58
|
+
} | {
|
53
59
|
edges: import("@lightdash/common").CatalogMetricsTreeEdge[];
|
54
60
|
} | import("@lightdash/common").MetricTotalResults | null | undefined>({ method, url, body, }: LightdashApiProps) => Promise<T>;
|
55
61
|
export declare const checkLightdashVersion: () => Promise<void>;
|
@@ -10,11 +10,26 @@ const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
10
10
|
const path = tslib_1.__importStar(require("path"));
|
11
11
|
const config_1 = require("../config");
|
12
12
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
13
|
+
const styles = tslib_1.__importStar(require("../styles"));
|
13
14
|
const apiClient_1 = require("./dbt/apiClient");
|
14
15
|
const DOWNLOAD_FOLDER = 'lightdash';
|
16
|
+
/*
|
17
|
+
This function is used to parse the content filters.
|
18
|
+
It can be slugs, uuids or urls
|
19
|
+
We remove the URL part (if any) and return a list of `slugs or uuids` that can be used in the API call
|
20
|
+
*/
|
21
|
+
const parseContentFilters = (items) => {
|
22
|
+
if (items.length === 0)
|
23
|
+
return '';
|
24
|
+
const parsedItems = items.map((item) => {
|
25
|
+
const uuidMatch = item.match(/https?:\/\/.+\/(?:saved|dashboards)\/([a-f0-9-]+)/i);
|
26
|
+
return uuidMatch ? uuidMatch[1] : item;
|
27
|
+
});
|
28
|
+
return `?${new URLSearchParams(parsedItems.map((item) => ['ids', item])).toString()}`;
|
29
|
+
};
|
15
30
|
const dumpIntoFiles = async (folder, items) => {
|
16
31
|
const outputDir = path.join(process.cwd(), DOWNLOAD_FOLDER, folder);
|
17
|
-
console.info(`
|
32
|
+
console.info(`Writing ${items.length} ${folder} into ${outputDir}`);
|
18
33
|
// Make directory
|
19
34
|
const created = await fs_1.promises.mkdir(outputDir, { recursive: true });
|
20
35
|
if (created)
|
@@ -80,22 +95,43 @@ const downloadHandler = async (options) => {
|
|
80
95
|
if (!projectId) {
|
81
96
|
throw new Error('No project selected. Run lightdash config set-project');
|
82
97
|
}
|
98
|
+
// If any filter is provided, we skip those items without filters
|
99
|
+
// eg: if a --charts filter is provided, we skip dashboards if no --dashboards filter is provided
|
100
|
+
const hasFilters = options.charts.length > 0 || options.dashboards.length > 0;
|
83
101
|
// Download charts
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
if (hasFilters && options.charts.length === 0) {
|
103
|
+
console.info(styles.warning(`No charts filters provided, skipping`));
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
globalState_1.default.debug(`Downloading charts`);
|
107
|
+
const chartFilters = parseContentFilters(options.charts);
|
108
|
+
const chartsAsCode = await (0, apiClient_1.lightdashApi)({
|
109
|
+
method: 'GET',
|
110
|
+
url: `/api/v1/projects/${projectId}/charts/code${chartFilters}`,
|
111
|
+
body: undefined,
|
112
|
+
});
|
113
|
+
chartsAsCode.missingIds.forEach((missingId) => {
|
114
|
+
console.warn(styles.warning(`No chart with id "${missingId}"`));
|
115
|
+
});
|
116
|
+
await dumpIntoFiles('charts', chartsAsCode.charts);
|
117
|
+
}
|
91
118
|
// Download dashboards
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
119
|
+
if (hasFilters && options.dashboards.length === 0) {
|
120
|
+
console.info(styles.warning(`No dashboards filters provided, skipping`));
|
121
|
+
}
|
122
|
+
else {
|
123
|
+
globalState_1.default.debug(`Downloading dashboards`);
|
124
|
+
const dashboardFilters = parseContentFilters(options.dashboards);
|
125
|
+
const dashboardsAsCode = await (0, apiClient_1.lightdashApi)({
|
126
|
+
method: 'GET',
|
127
|
+
url: `/api/v1/projects/${projectId}/dashboards/code${dashboardFilters}`,
|
128
|
+
body: undefined,
|
129
|
+
});
|
130
|
+
dashboardsAsCode.missingIds.forEach((missingId) => {
|
131
|
+
console.warn(styles.warning(`No dashboard with id "${missingId}"`));
|
132
|
+
});
|
133
|
+
await dumpIntoFiles('dashboards', dashboardsAsCode.dashboards);
|
134
|
+
}
|
99
135
|
// TODO delete files if chart don't exist ?*/
|
100
136
|
};
|
101
137
|
exports.downloadHandler = downloadHandler;
|
@@ -127,13 +163,31 @@ const logUploadChanges = (changes) => {
|
|
127
163
|
console.info(`Total ${key}: ${value} `);
|
128
164
|
});
|
129
165
|
};
|
130
|
-
|
166
|
+
/**
|
167
|
+
*
|
168
|
+
* @param slugs if slugs are provided, we only force upsert the charts/dashboards that match the slugs, if slugs are empty, we upload files that were locally updated
|
169
|
+
*/
|
170
|
+
const upsertResources = async (type, projectId, changes, force, slugs) => {
|
131
171
|
const items = await readCodeFiles(type);
|
132
172
|
console.info(`Found ${items.length} ${type} files`);
|
133
|
-
|
173
|
+
const hasFilter = slugs.length > 0;
|
174
|
+
const filteredItems = hasFilter
|
175
|
+
? items.filter((item) => slugs.includes(item.slug))
|
176
|
+
: items;
|
177
|
+
if (hasFilter) {
|
178
|
+
console.info(`Filtered ${filteredItems.length} ${type} with slugs: ${slugs.join(', ')}`);
|
179
|
+
const missingItems = slugs.filter((slug) => !items.find((item) => item.slug === slug));
|
180
|
+
missingItems.forEach((slug) => {
|
181
|
+
console.warn(styles.warning(`No ${type} with slug: "${slug}"`));
|
182
|
+
});
|
183
|
+
}
|
184
|
+
for (const item of filteredItems) {
|
134
185
|
// If a chart fails to update, we keep updating the rest
|
135
186
|
try {
|
136
187
|
if (!force && !item.needsUpdating) {
|
188
|
+
if (hasFilter) {
|
189
|
+
console.warn(styles.warning(`Skipping ${type} "${item.slug}" with no local changes`));
|
190
|
+
}
|
137
191
|
globalState_1.default.debug(`Skipping ${type} "${item.slug}" with no local changes`);
|
138
192
|
changes[`${type} skipped`] =
|
139
193
|
(changes[`${type} skipped`] ?? 0) + 1;
|
@@ -169,8 +223,8 @@ const uploadHandler = async (options) => {
|
|
169
223
|
throw new Error('No project selected. Run lightdash config set-project');
|
170
224
|
}
|
171
225
|
let changes = {};
|
172
|
-
changes = await upsertResources('charts', projectId, changes, options.force);
|
173
|
-
changes = await upsertResources('dashboards', projectId, changes, options.force);
|
226
|
+
changes = await upsertResources('charts', projectId, changes, options.force, options.charts);
|
227
|
+
changes = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards);
|
174
228
|
logUploadChanges(changes);
|
175
229
|
};
|
176
230
|
exports.uploadHandler = uploadHandler;
|
package/dist/index.js
CHANGED
@@ -194,11 +194,15 @@ commander_1.program
|
|
194
194
|
.command('download')
|
195
195
|
.description('Downloads charts and dashboards as code')
|
196
196
|
.option('--verbose', undefined, false)
|
197
|
+
.option('-c, --charts <charts...>', 'specify chart slugs, uuids, or urls to download', [])
|
198
|
+
.option('-d, --dashboards <dashboards...>', 'specify dashboard slugs, uuids or urls to download', [])
|
197
199
|
.action(download_1.downloadHandler);
|
198
200
|
commander_1.program
|
199
201
|
.command('upload')
|
200
202
|
.description('Uploads charts and dashboards as code')
|
201
203
|
.option('--verbose', undefined, false)
|
204
|
+
.option('-c, --charts <charts...>', 'specify chart slugs to force upload', [])
|
205
|
+
.option('-d, --dashboards <dashboards...>', 'specify dashboard slugs to force upload', [])
|
202
206
|
.option('--force', 'Force upload even if local files have not changed, use this when you want to upload files to a new project', false)
|
203
207
|
.action(download_1.uploadHandler);
|
204
208
|
commander_1.program
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lightdash/cli",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.1419.1",
|
4
4
|
"license": "MIT",
|
5
5
|
"bin": {
|
6
6
|
"lightdash": "dist/index.js"
|
@@ -11,8 +11,8 @@
|
|
11
11
|
],
|
12
12
|
"dependencies": {
|
13
13
|
"@actions/core": "^1.11.1",
|
14
|
-
"@lightdash/common": "^0.
|
15
|
-
"@lightdash/warehouses": "^0.
|
14
|
+
"@lightdash/common": "^0.1419.1",
|
15
|
+
"@lightdash/warehouses": "^0.1419.1",
|
16
16
|
"@types/columnify": "^1.5.1",
|
17
17
|
"ajv": "^8.11.0",
|
18
18
|
"ajv-formats": "^2.1.1",
|