@lightdash/cli 0.1621.2 → 0.1622.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.
@@ -1,4 +1,5 @@
1
- import { JobStep } from '@lightdash/common';
1
+ import { JobStep, Project } from '@lightdash/common';
2
+ export declare const getProject: (projectUuid: string) => Promise<Project>;
2
3
  export declare const getRunningStepsMessage: (steps: JobStep[]) => string;
3
4
  export declare const getErrorStepsMessage: (steps: JobStep[]) => string;
4
5
  type RefreshHandlerOptions = {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.refreshHandler = exports.getErrorStepsMessage = exports.getRunningStepsMessage = void 0;
3
+ exports.refreshHandler = exports.getErrorStepsMessage = exports.getRunningStepsMessage = exports.getProject = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@lightdash/common");
6
6
  const uuid_1 = require("uuid");
@@ -14,6 +14,7 @@ const getProject = async (projectUuid) => (0, apiClient_1.lightdashApi)({
14
14
  url: `/api/v1/projects/${projectUuid}`,
15
15
  body: undefined,
16
16
  });
17
+ exports.getProject = getProject;
17
18
  const refreshProject = async (projectUuid) => (0, apiClient_1.lightdashApi)({
18
19
  method: 'POST',
19
20
  url: `/api/v1/projects/${projectUuid}/refresh`,
@@ -63,7 +64,7 @@ const refreshHandler = async (options) => {
63
64
  throw new common_1.AuthorizationError(`No active Lightdash project. Run 'lightdash login --help'`);
64
65
  }
65
66
  const projectUuid = config.context.project;
66
- const project = await getProject(projectUuid);
67
+ const project = await (0, exports.getProject)(projectUuid);
67
68
  if (project.dbtConnection.type === common_1.DbtProjectType.NONE) {
68
69
  throw new common_1.ParameterError('Lightdash project must be connected to a remote repository. eg: GitHub, Gitlab, etc');
69
70
  }
@@ -0,0 +1,15 @@
1
+ import { RenameType } from '@lightdash/common';
2
+ type RenameHandlerOptions = {
3
+ verbose: boolean;
4
+ type: RenameType;
5
+ project?: string;
6
+ model?: string;
7
+ from: string;
8
+ to: string;
9
+ dryRun: boolean;
10
+ assumeYes: boolean;
11
+ list: boolean;
12
+ validate: boolean;
13
+ };
14
+ export declare const renameHandler: (options: RenameHandlerOptions) => Promise<void>;
15
+ export {};
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renameHandler = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@lightdash/common");
6
+ const fs_1 = tslib_1.__importDefault(require("fs"));
7
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
+ const path_1 = tslib_1.__importDefault(require("path"));
9
+ const styles = tslib_1.__importStar(require("../styles"));
10
+ const config_1 = require("../config");
11
+ const globalState_1 = tslib_1.__importDefault(require("../globalState"));
12
+ const apiClient_1 = require("./dbt/apiClient");
13
+ const refresh_1 = require("./dbt/refresh");
14
+ const validate_1 = require("./validate");
15
+ const REFETCH_JOB_INTERVAL = 2000;
16
+ const waitUntilFinished = async (jobUuid) => {
17
+ const job = await (0, validate_1.getJobState)(jobUuid);
18
+ if (job.status === common_1.SchedulerJobStatus.COMPLETED) {
19
+ return job.status;
20
+ }
21
+ if (job.status === common_1.SchedulerJobStatus.ERROR) {
22
+ throw new Error(`\nRename failed: ${job.details?.error || 'unknown error'}`);
23
+ }
24
+ return (0, validate_1.delay)(REFETCH_JOB_INTERVAL).then(() => waitUntilFinished(jobUuid));
25
+ };
26
+ const listResources = (resources, type, list) => {
27
+ if (resources.length === 0)
28
+ return;
29
+ const maxList = 5;
30
+ const resourcesToShow = list ? resources : resources.slice(0, maxList);
31
+ console.info(`- ${resourcesToShow.map((r) => r.name).join('\n- ')}`);
32
+ if (!list && resources.length > maxList) {
33
+ console.info(`${styles.secondary(`...\nShowing ${maxList} of ${resources.length} ${type}, use --list to see all`)}`);
34
+ }
35
+ };
36
+ const renameHandler = async (options) => {
37
+ globalState_1.default.setVerbose(options.verbose);
38
+ await (0, apiClient_1.checkLightdashVersion)();
39
+ const config = await (0, config_1.getConfig)();
40
+ if (!config.context?.apiKey || !config.context.serverUrl) {
41
+ throw new common_1.AuthorizationError(`Not logged in. Run 'lightdash login --help'`);
42
+ }
43
+ const projectUuid = options.project ||
44
+ config.context.previewProject ||
45
+ config.context.project;
46
+ if (!projectUuid) {
47
+ throw new Error('No project selected. Run lightdash config set-project');
48
+ }
49
+ const isValidInput = (input) => /^[a-z_]+$/.test(input);
50
+ if (!isValidInput(options.from) || !isValidInput(options.to)) {
51
+ throw new Error('Invalid input: Only lowercase letters (a-z) and underscores (_) are allowed.');
52
+ }
53
+ const project = await (0, refresh_1.getProject)(projectUuid);
54
+ if (!options.assumeYes && !options.dryRun) {
55
+ const answers = await inquirer_1.default.prompt([
56
+ {
57
+ type: 'confirm',
58
+ name: 'isConfirm',
59
+ message: `This command will replace all ${styles.title(options.type)} occurrences from ${styles.title(options.from)} to ${styles.title(options.to)} in all charts and dashboards in project ${styles.title(project.name)} (${projectUuid}).\nAre you sure you want to continue? `,
60
+ },
61
+ ]);
62
+ if (!answers.isConfirm) {
63
+ console.info('Aborting rename');
64
+ return;
65
+ }
66
+ }
67
+ try {
68
+ const jobResponse = await (0, apiClient_1.lightdashApi)({
69
+ method: 'POST',
70
+ url: `/api/v1/projects/${projectUuid}/rename`,
71
+ body: JSON.stringify(options),
72
+ });
73
+ globalState_1.default.debug(`Rename job scheduled with id: ${jobResponse.jobId}`);
74
+ const status = await waitUntilFinished(jobResponse.jobId);
75
+ globalState_1.default.debug(`Rename job finished with status: ${status}`);
76
+ const job = await (0, validate_1.getJobState)(jobResponse.jobId);
77
+ const results = job.details?.results;
78
+ globalState_1.default.debug(`Updated results: ${JSON.stringify(results, null, 2)}`);
79
+ console.info(`${styles.bold('Total updated charts:')} ${results.charts.length}`);
80
+ listResources(results.charts, 'charts', options.list);
81
+ console.info(`${styles.bold('Total updated dashboards:')} ${results.dashboards.length}`);
82
+ listResources(results.dashboards, 'dashboards', options.list);
83
+ if (results.alerts.length > 0) {
84
+ console.info(`${styles.bold('Total updated alerts:')} ${results.alerts.length}`);
85
+ listResources(results.alerts, 'chart alerts', options.list);
86
+ }
87
+ if (results.dashboardSchedulers.length > 0) {
88
+ console.info(`${styles.bold('Total updated dashboard schedulers:')} ${results.dashboardSchedulers.length}`);
89
+ listResources(results.dashboardSchedulers, 'dashboard schedulers', options.list);
90
+ }
91
+ const hasResults = results.charts.length > 0 ||
92
+ results.dashboards.length > 0 ||
93
+ results.alerts.length > 0 ||
94
+ results.dashboardSchedulers.length > 0;
95
+ if (options.dryRun) {
96
+ console.info(`\n${styles.warning(`This is a test run, no changes were committed to the database, remove ${styles.bold('--dry-run')} flag to make changes`)}\n`);
97
+ }
98
+ else if (hasResults) {
99
+ const currentDate = new Date().toISOString().split('T')[0];
100
+ const filePath = path_1.default.join(__dirname, `rename ${options.from} to ${options.to} ${currentDate}.json`);
101
+ fs_1.default.writeFileSync(filePath, JSON.stringify(results, null, 2), 'utf-8');
102
+ console.info(`Rename changes saved to: ${styles.success(` ${filePath}`)}`);
103
+ }
104
+ if (options.validate && !options.dryRun) {
105
+ // Can't validate if tests is true, changes need to be committed first
106
+ const validationJob = await (0, validate_1.requestValidation)(projectUuid, [], []);
107
+ const { jobId } = validationJob;
108
+ await waitUntilFinished(jobId);
109
+ const validation = await (0, validate_1.getValidation)(projectUuid, jobId);
110
+ console.info(validation);
111
+ }
112
+ }
113
+ catch (e) {
114
+ // We lose the error type on the waitUntilFinished method
115
+ const errorString = `${e}`;
116
+ if (errorString.includes(`Rename failed`)) {
117
+ console.error(errorString);
118
+ if (errorString.includes('was found on multiple models'))
119
+ console.info(`Use argument ${styles.bold('--model')} to specify a model to filter on`);
120
+ }
121
+ else {
122
+ console.error('Unable to rename, unexpected error:', e);
123
+ }
124
+ }
125
+ };
126
+ exports.renameHandler = renameHandler;
@@ -1,10 +1,20 @@
1
- import { ValidationTarget } from '@lightdash/common';
1
+ import { Explore, ExploreError, SchedulerJobStatus, ValidationTarget } from '@lightdash/common';
2
2
  import { CompileHandlerOptions } from './compile';
3
+ export declare const requestValidation: (projectUuid: string, explores: (Explore | ExploreError)[], validationTargets: ValidationTarget[]) => Promise<{
4
+ jobId: string;
5
+ }>;
6
+ export declare const getJobState: (jobUuid: string) => Promise<{
7
+ status: SchedulerJobStatus;
8
+ details: Record<string, import("@lightdash/common").AnyType> | null;
9
+ }>;
10
+ export declare const getValidation: (projectUuid: string, jobId: string) => Promise<import("@lightdash/common").ValidationResponse[]>;
11
+ export declare function delay(ms: number): Promise<unknown>;
3
12
  type ValidateHandlerOptions = CompileHandlerOptions & {
4
13
  project?: string;
5
14
  verbose: boolean;
6
15
  preview: boolean;
7
16
  only: ValidationTarget[];
8
17
  };
18
+ export declare const waitUntilFinished: (jobUuid: string) => Promise<string>;
9
19
  export declare const validateHandler: (options: ValidateHandlerOptions) => Promise<void>;
10
20
  export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateHandler = void 0;
3
+ exports.validateHandler = exports.waitUntilFinished = exports.getValidation = exports.getJobState = exports.requestValidation = void 0;
4
+ exports.delay = delay;
4
5
  const tslib_1 = require("tslib");
5
6
  const common_1 = require("@lightdash/common");
6
7
  const columnify_1 = tslib_1.__importDefault(require("columnify"));
@@ -14,16 +15,19 @@ const requestValidation = async (projectUuid, explores, validationTargets) => (0
14
15
  url: `/api/v1/projects/${projectUuid}/validate`,
15
16
  body: JSON.stringify({ explores, validationTargets }),
16
17
  });
18
+ exports.requestValidation = requestValidation;
17
19
  const getJobState = async (jobUuid) => (0, apiClient_1.lightdashApi)({
18
20
  method: 'GET',
19
21
  url: `/api/v1/schedulers/job/${jobUuid}/status`,
20
22
  body: undefined,
21
23
  });
24
+ exports.getJobState = getJobState;
22
25
  const getValidation = async (projectUuid, jobId) => (0, apiClient_1.lightdashApi)({
23
26
  method: 'GET',
24
27
  url: `/api/v1/projects/${projectUuid}/validate?jobId=${jobId}`,
25
28
  body: undefined,
26
29
  });
30
+ exports.getValidation = getValidation;
27
31
  function delay(ms) {
28
32
  return new Promise((resolve) => {
29
33
  setTimeout(resolve, ms);
@@ -31,15 +35,16 @@ function delay(ms) {
31
35
  }
32
36
  const REFETCH_JOB_INTERVAL = 3000;
33
37
  const waitUntilFinished = async (jobUuid) => {
34
- const job = await getJobState(jobUuid);
38
+ const job = await (0, exports.getJobState)(jobUuid);
35
39
  if (job.status === common_1.SchedulerJobStatus.COMPLETED) {
36
40
  return job.status;
37
41
  }
38
42
  if (job.status === common_1.SchedulerJobStatus.ERROR) {
39
43
  throw new common_1.UnexpectedServerError(`\nValidation failed: ${job.details?.error || 'unknown error'}`);
40
44
  }
41
- return delay(REFETCH_JOB_INTERVAL).then(() => waitUntilFinished(jobUuid));
45
+ return delay(REFETCH_JOB_INTERVAL).then(() => (0, exports.waitUntilFinished)(jobUuid));
42
46
  };
47
+ exports.waitUntilFinished = waitUntilFinished;
43
48
  const validateHandler = async (options) => {
44
49
  globalState_1.default.setVerbose(options.verbose);
45
50
  await (0, apiClient_1.checkLightdashVersion)();
@@ -64,11 +69,11 @@ const validateHandler = async (options) => {
64
69
  }
65
70
  const timeStart = new Date();
66
71
  const validationTargets = options.only ? options.only : [];
67
- const validationJob = await requestValidation(projectUuid, explores, validationTargets);
72
+ const validationJob = await (0, exports.requestValidation)(projectUuid, explores, validationTargets);
68
73
  const { jobId } = validationJob;
69
74
  const spinner = globalState_1.default.startSpinner(` Waiting for validation to finish`);
70
- await waitUntilFinished(jobId);
71
- const validation = await getValidation(projectUuid, jobId);
75
+ await (0, exports.waitUntilFinished)(jobId);
76
+ const validation = await (0, exports.getValidation)(projectUuid, jobId);
72
77
  if (validation.length === 0) {
73
78
  spinner?.succeed(` Validation finished without errors`);
74
79
  }
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ const generate_1 = require("./handlers/generate");
15
15
  const generateExposures_1 = require("./handlers/generateExposures");
16
16
  const login_1 = require("./handlers/login");
17
17
  const preview_1 = require("./handlers/preview");
18
+ const renameHandler_1 = require("./handlers/renameHandler");
18
19
  const setProject_1 = require("./handlers/setProject");
19
20
  const validate_1 = require("./handlers/validate");
20
21
  const styles = tslib_1.__importStar(require("./styles"));
@@ -314,6 +315,19 @@ ${styles.bold('Examples:')}
314
315
  .option('--exclude-meta', 'exclude Lightdash metadata from the generated .yml', false)
315
316
  .option('--verbose', undefined, false)
316
317
  .action(generate_1.generateHandler);
318
+ commander_1.program
319
+ .command('rename')
320
+ .description('Rename models and fields on Lightdash content')
321
+ .option('--verbose', undefined, false)
322
+ .option('-p, --project <project uuid>', 'specify a project UUID to rename', parseProjectArgument, undefined)
323
+ .option('-m, --model <model>', 'When renaming a field, specify which model the field belongs to', undefined)
324
+ .option('-y, --assume-yes', 'assume yes to prompts', false)
325
+ .requiredOption('-t, --type <type>', 'model or field', common_1.RenameType.MODEL)
326
+ .requiredOption('--from <from>', 'Name to replace from', undefined)
327
+ .requiredOption('--to <to>', 'Name to replace to', undefined)
328
+ .option('--dry-run', 'Test the rename, no changes will be made', false)
329
+ .option('--list', 'List all charts and dashboards that are renamed', false)
330
+ .action(renameHandler_1.renameHandler);
317
331
  commander_1.program
318
332
  .command('generate-exposures')
319
333
  .description('[Experimental command] Generates a .yml file for Lightdash exposures')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "0.1621.2",
3
+ "version": "0.1622.0",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "lightdash": "dist/index.js"
@@ -30,8 +30,8 @@
30
30
  "parse-node-version": "^2.0.0",
31
31
  "unique-names-generator": "^4.7.1",
32
32
  "uuid": "^11.0.3",
33
- "@lightdash/common": "0.1621.2",
34
- "@lightdash/warehouses": "0.1621.2"
33
+ "@lightdash/common": "0.1622.0",
34
+ "@lightdash/warehouses": "0.1622.0"
35
35
  },
36
36
  "description": "Lightdash CLI tool",
37
37
  "devDependencies": {