@lightdash/cli 0.1421.0 → 0.1422.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/analytics/analytics.d.ts +29 -1
- package/dist/analytics/analytics.js +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.js +2 -1
- package/dist/handlers/download.js +167 -73
- package/dist/handlers/login.js +7 -4
- package/package.json +3 -3
@@ -190,11 +190,39 @@ type CliLogin = BaseTrack & {
|
|
190
190
|
event: 'login.started' | 'login.completed';
|
191
191
|
properties: {
|
192
192
|
userId?: string;
|
193
|
+
organizationId?: string;
|
193
194
|
method: string;
|
194
195
|
url: string;
|
195
196
|
};
|
196
197
|
};
|
197
|
-
type
|
198
|
+
type CliContentAsCode = BaseTrack & ({
|
199
|
+
event: 'download.started' | 'upload.started';
|
200
|
+
properties: {
|
201
|
+
userId?: string;
|
202
|
+
organizationId?: string;
|
203
|
+
projectId: string;
|
204
|
+
};
|
205
|
+
} | {
|
206
|
+
event: 'download.completed' | 'upload.completed';
|
207
|
+
properties: {
|
208
|
+
userId?: string;
|
209
|
+
organizationId?: string;
|
210
|
+
projectId: string;
|
211
|
+
chartsNum?: number;
|
212
|
+
dashboardsNum?: number;
|
213
|
+
timeToCompleted: number;
|
214
|
+
};
|
215
|
+
} | {
|
216
|
+
event: 'download.error' | 'upload.error';
|
217
|
+
properties: {
|
218
|
+
userId?: string;
|
219
|
+
organizationId?: string;
|
220
|
+
projectId: string;
|
221
|
+
type?: 'charts' | 'dashboards';
|
222
|
+
error: string;
|
223
|
+
};
|
224
|
+
});
|
225
|
+
type Track = CliGenerateStarted | CliGenerateCompleted | CliGenerateError | CliDbtCommand | CliDbtError | CliPreviewStarted | CliPreviewCompleted | CliPreviewStopped | CliPreviewError | CliRefreshStarted | CliRefreshCompleted | CliRefreshError | CliCompileStarted | CliCompileCompleted | CliCompileError | CliDeployTriggered | CliCreateStarted | CliCreateCompleted | CliCreateError | CliStartStopPreview | CliStopPreviewMissing | CliGenerateExposuresStarted | CliGenerateExposuresCompleted | CliGenerateExposuresError | CliLogin | CliContentAsCode;
|
198
226
|
export declare class LightdashAnalytics {
|
199
227
|
static track(payload: Track): Promise<void>;
|
200
228
|
}
|
package/dist/config.d.ts
CHANGED
@@ -3,6 +3,7 @@ export type Config = {
|
|
3
3
|
user?: {
|
4
4
|
userUuid?: string;
|
5
5
|
anonymousUuid?: string;
|
6
|
+
organizationUuid?: string;
|
6
7
|
};
|
7
8
|
context?: {
|
8
9
|
serverUrl?: string;
|
@@ -22,6 +23,6 @@ export declare const getConfig: () => Promise<Config>;
|
|
22
23
|
export declare const setProject: (projectUuid: string, projectName: string) => Promise<void>;
|
23
24
|
export declare const setPreviewProject: (projectUuid: string, name: string) => Promise<void>;
|
24
25
|
export declare const unsetPreviewProject: () => Promise<void>;
|
25
|
-
export declare const setDefaultUser: (userUuid: string) => Promise<void>;
|
26
|
+
export declare const setDefaultUser: (userUuid: string, organizationUuid: string) => Promise<void>;
|
26
27
|
export declare const setContext: (context: Config['context']) => Promise<void>;
|
27
28
|
export declare const setAnswer: (answer: Config['answers']) => Promise<void>;
|
package/dist/config.js
CHANGED
@@ -93,13 +93,14 @@ const unsetPreviewProject = async () => {
|
|
93
93
|
});
|
94
94
|
};
|
95
95
|
exports.unsetPreviewProject = unsetPreviewProject;
|
96
|
-
const setDefaultUser = async (userUuid) => {
|
96
|
+
const setDefaultUser = async (userUuid, organizationUuid) => {
|
97
97
|
const config = await getRawConfig();
|
98
98
|
await (0, exports.setConfig)({
|
99
99
|
...config,
|
100
100
|
user: {
|
101
101
|
...(config.user || {}),
|
102
102
|
userUuid,
|
103
|
+
organizationUuid,
|
103
104
|
},
|
104
105
|
});
|
105
106
|
};
|
@@ -8,6 +8,7 @@ const common_1 = require("@lightdash/common");
|
|
8
8
|
const fs_1 = require("fs");
|
9
9
|
const yaml = tslib_1.__importStar(require("js-yaml"));
|
10
10
|
const path = tslib_1.__importStar(require("path"));
|
11
|
+
const analytics_1 = require("../analytics/analytics");
|
11
12
|
const config_1 = require("../config");
|
12
13
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
13
14
|
const styles = tslib_1.__importStar(require("../styles"));
|
@@ -95,64 +96,104 @@ const downloadHandler = async (options) => {
|
|
95
96
|
if (!projectId) {
|
96
97
|
throw new Error('No project selected. Run lightdash config set-project');
|
97
98
|
}
|
98
|
-
//
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
chartsAsCode
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
99
|
+
// For analytics
|
100
|
+
let chartTotal;
|
101
|
+
let dashboardTotal;
|
102
|
+
const start = Date.now();
|
103
|
+
await analytics_1.LightdashAnalytics.track({
|
104
|
+
event: 'download.started',
|
105
|
+
properties: {
|
106
|
+
userId: config.user?.userUuid,
|
107
|
+
organizationId: config.user?.organizationUuid,
|
108
|
+
projectId,
|
109
|
+
},
|
110
|
+
});
|
111
|
+
try {
|
112
|
+
// If any filter is provided, we skip those items without filters
|
113
|
+
// eg: if a --charts filter is provided, we skip dashboards if no --dashboards filter is provided
|
114
|
+
const hasFilters = options.charts.length > 0 || options.dashboards.length > 0;
|
115
|
+
// Download charts
|
116
|
+
if (hasFilters && options.charts.length === 0) {
|
117
|
+
console.info(styles.warning(`No charts filters provided, skipping`));
|
118
|
+
}
|
119
|
+
else {
|
120
|
+
const spinner = globalState_1.default.startSpinner(`Downloading charts`);
|
121
|
+
const chartFilters = parseContentFilters(options.charts);
|
122
|
+
let chartsAsCode;
|
123
|
+
let offset = 0;
|
124
|
+
do {
|
125
|
+
globalState_1.default.debug(`Downloading charts with offset "${offset}" and filters "${chartFilters}"`);
|
126
|
+
const queryParams = chartFilters
|
127
|
+
? `${chartFilters}&offset=${offset}`
|
128
|
+
: `?offset=${offset}`;
|
129
|
+
chartsAsCode = await (0, apiClient_1.lightdashApi)({
|
130
|
+
method: 'GET',
|
131
|
+
url: `/api/v1/projects/${projectId}/charts/code${queryParams}`,
|
132
|
+
body: undefined,
|
133
|
+
});
|
134
|
+
spinner.start(`Downloaded ${chartsAsCode.offset} of ${chartsAsCode.total} charts`);
|
135
|
+
chartsAsCode.missingIds.forEach((missingId) => {
|
136
|
+
console.warn(styles.warning(`\nNo chart with id "${missingId}"`));
|
137
|
+
});
|
138
|
+
await dumpIntoFiles('charts', chartsAsCode.charts);
|
139
|
+
offset = chartsAsCode.offset;
|
140
|
+
} while (chartsAsCode.offset < chartsAsCode.total);
|
141
|
+
chartTotal = chartsAsCode.total;
|
142
|
+
spinner.succeed(`Downloaded ${chartsAsCode.total} charts`);
|
143
|
+
}
|
144
|
+
// Download dashboards
|
145
|
+
if (hasFilters && options.dashboards.length === 0) {
|
146
|
+
console.info(styles.warning(`No dashboards filters provided, skipping`));
|
147
|
+
}
|
148
|
+
else {
|
149
|
+
const spinner = globalState_1.default.startSpinner(`Downloading dashboards`);
|
150
|
+
const dashboardFilters = parseContentFilters(options.dashboards);
|
151
|
+
let offset = 0;
|
152
|
+
let dashboardsAsCode;
|
153
|
+
do {
|
154
|
+
globalState_1.default.debug(`Downloading dashboards with offset "${offset}" and filters "${dashboardFilters}"`);
|
155
|
+
const queryParams = dashboardFilters
|
156
|
+
? `${dashboardFilters}&offset=${offset}`
|
157
|
+
: `?offset=${offset}`;
|
158
|
+
dashboardsAsCode = await (0, apiClient_1.lightdashApi)({
|
159
|
+
method: 'GET',
|
160
|
+
url: `/api/v1/projects/${projectId}/dashboards/code${queryParams}`,
|
161
|
+
body: undefined,
|
162
|
+
});
|
163
|
+
dashboardsAsCode.missingIds.forEach((missingId) => {
|
164
|
+
console.warn(styles.warning(`\nNo dashboard with id "${missingId}"`));
|
165
|
+
});
|
166
|
+
spinner?.start(`Downloaded ${dashboardsAsCode.offset} of ${dashboardsAsCode.total} dashboards`);
|
167
|
+
await dumpIntoFiles('dashboards', dashboardsAsCode.dashboards);
|
168
|
+
offset = dashboardsAsCode.offset;
|
169
|
+
} while (dashboardsAsCode.offset < dashboardsAsCode.total);
|
170
|
+
dashboardTotal = dashboardsAsCode.total;
|
171
|
+
spinner.succeed(`Downloaded ${dashboardsAsCode.total} dashboards`);
|
172
|
+
}
|
173
|
+
const end = Date.now();
|
174
|
+
await analytics_1.LightdashAnalytics.track({
|
175
|
+
event: 'download.completed',
|
176
|
+
properties: {
|
177
|
+
userId: config.user?.userUuid,
|
178
|
+
organizationId: config.user?.organizationUuid,
|
179
|
+
projectId,
|
180
|
+
chartsNum: chartTotal,
|
181
|
+
dashboardsNum: dashboardTotal,
|
182
|
+
timeToCompleted: (end - start) / 1000, // in seconds
|
183
|
+
},
|
184
|
+
});
|
132
185
|
}
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
method: 'GET',
|
145
|
-
url: `/api/v1/projects/${projectId}/dashboards/code${queryParams}`,
|
146
|
-
body: undefined,
|
147
|
-
});
|
148
|
-
dashboardsAsCode.missingIds.forEach((missingId) => {
|
149
|
-
console.warn(styles.warning(`\nNo dashboard with id "${missingId}"`));
|
150
|
-
});
|
151
|
-
spinner?.start(`Downloaded ${dashboardsAsCode.offset} of ${dashboardsAsCode.total} dashboards`);
|
152
|
-
await dumpIntoFiles('dashboards', dashboardsAsCode.dashboards);
|
153
|
-
offset = dashboardsAsCode.offset;
|
154
|
-
} while (dashboardsAsCode.offset < dashboardsAsCode.total);
|
155
|
-
spinner.succeed(`Downloaded ${dashboardsAsCode.total} dashboards`);
|
186
|
+
catch (error) {
|
187
|
+
console.error(styles.error(`\nError downloading ${error}`));
|
188
|
+
await analytics_1.LightdashAnalytics.track({
|
189
|
+
event: 'download.error',
|
190
|
+
properties: {
|
191
|
+
userId: config.user?.userUuid,
|
192
|
+
organizationId: config.user?.organizationUuid,
|
193
|
+
projectId,
|
194
|
+
error: `${error}`,
|
195
|
+
},
|
196
|
+
});
|
156
197
|
}
|
157
198
|
// TODO delete files if chart don't exist ?*/
|
158
199
|
};
|
@@ -203,6 +244,7 @@ const logUploadChanges = (changes) => {
|
|
203
244
|
* @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
|
204
245
|
*/
|
205
246
|
const upsertResources = async (type, projectId, changes, force, slugs) => {
|
247
|
+
const config = await (0, config_1.getConfig)();
|
206
248
|
const items = await readCodeFiles(type);
|
207
249
|
console.info(`Found ${items.length} ${type} files`);
|
208
250
|
const hasFilter = slugs.length > 0;
|
@@ -241,10 +283,20 @@ const upsertResources = async (type, projectId, changes, force, slugs) => {
|
|
241
283
|
catch (error) {
|
242
284
|
changes[`${type} with errors`] =
|
243
285
|
(changes[`${type} with errors`] ?? 0) + 1;
|
244
|
-
console.error(`Error upserting ${type}
|
286
|
+
console.error(styles.error(`Error upserting ${type}: ${error}`));
|
287
|
+
await analytics_1.LightdashAnalytics.track({
|
288
|
+
event: 'download.error',
|
289
|
+
properties: {
|
290
|
+
userId: config.user?.userUuid,
|
291
|
+
organizationId: config.user?.organizationUuid,
|
292
|
+
projectId,
|
293
|
+
type,
|
294
|
+
error: `${error}`,
|
295
|
+
},
|
296
|
+
});
|
245
297
|
}
|
246
298
|
}
|
247
|
-
return changes;
|
299
|
+
return { changes, total: filteredItems.length };
|
248
300
|
};
|
249
301
|
const uploadHandler = async (options) => {
|
250
302
|
globalState_1.default.setVerbose(options.verbose);
|
@@ -258,21 +310,63 @@ const uploadHandler = async (options) => {
|
|
258
310
|
throw new Error('No project selected. Run lightdash config set-project');
|
259
311
|
}
|
260
312
|
let changes = {};
|
261
|
-
//
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
313
|
+
// For analytics
|
314
|
+
let chartTotal;
|
315
|
+
let dashboardTotal;
|
316
|
+
const start = Date.now();
|
317
|
+
await analytics_1.LightdashAnalytics.track({
|
318
|
+
event: 'upload.started',
|
319
|
+
properties: {
|
320
|
+
userId: config.user?.userUuid,
|
321
|
+
organizationId: config.user?.organizationUuid,
|
322
|
+
projectId,
|
323
|
+
},
|
324
|
+
});
|
325
|
+
try {
|
326
|
+
// If any filter is provided, we skip those items without filters
|
327
|
+
// eg: if a --charts filter is provided, we skip dashboards if no --dashboards filter is provided
|
328
|
+
const hasFilters = options.charts.length > 0 || options.dashboards.length > 0;
|
329
|
+
if (hasFilters && options.charts.length === 0) {
|
330
|
+
console.info(styles.warning(`No charts filters provided, skipping`));
|
331
|
+
}
|
332
|
+
else {
|
333
|
+
const { changes: chartChanges, total } = await upsertResources('charts', projectId, changes, options.force, options.charts);
|
334
|
+
changes = chartChanges;
|
335
|
+
chartTotal = total;
|
336
|
+
}
|
337
|
+
if (hasFilters && options.dashboards.length === 0) {
|
338
|
+
console.info(styles.warning(`No dashboard filters provided, skipping`));
|
339
|
+
}
|
340
|
+
else {
|
341
|
+
const { changes: dashboardChanges, total } = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards);
|
342
|
+
changes = dashboardChanges;
|
343
|
+
dashboardTotal = total;
|
344
|
+
}
|
345
|
+
const end = Date.now();
|
346
|
+
await analytics_1.LightdashAnalytics.track({
|
347
|
+
event: 'upload.completed',
|
348
|
+
properties: {
|
349
|
+
userId: config.user?.userUuid,
|
350
|
+
organizationId: config.user?.organizationUuid,
|
351
|
+
projectId,
|
352
|
+
chartsNum: chartTotal,
|
353
|
+
dashboardsNum: dashboardTotal,
|
354
|
+
timeToCompleted: (end - start) / 1000, // in seconds
|
355
|
+
},
|
356
|
+
});
|
357
|
+
logUploadChanges(changes);
|
272
358
|
}
|
273
|
-
|
274
|
-
|
359
|
+
catch (error) {
|
360
|
+
console.error(styles.error(`\nError downloading ${error}`));
|
361
|
+
await analytics_1.LightdashAnalytics.track({
|
362
|
+
event: 'download.error',
|
363
|
+
properties: {
|
364
|
+
userId: config.user?.userUuid,
|
365
|
+
organizationId: config.user?.organizationUuid,
|
366
|
+
projectId,
|
367
|
+
error: `${error}`,
|
368
|
+
},
|
369
|
+
});
|
275
370
|
}
|
276
|
-
logUploadChanges(changes);
|
277
371
|
};
|
278
372
|
exports.uploadHandler = uploadHandler;
|
package/dist/handlers/login.js
CHANGED
@@ -30,9 +30,10 @@ const loginWithToken = async (url, token, proxyAuthorization) => {
|
|
30
30
|
throw new common_1.AuthorizationError(`Cannot sign in with token:\n${JSON.stringify(await response.json())}`);
|
31
31
|
}
|
32
32
|
const userBody = await response.json();
|
33
|
-
const { userUuid } = userBody;
|
33
|
+
const { userUuid, organizationUuid } = userBody;
|
34
34
|
return {
|
35
35
|
userUuid,
|
36
|
+
organizationUuid,
|
36
37
|
token,
|
37
38
|
};
|
38
39
|
};
|
@@ -71,7 +72,7 @@ const loginWithPassword = async (url) => {
|
|
71
72
|
if (header === null) {
|
72
73
|
throw new common_1.AuthorizationError(`Cannot sign in:\n${JSON.stringify(loginBody)}`);
|
73
74
|
}
|
74
|
-
const { userUuid } = loginBody.results;
|
75
|
+
const { userUuid, organizationUuid } = loginBody.results;
|
75
76
|
const cookie = header.split(';')[0].split('=')[1];
|
76
77
|
const patUrl = new url_1.URL(`/api/v1/user/me/personal-access-tokens`, url).href;
|
77
78
|
const now = new Date();
|
@@ -90,6 +91,7 @@ const loginWithPassword = async (url) => {
|
|
90
91
|
const { token } = patResponseBody.results;
|
91
92
|
return {
|
92
93
|
userUuid,
|
94
|
+
organizationUuid,
|
93
95
|
token,
|
94
96
|
};
|
95
97
|
};
|
@@ -109,7 +111,7 @@ const login = async (url, options) => {
|
|
109
111
|
console.error(`\n${styles.title('Warning')}: Login URL ${styles.secondary(url)} does not match a valid cloud server, perhaps you meant ${styles.secondary(cloudServer)} ?\n`);
|
110
112
|
}
|
111
113
|
const proxyAuthorization = process.env.LIGHTDASH_PROXY_AUTHORIZATION;
|
112
|
-
const { userUuid, token } = options.token
|
114
|
+
const { userUuid, token, organizationUuid } = options.token
|
113
115
|
? await loginWithToken(url, options.token, proxyAuthorization)
|
114
116
|
: await loginWithPassword(url);
|
115
117
|
globalState_1.default.debug(`> Logged in with userUuid: ${userUuid}`);
|
@@ -117,13 +119,14 @@ const login = async (url, options) => {
|
|
117
119
|
event: 'login.completed',
|
118
120
|
properties: {
|
119
121
|
userId: userUuid,
|
122
|
+
organizationId: organizationUuid,
|
120
123
|
url,
|
121
124
|
method: options.token ? 'token' : 'password',
|
122
125
|
},
|
123
126
|
});
|
124
127
|
await (0, config_1.setContext)({ serverUrl: url, apiKey: token });
|
125
128
|
globalState_1.default.debug(`> Saved config on: ${config_1.configFilePath}`);
|
126
|
-
await (0, config_1.setDefaultUser)(userUuid);
|
129
|
+
await (0, config_1.setDefaultUser)(userUuid, organizationUuid);
|
127
130
|
console.error(`\n ✅️ Login successful\n`);
|
128
131
|
try {
|
129
132
|
if (process.env.CI === 'true') {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lightdash/cli",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.1422.0",
|
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.1422.0",
|
15
|
+
"@lightdash/warehouses": "^0.1422.0",
|
16
16
|
"@types/columnify": "^1.5.1",
|
17
17
|
"ajv": "^8.11.0",
|
18
18
|
"ajv-formats": "^2.1.1",
|