@lightdash/cli 0.2234.3 → 0.2236.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.
|
@@ -11,7 +11,9 @@ export type DownloadHandlerOptions = {
|
|
|
11
11
|
includeCharts: boolean;
|
|
12
12
|
nested: boolean;
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
type DownloadContentType = 'charts' | 'dashboards' | 'sqlCharts';
|
|
15
|
+
export declare const downloadContent: (ids: string[], type: DownloadContentType, projectId: string, projectName: string, customPath?: string, languageMap?: boolean, nested?: boolean) => Promise<[number, string[]]>;
|
|
15
16
|
export declare const downloadHandler: (options: DownloadHandlerOptions) => Promise<void>;
|
|
16
17
|
export declare const uploadHandler: (options: DownloadHandlerOptions) => Promise<void>;
|
|
18
|
+
export {};
|
|
17
19
|
//# sourceMappingURL=download.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,sBAAsB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;CACnB,CAAC;AA2PF,KAAK,mBAAmB,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAC;AAkDjE,eAAO,MAAM,eAAe,QACnB,MAAM,EAAE,QACP,mBAAmB,aACd,MAAM,eACJ,MAAM,eACN,MAAM,gBACN,OAAO,WACZ,OAAO,KAChB,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA2G5B,CAAC;AAEF,eAAO,MAAM,eAAe,YACf,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAkLd,CAAC;AAyNF,eAAO,MAAM,aAAa,YACb,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAuHd,CAAC"}
|
|
@@ -51,8 +51,24 @@ const createDirForContent = async (projectName, spaceSlug, folder, customPath, f
|
|
|
51
51
|
await fs_1.promises.mkdir(outputDir, { recursive: true });
|
|
52
52
|
return outputDir;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Get file extension for content-as-code files.
|
|
56
|
+
* SQL charts use '.sql.yml' extension to avoid filename conflicts with regular charts
|
|
57
|
+
* that may have the same slug, since both chart types share the same output directory.
|
|
58
|
+
*/
|
|
59
|
+
const getFileExtension = (contentType) => {
|
|
60
|
+
switch (contentType) {
|
|
61
|
+
case 'sqlChart':
|
|
62
|
+
return '.sql.yml';
|
|
63
|
+
case 'chart':
|
|
64
|
+
case 'dashboard':
|
|
65
|
+
default:
|
|
66
|
+
return '.yml';
|
|
67
|
+
}
|
|
68
|
+
};
|
|
54
69
|
const writeContent = async (contentAsCode, outputDir, languageMap) => {
|
|
55
|
-
const
|
|
70
|
+
const extension = getFileExtension(contentAsCode.type);
|
|
71
|
+
const itemPath = path.join(outputDir, `${contentAsCode.content.slug}${extension}`);
|
|
56
72
|
const chartYml = yaml.dump(contentAsCode.content, {
|
|
57
73
|
quotingType: '"',
|
|
58
74
|
});
|
|
@@ -115,86 +131,135 @@ const groupBySpace = (items) => {
|
|
|
115
131
|
const itemsWithIndex = items.map((item, index) => ({ item, index }));
|
|
116
132
|
return (0, groupBy_1.default)(itemsWithIndex, (entry) => entry.item.spaceSlug);
|
|
117
133
|
};
|
|
118
|
-
const writeSpaceContent = async ({ projectName, spaceSlug, folder, contentInSpace, contentAsCode, customPath, languageMap, folderScheme, }) => {
|
|
134
|
+
const writeSpaceContent = async ({ projectName, spaceSlug, folder, contentType, contentInSpace, contentAsCode, customPath, languageMap, folderScheme, }) => {
|
|
119
135
|
const outputDir = await createDirForContent(projectName, spaceSlug, folder, customPath, folderScheme);
|
|
120
|
-
const contentType = folder === 'charts' ? 'chart' : 'dashboard';
|
|
121
136
|
for (const { item, index } of contentInSpace) {
|
|
137
|
+
const translationMap = 'languageMap' in contentAsCode
|
|
138
|
+
? contentAsCode.languageMap?.[index]
|
|
139
|
+
: undefined;
|
|
122
140
|
await writeContent({
|
|
123
141
|
type: contentType,
|
|
124
142
|
content: item,
|
|
125
|
-
translationMap
|
|
143
|
+
translationMap,
|
|
126
144
|
}, outputDir, languageMap);
|
|
127
145
|
}
|
|
128
146
|
};
|
|
129
|
-
const
|
|
130
|
-
type
|
|
147
|
+
const getContentTypeConfig = (type, projectId) => {
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'charts':
|
|
150
|
+
return {
|
|
151
|
+
endpoint: `/api/v1/projects/${projectId}/charts/code`,
|
|
152
|
+
displayName: 'charts',
|
|
153
|
+
supportsLanguageMap: true,
|
|
154
|
+
};
|
|
155
|
+
case 'dashboards':
|
|
156
|
+
return {
|
|
157
|
+
endpoint: `/api/v1/projects/${projectId}/dashboards/code`,
|
|
158
|
+
displayName: 'dashboards',
|
|
159
|
+
supportsLanguageMap: true,
|
|
160
|
+
};
|
|
161
|
+
case 'sqlCharts':
|
|
162
|
+
return {
|
|
163
|
+
endpoint: `/api/v1/projects/${projectId}/sqlCharts/code`,
|
|
164
|
+
displayName: 'SQL charts',
|
|
165
|
+
supportsLanguageMap: false,
|
|
166
|
+
};
|
|
167
|
+
default:
|
|
168
|
+
return (0, common_1.assertUnreachable)(type, `Unknown content type: ${type}`);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const extractChartSlugsFromDashboards = (dashboards) => dashboards.reduce((acc, dashboard) => {
|
|
172
|
+
const slugs = dashboard.tiles
|
|
173
|
+
.map((tile) => 'chartSlug' in tile.properties
|
|
174
|
+
? tile.properties.chartSlug
|
|
175
|
+
: undefined)
|
|
176
|
+
.filter((slug) => slug !== undefined);
|
|
177
|
+
return [...acc, ...slugs];
|
|
178
|
+
}, []);
|
|
179
|
+
const downloadContent = async (ids, type, projectId, projectName, customPath, languageMap = false, nested = false) => {
|
|
131
180
|
const spinner = globalState_1.default.getActiveSpinner();
|
|
132
181
|
const contentFilters = parseContentFilters(ids);
|
|
133
|
-
// Convert boolean flag to FolderScheme type
|
|
134
182
|
const folderScheme = nested ? 'nested' : 'flat';
|
|
135
|
-
|
|
183
|
+
const config = getContentTypeConfig(type, projectId);
|
|
136
184
|
let offset = 0;
|
|
185
|
+
let total = 0;
|
|
137
186
|
let chartSlugs = [];
|
|
138
187
|
do {
|
|
139
|
-
globalState_1.default.debug(`Downloading ${
|
|
140
|
-
const commonParams =
|
|
188
|
+
globalState_1.default.debug(`Downloading ${config.displayName} with offset "${offset}" and filters "${contentFilters}"`);
|
|
189
|
+
const commonParams = config.supportsLanguageMap
|
|
190
|
+
? `offset=${offset}&languageMap=${languageMap}`
|
|
191
|
+
: `offset=${offset}`;
|
|
141
192
|
const queryParams = contentFilters
|
|
142
193
|
? `${contentFilters}&${commonParams}`
|
|
143
194
|
: `?${commonParams}`;
|
|
144
|
-
|
|
195
|
+
const results = await (0, apiClient_1.lightdashApi)({
|
|
145
196
|
method: 'GET',
|
|
146
|
-
url:
|
|
197
|
+
url: `${config.endpoint}${queryParams}`,
|
|
147
198
|
body: undefined,
|
|
148
199
|
});
|
|
149
|
-
spinner?.start(`Downloaded ${
|
|
150
|
-
|
|
151
|
-
|
|
200
|
+
spinner?.start(`Downloaded ${results.offset} of ${results.total} ${config.displayName}`);
|
|
201
|
+
// For the same chart slug, we run the code for saved charts and sql chart
|
|
202
|
+
// so we are going to get more false positives here, so we keep it on the debug log
|
|
203
|
+
results.missingIds.forEach((missingId) => {
|
|
204
|
+
globalState_1.default.debug(`\nNo ${config.displayName} with id "${missingId}"`);
|
|
152
205
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
206
|
+
// Write content based on type
|
|
207
|
+
if ('sqlCharts' in results) {
|
|
208
|
+
const sqlChartsBySpace = groupBySpace(results.sqlCharts);
|
|
209
|
+
for (const [spaceSlug, sqlChartsInSpace] of Object.entries(sqlChartsBySpace)) {
|
|
210
|
+
await writeSpaceContent({
|
|
211
|
+
projectName,
|
|
212
|
+
spaceSlug,
|
|
213
|
+
folder: 'charts',
|
|
214
|
+
contentType: 'sqlChart',
|
|
215
|
+
contentInSpace: sqlChartsInSpace,
|
|
216
|
+
contentAsCode: results,
|
|
217
|
+
customPath,
|
|
218
|
+
languageMap,
|
|
219
|
+
folderScheme,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if ('dashboards' in results) {
|
|
224
|
+
const dashboardsBySpace = groupBySpace(results.dashboards);
|
|
157
225
|
for (const [spaceSlug, dashboardsInSpace] of Object.entries(dashboardsBySpace)) {
|
|
158
226
|
await writeSpaceContent({
|
|
159
227
|
projectName,
|
|
160
228
|
spaceSlug,
|
|
161
229
|
folder: 'dashboards',
|
|
230
|
+
contentType: 'dashboard',
|
|
162
231
|
contentInSpace: dashboardsInSpace,
|
|
163
|
-
contentAsCode,
|
|
232
|
+
contentAsCode: results,
|
|
164
233
|
customPath,
|
|
165
234
|
languageMap,
|
|
166
235
|
folderScheme,
|
|
167
236
|
});
|
|
168
237
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: undefined);
|
|
174
|
-
return [
|
|
175
|
-
...acc,
|
|
176
|
-
...slugs.filter((slug) => slug !== undefined),
|
|
177
|
-
];
|
|
178
|
-
}, []);
|
|
238
|
+
chartSlugs = [
|
|
239
|
+
...chartSlugs,
|
|
240
|
+
...extractChartSlugsFromDashboards(results.dashboards),
|
|
241
|
+
];
|
|
179
242
|
}
|
|
180
243
|
else {
|
|
181
|
-
const chartsBySpace = groupBySpace(
|
|
244
|
+
const chartsBySpace = groupBySpace(results.charts);
|
|
182
245
|
for (const [spaceSlug, chartsInSpace] of Object.entries(chartsBySpace)) {
|
|
183
246
|
await writeSpaceContent({
|
|
184
247
|
projectName,
|
|
185
248
|
spaceSlug,
|
|
186
249
|
folder: 'charts',
|
|
250
|
+
contentType: 'chart',
|
|
187
251
|
contentInSpace: chartsInSpace,
|
|
188
|
-
contentAsCode,
|
|
252
|
+
contentAsCode: results,
|
|
189
253
|
customPath,
|
|
190
254
|
languageMap,
|
|
191
255
|
folderScheme,
|
|
192
256
|
});
|
|
193
257
|
}
|
|
194
258
|
}
|
|
195
|
-
offset =
|
|
196
|
-
|
|
197
|
-
|
|
259
|
+
offset = results.offset;
|
|
260
|
+
total = results.total;
|
|
261
|
+
} while (offset < total);
|
|
262
|
+
return [total, [...new Set(chartSlugs)]];
|
|
198
263
|
};
|
|
199
264
|
exports.downloadContent = downloadContent;
|
|
200
265
|
const downloadHandler = async (options) => {
|
|
@@ -243,13 +308,18 @@ const downloadHandler = async (options) => {
|
|
|
243
308
|
// If any filter is provided, we skip those items without filters
|
|
244
309
|
// eg: if a --charts filter is provided, we skip dashboards if no --dashboards filter is provided
|
|
245
310
|
const hasFilters = options.charts.length > 0 || options.dashboards.length > 0;
|
|
246
|
-
// Download charts
|
|
311
|
+
// Download regular charts
|
|
247
312
|
if (hasFilters && options.charts.length === 0) {
|
|
248
313
|
console.info(styles.warning(`No charts filters provided, skipping`));
|
|
249
314
|
}
|
|
250
315
|
else {
|
|
251
|
-
[
|
|
252
|
-
spinner.succeed(`Downloaded ${
|
|
316
|
+
const [regularChartTotal] = await (0, exports.downloadContent)(options.charts, 'charts', projectId, projectName, options.path, options.languageMap, options.nested);
|
|
317
|
+
spinner.succeed(`Downloaded ${regularChartTotal} charts`);
|
|
318
|
+
// Download SQL charts
|
|
319
|
+
spinner.start(`Downloading SQL charts`);
|
|
320
|
+
const [sqlChartTotal] = await (0, exports.downloadContent)(options.charts, 'sqlCharts', projectId, projectName, options.path, options.languageMap, options.nested);
|
|
321
|
+
spinner.succeed(`Downloaded ${sqlChartTotal} SQL charts`);
|
|
322
|
+
chartTotal = regularChartTotal + sqlChartTotal;
|
|
253
323
|
}
|
|
254
324
|
// Download dashboards
|
|
255
325
|
if (hasFilters && options.dashboards.length === 0) {
|
|
@@ -259,12 +329,14 @@ const downloadHandler = async (options) => {
|
|
|
259
329
|
let chartSlugs = [];
|
|
260
330
|
[dashboardTotal, chartSlugs] = await (0, exports.downloadContent)(options.dashboards, 'dashboards', projectId, projectName, options.path, options.languageMap, options.nested);
|
|
261
331
|
spinner.succeed(`Downloaded ${dashboardTotal} dashboards`);
|
|
262
|
-
// If any filter is provided, we download all charts
|
|
332
|
+
// If any filter is provided, we download all charts linked to these dashboards
|
|
263
333
|
// We don't need to do this if we download everything (no filters)
|
|
264
334
|
if (hasFilters && chartSlugs.length > 0) {
|
|
265
335
|
spinner.start(`Downloading ${chartSlugs.length} charts linked to dashboards`);
|
|
266
|
-
|
|
267
|
-
|
|
336
|
+
// Download both regular charts and SQL charts linked to dashboards
|
|
337
|
+
const [regularCharts] = await (0, exports.downloadContent)(chartSlugs, 'charts', projectId, projectName, options.path, options.languageMap, options.nested);
|
|
338
|
+
const [sqlCharts] = await (0, exports.downloadContent)(chartSlugs, 'sqlCharts', projectId, projectName, options.path, options.languageMap, options.nested);
|
|
339
|
+
spinner.succeed(`Downloaded ${regularCharts + sqlCharts} charts linked to dashboards`);
|
|
268
340
|
}
|
|
269
341
|
}
|
|
270
342
|
const end = Date.now();
|
|
@@ -335,6 +407,8 @@ const logUploadChanges = (changes) => {
|
|
|
335
407
|
console.info(`Total ${key}: ${value} `);
|
|
336
408
|
});
|
|
337
409
|
};
|
|
410
|
+
// SQL charts have 'sql' field instead of 'tableName'/'metricQuery'
|
|
411
|
+
const isSqlChart = (item) => 'sql' in item && !('tableName' in item);
|
|
338
412
|
/**
|
|
339
413
|
*
|
|
340
414
|
* @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
|
|
@@ -368,9 +442,14 @@ const upsertResources = async (type, projectId, changes, force, slugs, customPat
|
|
|
368
442
|
continue;
|
|
369
443
|
}
|
|
370
444
|
globalState_1.default.debug(`Upserting ${type} ${item.slug}`);
|
|
445
|
+
// SQL charts use a different endpoint
|
|
446
|
+
const isSqlChartItem = type === 'charts' && isSqlChart(item);
|
|
447
|
+
const endpoint = isSqlChartItem
|
|
448
|
+
? `/api/v1/projects/${projectId}/sqlCharts/${item.slug}/code`
|
|
449
|
+
: `/api/v1/projects/${projectId}/${type}/${item.slug}/code`;
|
|
371
450
|
const upsertData = await (0, apiClient_1.lightdashApi)({
|
|
372
451
|
method: 'POST',
|
|
373
|
-
url:
|
|
452
|
+
url: endpoint,
|
|
374
453
|
body: JSON.stringify({
|
|
375
454
|
...item,
|
|
376
455
|
skipSpaceCreate,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2236.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lightdash": "dist/index.js"
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"unique-names-generator": "^4.7.1",
|
|
35
35
|
"uuid": "^11.0.3",
|
|
36
36
|
"yaml": "^2.7.0",
|
|
37
|
-
"@lightdash/common": "0.
|
|
38
|
-
"@lightdash/warehouses": "0.
|
|
37
|
+
"@lightdash/common": "0.2236.0",
|
|
38
|
+
"@lightdash/warehouses": "0.2236.0"
|
|
39
39
|
},
|
|
40
40
|
"description": "Lightdash CLI tool",
|
|
41
41
|
"devDependencies": {
|