@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
- } | import("@lightdash/common").ChartAsCode[] | {
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>;
@@ -1,5 +1,7 @@
1
1
  export type DownloadHandlerOptions = {
2
2
  verbose: boolean;
3
+ charts: string[];
4
+ dashboards: string[];
3
5
  force: boolean;
4
6
  };
5
7
  export declare const downloadHandler: (options: DownloadHandlerOptions) => 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(`Writting ${items.length} ${folder} into ${outputDir}`);
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
- globalState_1.default.debug('Downloading charts');
85
- const chartsAsCode = await (0, apiClient_1.lightdashApi)({
86
- method: 'GET',
87
- url: `/api/v1/projects/${projectId}/charts/code`,
88
- body: undefined,
89
- });
90
- await dumpIntoFiles('charts', chartsAsCode);
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
- globalState_1.default.debug('Downloading dashboards');
93
- const dashboardsAsCode = await (0, apiClient_1.lightdashApi)({
94
- method: 'GET',
95
- url: `/api/v1/projects/${projectId}/dashboards/code`,
96
- body: undefined,
97
- });
98
- await dumpIntoFiles('dashboards', dashboardsAsCode);
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
- const upsertResources = async (type, projectId, changes, force) => {
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
- for (const item of items) {
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.1418.2",
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.1418.2",
15
- "@lightdash/warehouses": "^0.1418.2",
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",