Skip to content

Commit 0b82baf

Browse files
authored
Sfdx user progress bar status bug (#141)
* fix: added print status function status of the progress will be printed in normal format when progress bar is false. * chore: added test case * chore: code refactoring * chore: code refactoring * chore: code refactoring * chore: code refactoring
1 parent 34e5cbc commit 0b82baf

File tree

5 files changed

+185
-48
lines changed

5 files changed

+185
-48
lines changed

src/commands/force/source/deploy.ts

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
87
import * as os from 'os';
98
import { flags, FlagsConfig } from '@salesforce/command';
109
import { Messages } from '@salesforce/core';
11-
import { AsyncResult, DeployResult, MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
10+
import { AsyncResult, DeployResult } from '@salesforce/source-deploy-retrieve';
1211
import { Duration } from '@salesforce/kit';
1312
import { getString, isString } from '@salesforce/ts-types';
1413
import { env, once } from '@salesforce/kit';
@@ -17,6 +16,9 @@ import { DeployCommand } from '../../../deployCommand';
1716
import { ComponentSetBuilder } from '../../../componentSetBuilder';
1817
import { DeployResultFormatter, DeployCommandResult } from '../../../formatters/deployResultFormatter';
1918
import { DeployAsyncResultFormatter, DeployCommandAsyncResult } from '../../../formatters/deployAsyncResultFormatter';
19+
import { ProgressFormatter } from '../../../formatters/progressFormatter';
20+
import { DeployProgressBarFormatter } from '../../../formatters/deployProgressBarFormatter';
21+
import { DeployProgressStatusFormatter } from '../../../formatters/deployProgressStatusFormatter';
2022

2123
Messages.importMessagesDirectory(__dirname);
2224
const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy');
@@ -156,10 +158,12 @@ export class Deploy extends DeployCommand {
156158
this.updateDeployId(deploy.id);
157159

158160
if (!this.isAsync) {
159-
// if SFDX_USE_PROGRESS_BAR is unset or true (default true) AND we're not print JSON output
160-
if (env.getBoolean('SFDX_USE_PROGRESS_BAR', true) && !this.isJsonOutput()) {
161-
this.initProgressBar();
162-
this.progress(deploy);
161+
// we're not print JSON output
162+
if (!this.isJsonOutput()) {
163+
const progressFormatter: ProgressFormatter = env.getBoolean('SFDX_USE_PROGRESS_BAR', true)
164+
? new DeployProgressBarFormatter(this.logger, this.ux)
165+
: new DeployProgressStatusFormatter(this.logger, this.ux);
166+
progressFormatter.progress(deploy);
163167
}
164168
this.deployResult = await deploy.pollStatus(500, waitDuration.seconds);
165169
}
@@ -225,39 +229,4 @@ export class Deploy extends DeployCommand {
225229

226230
return this.isAsync ? this.report(validatedDeployId) : this.poll(validatedDeployId);
227231
}
228-
229-
private progress(deploy: MetadataApiDeploy): void {
230-
const startProgressBar = once((componentTotal: number) => {
231-
this.progressBar.start(componentTotal);
232-
});
233-
234-
deploy.onUpdate((data) => {
235-
// the numCompTot. isn't computed right away, wait to start until we know how many we have
236-
if (data.numberComponentsTotal) {
237-
startProgressBar(data.numberComponentsTotal + data.numberTestsTotal);
238-
this.progressBar.update(data.numberComponentsDeployed + data.numberTestsCompleted);
239-
}
240-
241-
// the numTestsTot. isn't computed until validated as tests by the server, update the PB once we know
242-
if (data.numberTestsTotal && data.numberComponentsTotal) {
243-
this.progressBar.setTotal(data.numberComponentsTotal + data.numberTestsTotal);
244-
}
245-
});
246-
247-
// any thing else should stop the progress bar
248-
deploy.onFinish((data) => {
249-
// the final tick of `onUpdate` is actually fired with `onFinish`
250-
this.progressBar.update(data.response.numberComponentsDeployed + data.response.numberTestsCompleted);
251-
this.progressBar.stop();
252-
});
253-
254-
deploy.onCancel(() => {
255-
this.progressBar.stop();
256-
});
257-
258-
deploy.onError((error: Error) => {
259-
this.progressBar.stop();
260-
throw error;
261-
});
262-
}
263232
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { UX } from '@salesforce/command';
8+
import { Logger } from '@salesforce/core';
9+
import { MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
10+
import { once } from '@salesforce/kit';
11+
import cli from 'cli-ux';
12+
import { ProgressBar } from '../sourceCommand';
13+
import { ProgressFormatter } from './progressFormatter';
14+
export class DeployProgressBarFormatter extends ProgressFormatter {
15+
protected progressBar?: ProgressBar;
16+
public constructor(logger: Logger, ux: UX) {
17+
super(logger, ux);
18+
}
19+
20+
// displays the progress of the Deployment
21+
public progress(deploy: MetadataApiDeploy): void {
22+
this.initProgressBar();
23+
const startProgressBar = once((componentTotal: number) => {
24+
this.progressBar.start(componentTotal);
25+
});
26+
27+
deploy.onUpdate((data) => {
28+
// the numCompTot. isn't computed right away, wait to start until we know how many we have
29+
if (data.numberComponentsTotal) {
30+
startProgressBar(data.numberComponentsTotal + data.numberTestsTotal);
31+
this.progressBar.update(data.numberComponentsDeployed + data.numberTestsCompleted);
32+
}
33+
34+
// the numTestsTot. isn't computed until validated as tests by the server, update the PB once we know
35+
if (data.numberTestsTotal && data.numberComponentsTotal) {
36+
this.progressBar.setTotal(data.numberComponentsTotal + data.numberTestsTotal);
37+
}
38+
});
39+
40+
// any thing else should stop the progress bar
41+
deploy.onFinish((data) => {
42+
// the final tick of `onUpdate` is actually fired with `onFinish`
43+
this.progressBar.update(data.response.numberComponentsDeployed + data.response.numberTestsCompleted);
44+
this.progressBar.stop();
45+
});
46+
47+
deploy.onCancel(() => {
48+
this.progressBar.stop();
49+
});
50+
51+
deploy.onError((error: Error) => {
52+
this.progressBar.stop();
53+
throw error;
54+
});
55+
}
56+
57+
// used to intialise the progress bar
58+
protected initProgressBar(): void {
59+
this.logger.debug('initializing progress bar');
60+
this.progressBar = cli.progress({
61+
format: 'SOURCE PROGRESS | {bar} | {value}/{total} Components',
62+
barCompleteChar: '\u2588',
63+
barIncompleteChar: '\u2591',
64+
linewrap: true,
65+
}) as ProgressBar;
66+
}
67+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import * as chalk from 'chalk';
8+
import { UX } from '@salesforce/command';
9+
import { Logger } from '@salesforce/core';
10+
import { getNumber } from '@salesforce/ts-types';
11+
import { MetadataApiDeployStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
12+
import { MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
13+
import { Duration } from '@salesforce/kit';
14+
import { ProgressFormatter } from './progressFormatter';
15+
export class DeployProgressStatusFormatter extends ProgressFormatter {
16+
private previousComponents = -1;
17+
private previousTests = -1;
18+
public constructor(logger: Logger, ux: UX) {
19+
super(logger, ux);
20+
}
21+
22+
// This can be used to print the progress of the deployment.
23+
public progress(deploy: MetadataApiDeploy): void {
24+
deploy.onUpdate((data) => {
25+
// Printing status only when number of components or tests gets changed in progress.
26+
if (data.numberComponentsDeployed > this.previousComponents || data.numberTestsCompleted > this.previousTests) {
27+
this.printDeployStatus(data);
28+
this.previousComponents = data.numberComponentsDeployed;
29+
this.previousTests = data.numberTestsCompleted;
30+
}
31+
});
32+
deploy.onFinish((data) => {
33+
this.printDeployStatus(data.response);
34+
});
35+
deploy.onError((error: Error) => {
36+
throw error;
37+
});
38+
}
39+
40+
// Prints Deploying status
41+
private printDeployStatus(data: MetadataApiDeployStatus): void {
42+
if (!data.done) {
43+
this.ux.log('');
44+
this.ux.styledHeader(chalk.yellow(`Status: ${data.status}`));
45+
this.ux.log('');
46+
} else {
47+
if (data.completedDate) {
48+
const deployStart: number = new Date(data.createdDate).getTime();
49+
const deployEnd: number = new Date(data.completedDate).getTime();
50+
const duration = Duration.seconds((deployEnd - deployStart) / 1000);
51+
this.ux.log(`Deployment finished in ${duration.toString()} `);
52+
}
53+
this.ux.log('');
54+
const header: string = data.success ? chalk.green(`Result: ${data.status}`) : chalk.red(`Result: ${data.status}`);
55+
this.ux.styledHeader(header);
56+
this.ux.log('');
57+
}
58+
const componentsTotal = getNumber(data, 'numberComponentsTotal');
59+
if (componentsTotal) {
60+
const componentsDeployed = getNumber(data, 'numberComponentsDeployed');
61+
const componentErrors = getNumber(data, 'numberComponentErrors');
62+
const testsTotal = getNumber(data, 'numberTestsTotal');
63+
const testsCompleted = getNumber(data, 'numberTestsCompleted');
64+
const testErrors = getNumber(data, 'numberTestErrors');
65+
const deploys = `${componentsDeployed}/${componentsTotal} components deployed.`;
66+
const deployErrors = componentErrors === 1 ? `${componentErrors} error.` : `${componentErrors} errors.`;
67+
const tests = `${testsCompleted}/${testsTotal} tests completed.`;
68+
const testErrs = testErrors === 1 ? `${testErrors} error.` : `${testErrors} errors.`;
69+
this.ux.log(`${deploys} ${deployErrors}`);
70+
this.ux.log(`${tests} ${testErrs}`);
71+
} else {
72+
this.ux.log('No components deployed');
73+
}
74+
this.ux.log('');
75+
}
76+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { UX } from '@salesforce/command';
9+
import { Logger } from '@salesforce/core';
10+
import { MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
11+
12+
export abstract class ProgressFormatter {
13+
public logger: Logger;
14+
public ux: UX;
15+
16+
public constructor(logger: Logger, ux: UX) {
17+
this.logger = logger;
18+
this.ux = ux;
19+
}
20+
21+
public abstract progress(deploy: MetadataApiDeploy): void;
22+
}

test/commands/source/deploy.test.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import {
2020
DeployAsyncResultFormatter,
2121
} from '../../../src/formatters/deployAsyncResultFormatter';
2222
import { ComponentSetBuilder, ComponentSetOptions } from '../../../src/componentSetBuilder';
23+
import { DeployProgressBarFormatter } from '../../../src/formatters/deployProgressBarFormatter';
24+
import { DeployProgressStatusFormatter } from '../../../src/formatters/deployProgressStatusFormatter';
2325
import { getDeployResult } from './deployResponses';
2426
import { exampleSourceComponent } from './testConsts';
25-
2627
describe('force:source:deploy', () => {
2728
const sandbox = sinon.createSandbox();
2829
const username = '[email protected]';
@@ -57,7 +58,8 @@ describe('force:source:deploy', () => {
5758
// Stubs
5859
let buildComponentSetStub: sinon.SinonStub;
5960
let initProgressBarStub: sinon.SinonStub;
60-
let progressStub: sinon.SinonStub;
61+
let progressBarStub: sinon.SinonStub;
62+
let progressStatusStub: sinon.SinonStub;
6163
let deployStub: sinon.SinonStub;
6264
let pollStub: sinon.SinonStub;
6365
let lifecycleEmitStub: sinon.SinonStub;
@@ -97,7 +99,8 @@ describe('force:source:deploy', () => {
9799
cmd.setOrg(orgStub);
98100
});
99101
initProgressBarStub = stubMethod(sandbox, cmd, 'initProgressBar');
100-
progressStub = stubMethod(sandbox, cmd, 'progress');
102+
progressBarStub = stubMethod(sandbox, DeployProgressBarFormatter.prototype, 'progress');
103+
progressStatusStub = stubMethod(sandbox, DeployProgressStatusFormatter.prototype, 'progress');
101104
stubMethod(sandbox, UX.prototype, 'log');
102105
stubMethod(sandbox, Deploy.prototype, 'deployRecentValidation').resolves({});
103106
formatterDisplayStub = stubMethod(sandbox, DeployResultFormatter.prototype, 'display');
@@ -172,14 +175,12 @@ describe('force:source:deploy', () => {
172175

173176
const ensureProgressBar = (callCount: number) => {
174177
expect(initProgressBarStub.callCount).to.equal(callCount);
175-
expect(progressStub.callCount).to.equal(callCount);
176178
};
177179

178180
it('should pass along sourcepath', async () => {
179181
const sourcepath = ['somepath'];
180182
const result = await runDeployCmd(['--sourcepath', sourcepath[0], '--json']);
181183
expect(result).to.deep.equal(expectedResults);
182-
expect(progressStub.called).to.be.false;
183184
ensureCreateComponentSetArgs({ sourcepath });
184185
ensureDeployArgs();
185186
ensureHookArgs();
@@ -398,7 +399,8 @@ describe('force:source:deploy', () => {
398399
ensureCreateComponentSetArgs({ sourcepath });
399400
ensureDeployArgs();
400401
ensureHookArgs();
401-
ensureProgressBar(0);
402+
expect(progressStatusStub.calledOnce).to.be.true;
403+
expect(progressBarStub.calledOnce).to.be.false;
402404
} finally {
403405
delete process.env.SFDX_USE_PROGRESS_BAR;
404406
}
@@ -411,7 +413,8 @@ describe('force:source:deploy', () => {
411413
ensureCreateComponentSetArgs({ sourcepath });
412414
ensureDeployArgs();
413415
ensureHookArgs();
414-
ensureProgressBar(1);
416+
expect(progressStatusStub.calledOnce).to.be.false;
417+
expect(progressBarStub.calledOnce).to.be.true;
415418
});
416419

417420
it('should NOT call progress bar because of --json', async () => {

0 commit comments

Comments
 (0)