diff --git a/packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts b/packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts index cad173f99..4a9ed02c3 100644 --- a/packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts +++ b/packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts @@ -30,6 +30,17 @@ export type BundleRemoteState = BundleTarget & { }; /* eslint-enable @typescript-eslint/naming-convention */ +export function getResource( + key: string, + resources?: BundleRemoteState["resources"] +) { + return key.split(".").reduce((prev: any, k) => { + if (prev === undefined) { + return undefined; + } + return prev[k]; + }, resources ?? {}); +} export class BundleRemoteStateModel extends BaseModelWithStateCache { public target: string | undefined; diff --git a/packages/databricks-vscode/src/bundle/run/BundleRunStatus.ts b/packages/databricks-vscode/src/bundle/run/BundleRunStatus.ts index 3165549bb..f229ceef9 100644 --- a/packages/databricks-vscode/src/bundle/run/BundleRunStatus.ts +++ b/packages/databricks-vscode/src/bundle/run/BundleRunStatus.ts @@ -2,7 +2,7 @@ import {EventEmitter} from "vscode"; import {RunState} from "./types"; import {ResourceKey} from "../types"; import {BundleRemoteState} from "../models/BundleRemoteStateModel"; - +import {Time, TimeUnits} from "@databricks/databricks-sdk"; export abstract class BundleRunStatus { abstract readonly type: ResourceKey; protected readonly onDidChangeEmitter = new EventEmitter(); @@ -10,6 +10,14 @@ export abstract class BundleRunStatus { runId: string | undefined; data: any; + constructor() { + // Timeout in 60 seconds if we don't have a runId till then. + setTimeout(() => { + if (this.runState === "unknown") { + this.runState = "timeout"; + } + }, new Time(60, TimeUnits.seconds).toMillSeconds().value); + } protected _runState: RunState = "unknown"; public get runState(): RunState { return this._runState; diff --git a/packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts b/packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts index 39c428da6..f500301b0 100644 --- a/packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts +++ b/packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts @@ -1,14 +1,12 @@ import {Disposable, EventEmitter} from "vscode"; -import { - BundleRemoteState, - BundleRemoteStateModel, -} from "../models/BundleRemoteStateModel"; +import {BundleRemoteState, getResource} from "../models/BundleRemoteStateModel"; import {BundleRunTerminalManager} from "./BundleRunTerminalManager"; import {JobRunStatus} from "./JobRunStatus"; import {AuthProvider} from "../../configuration/auth/AuthProvider"; import {BundleRunStatus} from "./BundleRunStatus"; import {PipelineRunStatus} from "./PipelineRunStatus"; import {Resource, ResourceKey} from "../types"; +import {ConfigModel} from "../../configuration/models/ConfigModel"; /** * This class monitors the cli bundle run output and record ids for runs. It also polls for status of the these runs. */ @@ -20,9 +18,16 @@ export class BundleRunStatusManager implements Disposable { readonly onDidChange = this.onDidChangeEmitter.event; constructor( - private readonly bundleRemoteStateModel: BundleRemoteStateModel, + private readonly configModel: ConfigModel, private readonly bundleRunTerminalManager: BundleRunTerminalManager - ) {} + ) { + this.disposables.push( + this.configModel.onDidChangeTarget(() => { + this.runStatuses.clear(); + this.onDidChangeEmitter.fire(); + }) + ); + } getRunStatusMonitor( resourceKey: string, @@ -54,10 +59,12 @@ export class BundleRunStatusManager implements Disposable { resourceKey: string, resourceType: ResourceKey ) { - const target = this.bundleRemoteStateModel.target; - const authProvider = this.bundleRemoteStateModel.authProvider; - const resource = - await this.bundleRemoteStateModel.getResource(resourceKey); + const target = this.configModel.target; + const authProvider = this.configModel.authProvider; + const resource = getResource( + resourceKey, + (await this.configModel.get("remoteStateConfig"))?.resources + ); if (target === undefined) { throw new Error(`Cannot run ${resourceKey}, Target is undefined`); @@ -85,6 +92,7 @@ export class BundleRunStatusManager implements Disposable { this.onDidChangeEmitter.fire(); }) ); + this.onDidChangeEmitter.fire(); await this.bundleRunTerminalManager.run(resourceKey, (data) => { remoteRunStatus.parseId(data); }); diff --git a/packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts b/packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts index 798e84192..bfd72d856 100644 --- a/packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts +++ b/packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts @@ -99,7 +99,7 @@ export class BundleRunTerminalManager implements Disposable { }, disposables); window.onDidCloseTerminal((e) => { // Resolve when the process is closed by human action - e.name === terminal.terminal.name && reject(resolve()); + e.name === terminal.terminal.name && resolve(); }, disposables); }); } finally { @@ -135,6 +135,8 @@ export class BundleRunTerminalManager implements Disposable { } const terminalName = this.getTerminalName(target, resourceKey); + window.terminals.find((i) => i.name === terminalName)?.show(); + this.cancellationTokenSources.get(terminalName)?.cancel(); this.cancellationTokenSources.get(terminalName)?.dispose(); this.cancellationTokenSources.delete(terminalName); diff --git a/packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts b/packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts index 3ed957dd4..13ddad148 100644 --- a/packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts +++ b/packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts @@ -62,6 +62,20 @@ export class CustomOutputTerminal implements Pseudoterminal { this.process.stderr.on("data", handleOutput); this.process.on("close", (exitCode) => { + if (exitCode === 0) { + this.writeEmitter.fire( + "\x1b[32mProcess completed successfully\x1b[0m\r\n" + ); + } + + if (exitCode !== 0) { + this.writeEmitter.fire( + "\x1b[31mProcess exited with code " + + exitCode + + "\x1b[0m\r\n" + ); + } + this.onDidCloseProcessEmitter.fire(exitCode); this._process = undefined; }); diff --git a/packages/databricks-vscode/src/bundle/run/JobRunStatus.ts b/packages/databricks-vscode/src/bundle/run/JobRunStatus.ts index 0ef9687bf..a0740d4b8 100644 --- a/packages/databricks-vscode/src/bundle/run/JobRunStatus.ts +++ b/packages/databricks-vscode/src/bundle/run/JobRunStatus.ts @@ -66,7 +66,7 @@ export class JobRunStatus extends BundleRunStatus { async cancel(): Promise { if (this.runId === undefined || this.runState !== "running") { - this.runState = "completed"; + this.runState = "cancelled"; return; } @@ -74,6 +74,6 @@ export class JobRunStatus extends BundleRunStatus { await ( await client.jobs.cancelRun({run_id: parseInt(this.runId)}) ).wait(); - this.runState = "completed"; + this.runState = "cancelled"; } } diff --git a/packages/databricks-vscode/src/bundle/run/PipelineRunStatus.ts b/packages/databricks-vscode/src/bundle/run/PipelineRunStatus.ts index c8f160ae6..9560bfd13 100644 --- a/packages/databricks-vscode/src/bundle/run/PipelineRunStatus.ts +++ b/packages/databricks-vscode/src/bundle/run/PipelineRunStatus.ts @@ -85,9 +85,17 @@ export class PipelineRunStatus extends BundleRunStatus { this.runState = "completed"; } + private markCancelled() { + if (this.interval !== undefined) { + clearInterval(this.interval); + this.interval = undefined; + } + this.runState = "cancelled"; + } + async cancel() { if (this.runState !== "running" || this.runId === undefined) { - this.markCompleted(); + this.markCancelled(); return; } @@ -109,6 +117,6 @@ export class PipelineRunStatus extends BundleRunStatus { pipeline_id: this.pipelineId, update_id: this.runId, }); - this.markCompleted(); + this.markCancelled(); } } diff --git a/packages/databricks-vscode/src/bundle/run/types.ts b/packages/databricks-vscode/src/bundle/run/types.ts index a4ebf0104..07cba9de7 100644 --- a/packages/databricks-vscode/src/bundle/run/types.ts +++ b/packages/databricks-vscode/src/bundle/run/types.ts @@ -1 +1,7 @@ -export type RunState = "running" | "completed" | "unknown" | "error"; +export type RunState = + | "running" + | "completed" + | "unknown" + | "error" + | "timeout" + | "cancelled"; diff --git a/packages/databricks-vscode/src/extension.ts b/packages/databricks-vscode/src/extension.ts index f48601a05..9bed6087a 100644 --- a/packages/databricks-vscode/src/extension.ts +++ b/packages/databricks-vscode/src/extension.ts @@ -534,7 +534,7 @@ export async function activate( bundleRemoteStateModel ); const bundleRunStatusManager = new BundleRunStatusManager( - bundleRemoteStateModel, + configModel, bundleRunTerminalManager ); const bundleResourceExplorerTreeDataProvider = diff --git a/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobRunStatusTreeNode.ts b/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobRunStatusTreeNode.ts index a64177775..296e0bd9e 100644 --- a/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobRunStatusTreeNode.ts +++ b/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobRunStatusTreeNode.ts @@ -68,6 +68,17 @@ export class JobRunStatusTreeNode implements BundleResourceExplorerTreeNode { } getTreeItem(): BundleResourceExplorerTreeItem { + const runMonitorRunStateTreeItem = + RunStateUtils.getTreeItemFromRunMonitorStatus( + this.type, + this.url, + this.runMonitor + ); + + if (runMonitorRunStateTreeItem) { + return runMonitorRunStateTreeItem; + } + if (this.runDetails === undefined) { return { label: "Run Status", @@ -76,7 +87,7 @@ export class JobRunStatusTreeNode implements BundleResourceExplorerTreeNode { contextValue: ContextUtils.getContextString({ nodeType: this.type, }), - collapsibleState: TreeItemCollapsibleState.Collapsed, + collapsibleState: TreeItemCollapsibleState.None, }; } diff --git a/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobTreeNode.ts b/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobTreeNode.ts index afc59a880..0aa2f5127 100644 --- a/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobTreeNode.ts +++ b/packages/databricks-vscode/src/ui/bundle-resource-explorer/JobTreeNode.ts @@ -34,21 +34,21 @@ export class JobTreeNode implements BundleResourceExplorerTreeNode { public parent?: BundleResourceExplorerTreeNode ) {} - isRunning(resourceKey: string) { - const runner = this.bundleRunStatusManager.runStatuses.get(resourceKey); - return runner?.runState === "running"; + get isRunning() { + const runner = this.bundleRunStatusManager.runStatuses.get( + this.resourceKey + ); + return runner?.runState === "running" || runner?.runState === "unknown"; } getTreeItem(): BundleResourceExplorerTreeItem { - const isRunning = this.isRunning(this.resourceKey); - return { label: this.data.name, contextValue: ContextUtils.getContextString({ resourceType: this.type, - running: isRunning, + running: this.isRunning, hasUrl: this.url !== undefined, - cancellable: isRunning, + cancellable: this.isRunning, nodeType: this.type, modifiedStatus: this.data.modified_status, }), @@ -57,7 +57,7 @@ export class JobTreeNode implements BundleResourceExplorerTreeNode { this.data.modified_status ), collapsibleState: DecorationUtils.getCollapsibleState( - isRunning, + this.isRunning, this.data.modified_status ), }; @@ -73,7 +73,7 @@ export class JobTreeNode implements BundleResourceExplorerTreeNode { this.resourceKey ) as JobRunStatus | undefined; - if (runMonitor?.runId !== undefined) { + if (runMonitor) { children.push(new JobRunStatusTreeNode(this, runMonitor)); } children.push( diff --git a/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineRunStatusTreeNode.ts b/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineRunStatusTreeNode.ts index 3c820cfe9..3ace6d14e 100644 --- a/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineRunStatusTreeNode.ts +++ b/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineRunStatusTreeNode.ts @@ -108,6 +108,17 @@ export class PipelineRunStatusTreeNode } getTreeItem(): BundleResourceExplorerTreeItem { + const runMonitorRunStateTreeItem = + RunStateUtils.getTreeItemFromRunMonitorStatus( + this.type, + this.url, + this.runMonitor + ); + + if (runMonitorRunStateTreeItem) { + return runMonitorRunStateTreeItem; + } + if (this.update === undefined) { return { label: "Run Status", @@ -116,7 +127,7 @@ export class PipelineRunStatusTreeNode contextValue: ContextUtils.getContextString({ nodeType: this.type, }), - collapsibleState: TreeItemCollapsibleState.Collapsed, + collapsibleState: TreeItemCollapsibleState.None, }; } diff --git a/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineTreeNode.ts b/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineTreeNode.ts index 0ac0100c7..f8699ecab 100644 --- a/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineTreeNode.ts +++ b/packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineTreeNode.ts @@ -33,7 +33,7 @@ export class PipelineTreeNode implements BundleResourceExplorerTreeNode { isRunning(resourceKey: string) { const runner = this.bundleRunStatusManager.runStatuses.get(resourceKey); - return runner?.runState === "running"; + return runner?.runState === "running" || runner?.runState === "unknown"; } getTreeItem(): BundleResourceExplorerTreeItem { @@ -65,7 +65,7 @@ export class PipelineTreeNode implements BundleResourceExplorerTreeNode { const runMonitor = this.bundleRunStatusManager.runStatuses.get( this.resourceKey ) as PipelineRunStatus | undefined; - if (runMonitor?.data?.update?.update_id !== undefined) { + if (runMonitor) { children.push( new PipelineRunStatusTreeNode( this.connectionManager, diff --git a/packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/RunStateUtils.ts b/packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/RunStateUtils.ts index 52d720e37..3fe2d156b 100644 --- a/packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/RunStateUtils.ts +++ b/packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/RunStateUtils.ts @@ -1,5 +1,11 @@ -import {ThemeColor, ThemeIcon} from "vscode"; +import {ThemeColor, ThemeIcon, TreeItemCollapsibleState} from "vscode"; import {DateUtils} from "../../../utils"; +import {BundleRunStatus} from "../../../bundle/run/BundleRunStatus"; +import {ContextUtils} from "."; +import { + BundleResourceExplorerTreeItem, + BundleResourceExplorerTreeNode, +} from "../types"; export type SimplifiedRunState = | "Terminated" @@ -10,7 +16,8 @@ export type SimplifiedRunState = | "Terminating" | "Cancelled" | "Success" - | "Unknown"; + | "Unknown" + | "Timeout"; export function humaniseDate(timestamp?: number) { if (timestamp === undefined) { @@ -42,7 +49,7 @@ export function getThemeIconForStatus(status: SimplifiedRunState): ThemeIcon { case "Failed": return new ThemeIcon( "testing-error-icon", - new ThemeColor("errorForeground") + new ThemeColor("problemsErrorIcon.foreground") ); case "Skipped": return new ThemeIcon("testing-skipped-icon"); @@ -51,15 +58,17 @@ export function getThemeIconForStatus(status: SimplifiedRunState): ThemeIcon { case "Running": return new ThemeIcon("sync~spin", new ThemeColor("charts.green")); case "Terminating": - return new ThemeIcon( - "sync-ignored~spin", - new ThemeColor("charts.red") - ); + return new ThemeIcon("sync-ignored", new ThemeColor("charts.red")); case "Terminated": case "Cancelled": return new ThemeIcon("circle-slash"); case "Success": return new ThemeIcon("check-all", new ThemeColor("charts.green")); + case "Timeout": + return new ThemeIcon( + "warning", + new ThemeColor("problemsWarningIcon.foreground") + ); default: return new ThemeIcon("question"); } @@ -74,3 +83,34 @@ export function sentenceCase(str?: string, sep: string = "_") { .split(sep) .join(" "); } + +export function getTreeItemFromRunMonitorStatus( + type: BundleResourceExplorerTreeNode["type"], + url?: string, + runMonitor?: BundleRunStatus +): BundleResourceExplorerTreeItem | undefined { + if (runMonitor?.runState === "timeout") { + return { + label: "Run Status", + iconPath: getThemeIconForStatus("Timeout"), + description: "Timeout while fetching run status", + contextValue: ContextUtils.getContextString({ + nodeType: type, + }), + collapsibleState: TreeItemCollapsibleState.None, + }; + } + + if (runMonitor?.runState === "cancelled") { + return { + label: "Run Status", + iconPath: getThemeIconForStatus("Cancelled"), + description: "Cancelled", + contextValue: ContextUtils.getContextString({ + nodeType: type, + hasUrl: url !== undefined, + }), + collapsibleState: TreeItemCollapsibleState.None, + }; + } +} diff --git a/packages/databricks-vscode/src/utils/DateUtils.ts b/packages/databricks-vscode/src/utils/DateUtils.ts index 3b3f3f37d..4691b7d3a 100644 --- a/packages/databricks-vscode/src/utils/DateUtils.ts +++ b/packages/databricks-vscode/src/utils/DateUtils.ts @@ -4,10 +4,10 @@ export function toString(date: Date): string { export function toDateString(date: Date): string { const day = date.getDay(); - const month = date.getMonth() + 1; + const month = date.toLocaleString("default", {month: "short"}); const year = date.getFullYear(); - return `${day}-${month}-${year}`; + return `${day} ${month}, ${year}`; } export function toTimeString(date: Date): string {