Skip to content

Commit 1770925

Browse files
authored
Merge pull request #202 from matthewpwilson/subtask-progress
Improve progress bar updating
2 parents 6d76f1a + c4f4cf1 commit 1770925

File tree

5 files changed

+217
-19
lines changed

5 files changed

+217
-19
lines changed

__tests__/api/BundleDeploy/BundleDeployer.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import { BundleDeployer } from "../../../src/api/BundleDeploy/BundleDeployer";
1313
import { IHandlerParameters, TaskStage } from "@zowe/imperative";
1414
import * as DeployBundleDefinition from "../../../src/cli/deploy/bundle/DeployBundle.definition";
15-
import * as fse from "fs-extra";
1615
import { ZosmfSession, SubmitJobs, List } from "@zowe/cli";
1716

1817

@@ -69,6 +68,9 @@ describe("BundleDeployer01", () => {
6968
});
7069
afterEach(() => {
7170
jest.restoreAllMocks();
71+
(DEFAULT_PARAMTERS.response.progress.startBar as jest.Mock).mockReset();
72+
(DEFAULT_PARAMTERS.response.progress.endBar as jest.Mock).mockReset();
73+
7274
});
7375
it("should complain with missing zOSMF profile for deploy", async () => {
7476
createSpy.mockImplementationOnce(() => { throw new Error( "Injected Create error" ); });
@@ -506,6 +508,24 @@ describe("BundleDeployer01", () => {
506508
parms.arguments.verbose = false;
507509
await testUndeployJCL(parms);
508510
});
511+
512+
it("should use task passed as parameter on deploy", async () => {
513+
submitSpy.mockImplementationOnce(() => [{ddName: "SYSTSPRT", stepName: "DFHDPLOY", data: "DFHRL2012I"}] );
514+
515+
let parms: IHandlerParameters;
516+
parms = DEFAULT_PARAMTERS;
517+
setCommonParmsForDeployTests(parms);
518+
parms.arguments.csdgroup = "12345678";
519+
520+
const bd = new BundleDeployer(parms);
521+
const task = { percentComplete: 0, stageName: TaskStage.NOT_STARTED, statusMessage: ""};
522+
const response = await bd.deployBundle(undefined, task);
523+
expect(task.stageName).toEqual(TaskStage.COMPLETE);
524+
expect(task.statusMessage).toEqual("Completed DFHDPLOY");
525+
expect(parms.response.progress.startBar).toHaveBeenCalledTimes(0);
526+
expect(parms.response.progress.endBar).toHaveBeenCalledTimes(0);
527+
528+
});
509529
});
510530

511531
async function runDeployTestWithError() {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright IBM Corp, 2019
9+
*
10+
*/
11+
import { SubtaskWithStatus } from "../../../src/api/BundlePush/SubtaskWithStatus";
12+
import { TaskStage, ITaskWithStatus, TaskProgress } from "@zowe/imperative";
13+
14+
15+
let parentTask: ITaskWithStatus;
16+
17+
describe("SubtaskWithStatus", () => {
18+
19+
beforeEach(() => {
20+
parentTask = {
21+
percentComplete: 0,
22+
statusMessage: "status",
23+
stageName: TaskStage.IN_PROGRESS
24+
};
25+
});
26+
it("Should fail with ticks < 0", () => {
27+
expect(() => {
28+
const task = new SubtaskWithStatus(parentTask, -1);
29+
}).toThrow("Ticks must be between 0 and 100");
30+
});
31+
32+
it("Should fail with ticks > 0", () => {
33+
expect(() => {
34+
const task = new SubtaskWithStatus(parentTask, 101);
35+
}).toThrow("Ticks must be between 0 and 100");
36+
});
37+
38+
it("Should set the parent task status", () => {
39+
const subTask = new SubtaskWithStatus(parentTask, 20);
40+
subTask.statusMessage = "Message from subtask";
41+
expect(parentTask.statusMessage).toEqual("Message from subtask");
42+
});
43+
44+
it("Should set the parent task stage", () => {
45+
const subTask = new SubtaskWithStatus(parentTask, 20);
46+
subTask.stageName = TaskStage.FAILED;
47+
expect(parentTask.stageName).toEqual(TaskStage.FAILED);
48+
});
49+
50+
it("Should not set the parent task to completed", () => {
51+
const subTask = new SubtaskWithStatus(parentTask, 20);
52+
subTask.stageName = TaskStage.COMPLETE;
53+
expect(parentTask.stageName).toEqual(TaskStage.IN_PROGRESS);
54+
});
55+
56+
it("Should not set the parent task to NOT_STARTED", () => {
57+
const subTask = new SubtaskWithStatus(parentTask, 20);
58+
subTask.stageName = TaskStage.NOT_STARTED;
59+
expect(parentTask.stageName).toEqual(TaskStage.IN_PROGRESS);
60+
});
61+
62+
it("should set percentComplete on parent task using scaled value", () => {
63+
const subTask = new SubtaskWithStatus(parentTask, 20);
64+
subTask.percentComplete = TaskProgress.FIFTY_PERCENT;
65+
expect(parentTask.percentComplete).toEqual(TaskProgress.TEN_PERCENT);
66+
});
67+
68+
it("should add to percentComplete on parent task using scaled value", () => {
69+
const subTask = new SubtaskWithStatus(parentTask, 40);
70+
parentTask.percentComplete = 50;
71+
subTask.percentComplete = TaskProgress.FIFTY_PERCENT;
72+
expect(parentTask.percentComplete).toEqual(TaskProgress.SEVENTY_PERCENT);
73+
});
74+
75+
it("should add to percentComplete on parent task using scaled value multiple times", () => {
76+
const subTask = new SubtaskWithStatus(parentTask, 40);
77+
parentTask.percentComplete = TaskProgress.FIFTY_PERCENT;
78+
subTask.percentComplete = 25;
79+
subTask.percentComplete = 50;
80+
subTask.percentComplete = 75;
81+
expect(parentTask.percentComplete).toEqual(TaskProgress.EIGHTY_PERCENT);
82+
});
83+
84+
it("should return subtask percentage complete", () => {
85+
const subTask = new SubtaskWithStatus(parentTask, 40);
86+
subTask.percentComplete = 50;
87+
expect(subTask.percentComplete).toEqual(50);
88+
});
89+
90+
it("should allow really small increments", () => {
91+
const subTask = new SubtaskWithStatus(parentTask, 40);
92+
subTask.percentComplete = 50;
93+
for (let i = 0; i < 40; i++) {
94+
subTask.percentComplete += 0.25;
95+
}
96+
expect(parentTask.percentComplete).toBeCloseTo(24);
97+
98+
});
99+
100+
it("should return subtask stage", () => {
101+
const subTask = new SubtaskWithStatus(parentTask, 40);
102+
subTask.stageName = TaskStage.IN_PROGRESS;
103+
expect(subTask.stageName).toEqual(TaskStage.IN_PROGRESS);
104+
});
105+
106+
it("should return subtask status message", () => {
107+
const subTask = new SubtaskWithStatus(parentTask, 40);
108+
subTask.statusMessage = "Status";
109+
expect(subTask.statusMessage).toEqual("Status");
110+
});
111+
});

src/api/BundleDeploy/BundleDeployer.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class BundleDeployer {
3333
private hlqsValidated: boolean = false;
3434
private jobId: string;
3535
private progressBar: ITaskWithStatus;
36+
private useResponseProgressBar = true;
3637
private jobOutput: string = "";
3738

3839
/**
@@ -43,6 +44,9 @@ export class BundleDeployer {
4344
*/
4445
constructor(params: IHandlerParameters) {
4546
this.params = params;
47+
this.progressBar = { percentComplete: 0,
48+
stageName: TaskStage.NOT_STARTED,
49+
statusMessage: ""};
4650
}
4751

4852
/**
@@ -52,7 +56,7 @@ export class BundleDeployer {
5256
* @throws ImperativeError
5357
* @memberof BundleDeployer
5458
*/
55-
public async deployBundle(session?: AbstractSession): Promise<string> {
59+
public async deployBundle(session?: AbstractSession, task?: ITaskWithStatus): Promise<string> {
5660

5761
// Validate that the parms are valid for Deploy
5862
this.validateDeployParms();
@@ -69,7 +73,7 @@ export class BundleDeployer {
6973
const jcl = this.getDeployJCL();
7074

7175
// Submit it
72-
return this.submitJCL(jcl, "DEPLOY", session);
76+
return this.submitJCL(jcl, "DEPLOY", session, task);
7377
}
7478

7579
/**
@@ -79,7 +83,7 @@ export class BundleDeployer {
7983
* @throws ImperativeError
8084
* @memberof BundleDeployer
8185
*/
82-
public async undeployBundle(session?: AbstractSession): Promise<string> {
86+
public async undeployBundle(session?: AbstractSession, task?: ITaskWithStatus): Promise<string> {
8387

8488
// Validate that the parms are valid for Undeploy
8589
this.validateUndeployParms();
@@ -96,7 +100,7 @@ export class BundleDeployer {
96100
const jcl = this.getUndeployJCL();
97101

98102
// Submit it
99-
return this.submitJCL(jcl, "UNDEPLOY", session);
103+
return this.submitJCL(jcl, "UNDEPLOY", session, task);
100104
}
101105

102106
/**
@@ -271,7 +275,7 @@ export class BundleDeployer {
271275

272276
// Have a look at the status message for the progress bar, has it been updated with
273277
// the jobid yet? If so, parse it out and refresh the message.
274-
if (this.jobId === "UNKNOWN") {
278+
if (this.jobId === "UNKNOWN" && this.progressBar.statusMessage) {
275279
const statusWords = this.progressBar.statusMessage.split(" ");
276280
if (statusWords.length >= 2) {
277281
if (statusWords[2] !== undefined && statusWords[2].indexOf("JOB") === 0) {
@@ -300,11 +304,17 @@ export class BundleDeployer {
300304
}
301305
}
302306

303-
private async submitJCL(jcl: string, action: string, session: any): Promise<string> {
307+
private async submitJCL(jcl: string, action: string, session: any, task?: ITaskWithStatus): Promise<string> {
304308
let spoolOutput: any;
305-
this.progressBar = { percentComplete: TaskProgress.TEN_PERCENT,
306-
statusMessage: "Submitting DFHDPLOY JCL for the " + action + " action",
307-
stageName: TaskStage.IN_PROGRESS };
309+
if (task) {
310+
this.progressBar = task;
311+
this.useResponseProgressBar = false;
312+
}
313+
314+
this.progressBar.percentComplete = TaskProgress.TEN_PERCENT;
315+
this.progressBar.statusMessage = "Submitting DFHDPLOY JCL for the " + action + " action";
316+
this.progressBar.stageName = TaskStage.IN_PROGRESS;
317+
308318
this.startProgressBar();
309319
this.jobId = "UNKNOWN";
310320
this.jobOutput = "";
@@ -402,13 +412,13 @@ export class BundleDeployer {
402412
}
403413

404414
private startProgressBar() {
405-
if (this.params.arguments.verbose !== true) {
415+
if (this.params.arguments.verbose !== true && this.useResponseProgressBar === true) {
406416
this.params.response.progress.startBar({task: this.progressBar});
407417
}
408418
}
409419

410420
private endProgressBar() {
411-
if (this.params.arguments.verbose !== true) {
421+
if (this.params.arguments.verbose !== true && this.useResponseProgressBar === true) {
412422
this.params.response.progress.endBar();
413423
}
414424
}

src/api/BundlePush/BundlePusher.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { List, ZosmfSession, SshSession, Shell, Upload, IUploadOptions, ZosFiles
1616
import { getResource, IResourceParms } from "@zowe/cics";
1717
import { BundleDeployer } from "../BundleDeploy/BundleDeployer";
1818
import { Bundle } from "../BundleContent/Bundle";
19+
import { SubtaskWithStatus } from "./SubtaskWithStatus";
1920

2021

2122
/**
@@ -327,11 +328,11 @@ export class BundlePusher {
327328
private async undeployExistingBundle(zosMFSession: AbstractSession, bd: BundleDeployer) {
328329
// End the current progress bar so that UNDEPLOY can create its own
329330
this.updateStatus("Undeploying bundle '" + this.params.arguments.name + "' from CICS");
330-
this.endProgressBar();
331331

332332
const targetstateLocal = this.params.arguments.targetstate;
333333
this.params.arguments.targetstate = "DISCARDED";
334-
await bd.undeployBundle(zosMFSession);
334+
const subtask = new SubtaskWithStatus(this.progressBar, TaskProgress.THIRTY_PERCENT);
335+
await bd.undeployBundle(zosMFSession, subtask);
335336
this.params.arguments.targetstate = targetstateLocal;
336337

337338
// Resume the current progress bar
@@ -343,12 +344,12 @@ export class BundlePusher {
343344
private async deployBundle(zosMFSession: AbstractSession, bd: BundleDeployer, cicsSession: AbstractSession, bundle: Bundle) {
344345
// End the current progress bar so that DEPLOY can create its own
345346
this.updateStatus("Deploying bundle '" + this.params.arguments.name + "' to CICS");
346-
this.endProgressBar();
347+
const subtask = new SubtaskWithStatus(this.progressBar, TaskProgress.THIRTY_PERCENT);
347348

348349
let deployError: Error;
349350
let dfhdployOutput = "";
350351
try {
351-
await bd.deployBundle(zosMFSession);
352+
await bd.deployBundle(zosMFSession, subtask);
352353
}
353354
catch (error) {
354355
// temporarily ignore the error as we might want to generate additional resource
@@ -520,6 +521,7 @@ export class BundlePusher {
520521

521522
const uploadOptions: IUploadOptions = { recursive: true };
522523
uploadOptions.attributes = this.findZosAttributes();
524+
uploadOptions.task = new SubtaskWithStatus(this.progressBar, TaskProgress.TEN_PERCENT);
523525

524526
try {
525527
await Upload.dirToUSSDirRecursive(zosMFSession, this.localDirectory, this.params.arguments.bundledir, uploadOptions);
@@ -548,10 +550,9 @@ export class BundlePusher {
548550
return new ZosFilesAttributes(Bundle.getTemplateZosAttributesFile());
549551
}
550552

551-
private updateStatus(status: string) {
552-
const PERCENT5 = 5;
553+
private updateStatus(status: string, percentageIncrease = 3) {
553554
const MAX_PROGRESS_BAR_MESSAGE = 60;
554-
this.progressBar.percentComplete += PERCENT5;
555+
this.progressBar.percentComplete += percentageIncrease;
555556

556557
if (status.length > MAX_PROGRESS_BAR_MESSAGE)
557558
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright IBM Corp, 2019
9+
*
10+
*/
11+
import { ITaskWithStatus, TaskProgress, TaskStage, ImperativeExpect, ImperativeError, Logger} from "@zowe/imperative";
12+
13+
14+
export class SubtaskWithStatus {
15+
16+
private parent: ITaskWithStatus;
17+
private ticks: number;
18+
private percentCompleteInternal: number;
19+
20+
constructor(parent: ITaskWithStatus, ticks: number) {
21+
this.parent = parent;
22+
this.ticks = ticks;
23+
this.percentCompleteInternal = 0;
24+
if (this.ticks < 0 || this.ticks > TaskProgress.ONE_HUNDRED_PERCENT) {
25+
throw new ImperativeError({msg: "Ticks must be between 0 and 100"});
26+
}
27+
}
28+
29+
public set statusMessage(statusMessage: string) {
30+
this.parent.statusMessage = statusMessage;
31+
}
32+
33+
public get statusMessage() {
34+
return this.parent.statusMessage;
35+
}
36+
37+
public set stageName(stageName: TaskStage) {
38+
if (stageName !== TaskStage.COMPLETE && stageName !== TaskStage.NOT_STARTED) {
39+
this.parent.stageName = stageName;
40+
}
41+
}
42+
43+
public get stageName() {
44+
return this.parent.stageName;
45+
}
46+
47+
public set percentComplete(percentComplete: number) {
48+
const delta = percentComplete - this.percentCompleteInternal;
49+
this.percentCompleteInternal = percentComplete;
50+
this.parent.percentComplete = this.parent.percentComplete + delta * (this.ticks / TaskProgress.ONE_HUNDRED_PERCENT);
51+
}
52+
53+
public get percentComplete() {
54+
return this.percentCompleteInternal;
55+
}
56+
}

0 commit comments

Comments
 (0)