@lightdash/cli 0.1418.2 → 0.1419.1
Sign up to get free protection for your applications and to get access to all the features.
@@ -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",
|