Skip to content

Commit 7412d95

Browse files
Prevent running resources when cli already launched but no run status (#1072)
## Changes * When we launch a run, there is a lag between the CLI process execution and when the IDE is able to poll for the first status update from the API. We should not allow a second run of the resource during this time. This PR blocks second runs by considering the "unknown" status also as a running state for the viewitem context. This allows vscode to hide the run button, even when we, we do not have a status update. Errors and cancellations are still handled gracefully, because the status reaches completed state. * Also, clear the run statuses map on target change. ## Tests <!-- How is this tested? -->
1 parent fa4013c commit 7412d95

File tree

15 files changed

+159
-40
lines changed

15 files changed

+159
-40
lines changed

packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ export type BundleRemoteState = BundleTarget & {
3030
};
3131

3232
/* eslint-enable @typescript-eslint/naming-convention */
33+
export function getResource(
34+
key: string,
35+
resources?: BundleRemoteState["resources"]
36+
) {
37+
return key.split(".").reduce((prev: any, k) => {
38+
if (prev === undefined) {
39+
return undefined;
40+
}
41+
return prev[k];
42+
}, resources ?? {});
43+
}
3344

3445
export class BundleRemoteStateModel extends BaseModelWithStateCache<BundleRemoteState> {
3546
public target: string | undefined;

packages/databricks-vscode/src/bundle/run/BundleRunStatus.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@ import {EventEmitter} from "vscode";
22
import {RunState} from "./types";
33
import {ResourceKey} from "../types";
44
import {BundleRemoteState} from "../models/BundleRemoteStateModel";
5-
5+
import {Time, TimeUnits} from "@databricks/databricks-sdk";
66
export abstract class BundleRunStatus {
77
abstract readonly type: ResourceKey<BundleRemoteState>;
88
protected readonly onDidChangeEmitter = new EventEmitter<void>();
99
readonly onDidChange = this.onDidChangeEmitter.event;
1010
runId: string | undefined;
1111
data: any;
1212

13+
constructor() {
14+
// Timeout in 60 seconds if we don't have a runId till then.
15+
setTimeout(() => {
16+
if (this.runState === "unknown") {
17+
this.runState = "timeout";
18+
}
19+
}, new Time(60, TimeUnits.seconds).toMillSeconds().value);
20+
}
1321
protected _runState: RunState = "unknown";
1422
public get runState(): RunState {
1523
return this._runState;

packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {Disposable, EventEmitter} from "vscode";
2-
import {
3-
BundleRemoteState,
4-
BundleRemoteStateModel,
5-
} from "../models/BundleRemoteStateModel";
2+
import {BundleRemoteState, getResource} from "../models/BundleRemoteStateModel";
63
import {BundleRunTerminalManager} from "./BundleRunTerminalManager";
74
import {JobRunStatus} from "./JobRunStatus";
85
import {AuthProvider} from "../../configuration/auth/AuthProvider";
96
import {BundleRunStatus} from "./BundleRunStatus";
107
import {PipelineRunStatus} from "./PipelineRunStatus";
118
import {Resource, ResourceKey} from "../types";
9+
import {ConfigModel} from "../../configuration/models/ConfigModel";
1210
/**
1311
* This class monitors the cli bundle run output and record ids for runs. It also polls for status of the these runs.
1412
*/
@@ -20,9 +18,16 @@ export class BundleRunStatusManager implements Disposable {
2018
readonly onDidChange = this.onDidChangeEmitter.event;
2119

2220
constructor(
23-
private readonly bundleRemoteStateModel: BundleRemoteStateModel,
21+
private readonly configModel: ConfigModel,
2422
private readonly bundleRunTerminalManager: BundleRunTerminalManager
25-
) {}
23+
) {
24+
this.disposables.push(
25+
this.configModel.onDidChangeTarget(() => {
26+
this.runStatuses.clear();
27+
this.onDidChangeEmitter.fire();
28+
})
29+
);
30+
}
2631

2732
getRunStatusMonitor(
2833
resourceKey: string,
@@ -54,10 +59,12 @@ export class BundleRunStatusManager implements Disposable {
5459
resourceKey: string,
5560
resourceType: ResourceKey<BundleRemoteState>
5661
) {
57-
const target = this.bundleRemoteStateModel.target;
58-
const authProvider = this.bundleRemoteStateModel.authProvider;
59-
const resource =
60-
await this.bundleRemoteStateModel.getResource(resourceKey);
62+
const target = this.configModel.target;
63+
const authProvider = this.configModel.authProvider;
64+
const resource = getResource(
65+
resourceKey,
66+
(await this.configModel.get("remoteStateConfig"))?.resources
67+
);
6168

6269
if (target === undefined) {
6370
throw new Error(`Cannot run ${resourceKey}, Target is undefined`);
@@ -85,6 +92,7 @@ export class BundleRunStatusManager implements Disposable {
8592
this.onDidChangeEmitter.fire();
8693
})
8794
);
95+
this.onDidChangeEmitter.fire();
8896
await this.bundleRunTerminalManager.run(resourceKey, (data) => {
8997
remoteRunStatus.parseId(data);
9098
});

packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class BundleRunTerminalManager implements Disposable {
9999
}, disposables);
100100
window.onDidCloseTerminal((e) => {
101101
// Resolve when the process is closed by human action
102-
e.name === terminal.terminal.name && reject(resolve());
102+
e.name === terminal.terminal.name && resolve();
103103
}, disposables);
104104
});
105105
} finally {
@@ -135,6 +135,8 @@ export class BundleRunTerminalManager implements Disposable {
135135
}
136136

137137
const terminalName = this.getTerminalName(target, resourceKey);
138+
window.terminals.find((i) => i.name === terminalName)?.show();
139+
138140
this.cancellationTokenSources.get(terminalName)?.cancel();
139141
this.cancellationTokenSources.get(terminalName)?.dispose();
140142
this.cancellationTokenSources.delete(terminalName);

packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ export class CustomOutputTerminal implements Pseudoterminal {
6262
this.process.stderr.on("data", handleOutput);
6363

6464
this.process.on("close", (exitCode) => {
65+
if (exitCode === 0) {
66+
this.writeEmitter.fire(
67+
"\x1b[32mProcess completed successfully\x1b[0m\r\n"
68+
);
69+
}
70+
71+
if (exitCode !== 0) {
72+
this.writeEmitter.fire(
73+
"\x1b[31mProcess exited with code " +
74+
exitCode +
75+
"\x1b[0m\r\n"
76+
);
77+
}
78+
6579
this.onDidCloseProcessEmitter.fire(exitCode);
6680
this._process = undefined;
6781
});

packages/databricks-vscode/src/bundle/run/JobRunStatus.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ export class JobRunStatus extends BundleRunStatus {
6666

6767
async cancel(): Promise<void> {
6868
if (this.runId === undefined || this.runState !== "running") {
69-
this.runState = "completed";
69+
this.runState = "cancelled";
7070
return;
7171
}
7272

7373
const client = this.authProvider.getWorkspaceClient();
7474
await (
7575
await client.jobs.cancelRun({run_id: parseInt(this.runId)})
7676
).wait();
77-
this.runState = "completed";
77+
this.runState = "cancelled";
7878
}
7979
}

packages/databricks-vscode/src/bundle/run/PipelineRunStatus.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,17 @@ export class PipelineRunStatus extends BundleRunStatus {
8585
this.runState = "completed";
8686
}
8787

88+
private markCancelled() {
89+
if (this.interval !== undefined) {
90+
clearInterval(this.interval);
91+
this.interval = undefined;
92+
}
93+
this.runState = "cancelled";
94+
}
95+
8896
async cancel() {
8997
if (this.runState !== "running" || this.runId === undefined) {
90-
this.markCompleted();
98+
this.markCancelled();
9199
return;
92100
}
93101

@@ -109,6 +117,6 @@ export class PipelineRunStatus extends BundleRunStatus {
109117
pipeline_id: this.pipelineId,
110118
update_id: this.runId,
111119
});
112-
this.markCompleted();
120+
this.markCancelled();
113121
}
114122
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
export type RunState = "running" | "completed" | "unknown" | "error";
1+
export type RunState =
2+
| "running"
3+
| "completed"
4+
| "unknown"
5+
| "error"
6+
| "timeout"
7+
| "cancelled";

packages/databricks-vscode/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ export async function activate(
534534
bundleRemoteStateModel
535535
);
536536
const bundleRunStatusManager = new BundleRunStatusManager(
537-
bundleRemoteStateModel,
537+
configModel,
538538
bundleRunTerminalManager
539539
);
540540
const bundleResourceExplorerTreeDataProvider =

packages/databricks-vscode/src/ui/bundle-resource-explorer/JobRunStatusTreeNode.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ export class JobRunStatusTreeNode implements BundleResourceExplorerTreeNode {
6868
}
6969

7070
getTreeItem(): BundleResourceExplorerTreeItem {
71+
const runMonitorRunStateTreeItem =
72+
RunStateUtils.getTreeItemFromRunMonitorStatus(
73+
this.type,
74+
this.url,
75+
this.runMonitor
76+
);
77+
78+
if (runMonitorRunStateTreeItem) {
79+
return runMonitorRunStateTreeItem;
80+
}
81+
7182
if (this.runDetails === undefined) {
7283
return {
7384
label: "Run Status",
@@ -76,7 +87,7 @@ export class JobRunStatusTreeNode implements BundleResourceExplorerTreeNode {
7687
contextValue: ContextUtils.getContextString({
7788
nodeType: this.type,
7889
}),
79-
collapsibleState: TreeItemCollapsibleState.Collapsed,
90+
collapsibleState: TreeItemCollapsibleState.None,
8091
};
8192
}
8293

0 commit comments

Comments
 (0)