From 6394f35a35a279f92315e8dc3e6fdb7004fb52c9 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Tue, 14 May 2019 05:30:10 +0300 Subject: [PATCH] Add ability to track che tasks Signed-off-by: Roman Nikitenko --- .../src/browser/che-task-client.ts | 30 ++++++-- .../src/browser/che-task-main.ts | 10 ++- .../src/common/che-protocol.ts | 11 ++- .../src/node/che-task-service.ts | 68 +++++++++++++++--- .../src/plugin/che-api.ts | 4 +- .../src/plugin/che-task-impl.ts | 18 +++-- .../src/che-proposed.d.ts | 15 +++- .../src/che-task-backend-module.ts | 2 + .../task-plugin/src/machine/attach-client.ts | 14 +++- .../src/machine/machine-exec-client.ts | 15 +++- .../src/machine/machine-exec-watcher.ts | 70 +++++++++++++++++++ plugins/task-plugin/src/machine/websocket.ts | 3 +- .../src/preview/task-events-handler.ts | 15 +++- .../src/preview/tasks-preview-manager.ts | 47 ++++++++++--- .../task-plugin/src/task/che-task-runner.ts | 18 ++++- yarn.lock | 4 +- 16 files changed, 294 insertions(+), 50 deletions(-) create mode 100644 plugins/task-plugin/src/machine/machine-exec-watcher.ts diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts index e5bdb2f72..0433669de 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts @@ -17,6 +17,8 @@ export class CheTaskClientImpl implements CheTaskClient { private readonly onKillEventEmitter: Emitter; private taskInfoHandlers: ((id: number) => Promise)[] = []; private runTaskHandlers: ((id: number, config: TaskConfiguration, ctx?: string) => Promise)[] = []; + private taskExitedHandlers: ((id: number) => Promise)[] = []; + constructor() { this.onKillEventEmitter = new Emitter(); } @@ -30,14 +32,28 @@ export class CheTaskClientImpl implements CheTaskClient { async getTaskInfo(id: number): Promise { for (const taskInfoHandler of this.taskInfoHandlers) { - const taskInfo = await taskInfoHandler(id); - if (taskInfo) { - return taskInfo; + try { + const taskInfo = await taskInfoHandler(id); + if (taskInfo) { + return taskInfo; + } + } catch (e) { + // allow another handlers to handle request } } return undefined; } + async onTaskExited(id: number): Promise { + for (const taskExitedHandler of this.taskExitedHandlers) { + try { + await taskExitedHandler(id); + } catch (e) { + // allow another handlers to handle request + } + } + } + get onKillEvent(): Event { return this.onKillEventEmitter.event; } @@ -46,11 +62,15 @@ export class CheTaskClientImpl implements CheTaskClient { this.onKillEventEmitter.fire(id); } - setTaskInfoHandler(handler: (id: number) => Promise) { + addTaskInfoHandler(handler: (id: number) => Promise) { this.taskInfoHandlers.push(handler); } - setRunTaskHandler(handler: (id: number, config: TaskConfiguration, ctx?: string) => Promise) { + addRunTaskHandler(handler: (id: number, config: TaskConfiguration, ctx?: string) => Promise) { this.runTaskHandlers.push(handler); } + + addTaskExitedHandler(handler: (id: number) => Promise) { + this.taskExitedHandlers.push(handler); + } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts index 3e45cf585..83a39e2e0 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts @@ -10,6 +10,7 @@ import { CheTask, CheTaskMain, CheTaskService, CheTaskClient, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; import { interfaces, injectable } from 'inversify'; +import { TaskExitedEvent } from '@eclipse-che/plugin'; @injectable() export class CheTaskMainImpl implements CheTaskMain { @@ -20,8 +21,9 @@ export class CheTaskMainImpl implements CheTaskMain { this.delegate = container.get(CheTaskService); this.cheTaskClient = container.get(CheTaskClient); this.cheTaskClient.onKillEvent(id => proxy.$killTask(id)); - this.cheTaskClient.setTaskInfoHandler(id => proxy.$getTaskInfo(id)); - this.cheTaskClient.setRunTaskHandler((id, config, ctx) => proxy.$runTask(id, config, ctx)); + this.cheTaskClient.addTaskInfoHandler(id => proxy.$getTaskInfo(id)); + this.cheTaskClient.addTaskExitedHandler(id => proxy.$onTaskExited(id)); + this.cheTaskClient.addRunTaskHandler((id, config, ctx) => proxy.$runTask(id, config, ctx)); } $registerTaskRunner(type: string): Promise { return this.delegate.registerTaskRunner(type); @@ -30,4 +32,8 @@ export class CheTaskMainImpl implements CheTaskMain { $disposeTaskRunner(type: string): Promise { return this.delegate.disposeTaskRunner(type); } + + $fireTaskExited(event: TaskExitedEvent): Promise { + return this.delegate.fireTaskExited(event); + } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts index fc209046e..3ccf1f6c1 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts @@ -75,8 +75,9 @@ export interface CheVariablesMain { export interface CheTask { registerTaskRunner(type: string, runner: che.TaskRunner): Promise; - fireTaskExited(taskId: number): Promise; + fireTaskExited(event: che.TaskExitedEvent): Promise; $runTask(id: number, config: che.TaskConfiguration, ctx?: string): Promise; + $onTaskExited(id: number): Promise; $killTask(id: number): Promise; $getTaskInfo(id: number): Promise; } @@ -85,6 +86,7 @@ export const CheTaskMain = Symbol('CheTaskMain'); export interface CheTaskMain { $registerTaskRunner(type: string): Promise; $disposeTaskRunner(type: string): Promise; + $fireTaskExited(event: che.TaskExitedEvent): Promise; } export interface Variable { @@ -400,6 +402,7 @@ export interface CheTaskService extends JsonRpcServer { registerTaskRunner(type: string): Promise; disposeTaskRunner(type: string): Promise; disconnectClient(client: CheTaskClient): void; + fireTaskExited(event: che.TaskExitedEvent): Promise; } export const CheTaskClient = Symbol('CheTaskClient'); @@ -407,7 +410,9 @@ export interface CheTaskClient { runTask(id: number, taskConfig: che.TaskConfiguration, ctx?: string): Promise; killTask(id: number): Promise; getTaskInfo(id: number): Promise; - setTaskInfoHandler(func: (id: number) => Promise): void; - setRunTaskHandler(func: (id: number, config: che.TaskConfiguration, ctx?: string) => Promise): void; + onTaskExited(id: number): Promise; + addTaskInfoHandler(func: (id: number) => Promise): void; + addRunTaskHandler(func: (id: number, config: che.TaskConfiguration, ctx?: string) => Promise): void; + addTaskExitedHandler(func: (id: number) => Promise): void; onKillEvent: Event } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts index 8e486d13a..5a9223fd1 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts @@ -12,6 +12,7 @@ import { injectable, interfaces } from 'inversify'; import { Task, TaskManager, TaskOptions, TaskRunnerRegistry } from '@theia/task/lib/node'; import { Disposable, ILogger } from '@theia/core'; import { TaskConfiguration, TaskInfo } from '@theia/task/lib/common/task-protocol'; +import { TaskExitedEvent } from '@eclipse-che/plugin'; @injectable() export class CheTaskServiceImpl implements CheTaskService { @@ -19,6 +20,7 @@ export class CheTaskServiceImpl implements CheTaskService { private readonly taskManager: TaskManager; private readonly logger: ILogger; private readonly disposableMap: Map; + private readonly cheTasks: CheTask[] = []; private readonly clients: CheTaskClient[]; private taskId: number; constructor(container: interfaces.Container) { @@ -42,7 +44,9 @@ export class CheTaskServiceImpl implements CheTaskService { for (const client of this.clients) { await client.runTask(id, config, ctx); } - return new CheTask(id, this.taskManager, this.logger, { label: config.label, config, context: ctx }, this.clients); + const cheTask = new CheTask(id, this.taskManager, this.logger, { label: config.label, config, context: ctx }, this.clients); + this.cheTasks.push(cheTask); + return cheTask; }; } @@ -67,6 +71,28 @@ export class CheTaskServiceImpl implements CheTaskService { this.clients.splice(idx, 1); } } + + async fireTaskExited(event: TaskExitedEvent): Promise { + for (const task of this.cheTasks) { + try { + const runtimeInfo = await task.getRuntimeInfo(); + if (runtimeInfo.execId === event.execId || runtimeInfo.taskId === event.taskId) { + + task.fireTaskExited({ taskId: task.id, code: event.code, ctx: runtimeInfo.ctx }); + + task.onTaskExited(); + + const index = this.cheTasks.indexOf(task); + if (index > -1) { + this.cheTasks.splice(index, 1); + } + break; + } + } catch (e) { + // allow another handlers to handle request + } + } + } } class CheTask extends Task { @@ -85,18 +111,44 @@ class CheTask extends Task { for (const client of this.clients) { const taskInfo = await client.getTaskInfo(this.taskId); if (taskInfo) { - return { - taskId: this.taskId, - terminalId: taskInfo.terminalId, - ctx: taskInfo.ctx, - config: taskInfo.config - }; + return this.toTaskInfo(taskInfo); } } - throw new Error('Information not found'); + throw new Error(`Runtime Information for task ${this.options.label} is not found`); + } + + async onTaskExited(): Promise { + for (const client of this.clients) { + await client.onTaskExited(this.taskId); + } } async kill(): Promise { this.clients.forEach(client => client.killTask(this.taskId)); } + + fireTaskExited(event: TaskExitedEvent): void { + super.fireTaskExited({ taskId: event.taskId, code: event.code, ctx: event.ctx }); + } + + private toTaskInfo(runtimeInfo: TaskInfo): TaskInfo { + const { taskId, terminalId, ctx, config, ...properties } = runtimeInfo; + const result = { + taskId: this.taskId, + terminalId, + ctx, + config + }; + + if (!properties) { + return result; + } + + for (const key in properties) { + if (properties.hasOwnProperty(key)) { + result[key] = properties[key]; + } + } + return result; + } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts index 04886457d..11d1b027c 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts @@ -119,8 +119,8 @@ export function createAPIFactory(rpc: RPCProtocol): CheApiFactory { registerTaskRunner(type: string, runner: che.TaskRunner): Promise { return cheTaskImpl.registerTaskRunner(type, runner); }, - fireTaskExited(id: number): Promise { - return cheTaskImpl.fireTaskExited(id); + fireTaskExited(event: che.TaskExitedEvent): Promise { + return cheTaskImpl.fireTaskExited(event); } }; diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts index b367f07b9..c232b7900 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts @@ -8,9 +8,8 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ import { CheTask, CheTaskMain, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; -import { TaskRunner, Disposable, Task, TaskInfo } from '@eclipse-che/plugin'; +import { TaskRunner, Disposable, Task, TaskInfo, TaskExitedEvent, TaskConfiguration } from '@eclipse-che/plugin'; import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; -import { TaskConfiguration } from '@theia/task/lib/common'; export class CheTaskImpl implements CheTask { private readonly cheTaskMain: CheTaskMain; @@ -54,15 +53,14 @@ export class CheTaskImpl implements CheTask { } } - async fireTaskExited(taskId: number): Promise { - let id: number | undefined; - this.taskMap.forEach((value: Task, key: number) => { - if (value.getRuntimeInfo().taskId === taskId) { - id = key; - } - }); - if (id) { + async $onTaskExited(id: number): Promise { + const task = this.taskMap.get(id); + if (task) { this.taskMap.delete(id); } } + + async fireTaskExited(event: TaskExitedEvent): Promise { + this.cheTaskMain.$fireTaskExited(event); + } } diff --git a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts index 57e4ad4e1..33417c16a 100644 --- a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts +++ b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts @@ -106,7 +106,7 @@ declare module '@eclipse-che/plugin' { export namespace task { export function registerTaskRunner(type: string, runner: TaskRunner): Promise; /** Needs to be executed when the task is finished */ - export function fireTaskExited(id: number): Promise; + export function fireTaskExited(event: TaskExitedEvent): Promise; } /** A Task Runner knows how to run a Task of a particular type. */ @@ -132,6 +132,19 @@ declare module '@eclipse-che/plugin' { readonly ctx?: string, /** task config used for launching a task */ readonly config: TaskConfiguration + // tslint:disable-next-line:no-any + readonly [key: string]: any; + } + + export interface TaskExitedEvent { + readonly taskId?: number; + readonly ctx?: string; + + readonly code?: number; + readonly signal?: string; + + // tslint:disable-next-line:no-any + readonly [key: string]: any; } export interface TaskConfiguration { diff --git a/plugins/task-plugin/src/che-task-backend-module.ts b/plugins/task-plugin/src/che-task-backend-module.ts index e264baa6d..7f5adb35f 100644 --- a/plugins/task-plugin/src/che-task-backend-module.ts +++ b/plugins/task-plugin/src/che-task-backend-module.ts @@ -15,6 +15,7 @@ import { ProjectPathVariableResolver } from './variable/project-path-variable-re import { CheTaskRunner } from './task/che-task-runner'; import { ServerVariableResolver } from './variable/server-variable-resolver'; import { MachineExecClient } from './machine/machine-exec-client'; +import { MachineExecWatcher } from './machine/machine-exec-watcher'; import { CheTerminalWidget, CheTerminalWidgetOptions, TerminalWidgetFactory } from './machine/terminal-widget'; import { CheTaskEventsHandler } from './preview/task-events-handler'; import { TasksPreviewManager } from './preview/tasks-preview-manager'; @@ -32,6 +33,7 @@ container.bind(CheTaskRunner).toSelf().inSingletonScope(); container.bind(MachinesPicker).toSelf().inSingletonScope(); container.bind(AttachTerminalClient).toSelf().inSingletonScope(); container.bind(MachineExecClient).toSelf().inSingletonScope(); +container.bind(MachineExecWatcher).toSelf().inSingletonScope(); container.bind(ServerVariableResolver).toSelf().inSingletonScope(); container.bind(ProjectPathVariableResolver).toSelf().inSingletonScope(); container.bind(CheWorkspaceClient).toSelf().inSingletonScope(); diff --git a/plugins/task-plugin/src/machine/attach-client.ts b/plugins/task-plugin/src/machine/attach-client.ts index 9261cf644..34ac95c77 100644 --- a/plugins/task-plugin/src/machine/attach-client.ts +++ b/plugins/task-plugin/src/machine/attach-client.ts @@ -12,6 +12,8 @@ import { injectable, inject } from 'inversify'; import { CheWorkspaceClient } from '../che-workspace-client'; import { ReconnectingWebSocket } from './websocket'; import { applySegmentsToUri } from '../uri-helper'; +import { MachineExecWatcher } from './machine-exec-watcher'; +import * as startPoint from '../task-plugin-backend'; const ATTACH_TERMINAL_SEGMENT: string = 'attach'; @@ -25,6 +27,9 @@ export class AttachTerminalClient { @inject(CheWorkspaceClient) protected readonly cheWorkspaceClient!: CheWorkspaceClient; + @inject(MachineExecWatcher) + protected readonly machineExecWatcher: MachineExecWatcher; + async connectTerminalProcess(terminalId: number, outputHandler: TerminalProcessOutputHandler): Promise { const termServerEndpoint = await this.cheWorkspaceClient.getMachineExecServerURL(); @@ -39,6 +44,13 @@ export class AttachTerminalClient { webSocket.onError = (error: Error) => { console.error('Websocket error:', error); }; - // TODO close webSocket when task is completed; event with runtime info is not implemented for plugin API at the moment + + const disposable = this.machineExecWatcher.onExit(event => { + if (event.id === terminalId) { + webSocket.close(); + disposable.dispose(); + } + }); + startPoint.getSubscriptions().push(disposable); } } diff --git a/plugins/task-plugin/src/machine/machine-exec-client.ts b/plugins/task-plugin/src/machine/machine-exec-client.ts index d91a90bf3..1c0579286 100644 --- a/plugins/task-plugin/src/machine/machine-exec-client.ts +++ b/plugins/task-plugin/src/machine/machine-exec-client.ts @@ -9,10 +9,11 @@ **********************************************************************/ import * as rpc from 'vscode-ws-jsonrpc'; -import { injectable, inject } from 'inversify'; +import { injectable, inject, postConstruct } from 'inversify'; import { CheWorkspaceClient } from '../che-workspace-client'; import { createConnection } from './websocket'; import { applySegmentsToUri } from '../uri-helper'; +import { MachineExecWatcher } from './machine-exec-watcher'; const CREATE_METHOD_NAME: string = 'create'; const CONNECT_TERMINAL_SEGMENT: string = 'connect'; @@ -21,6 +22,7 @@ export interface MachineIdentifier { workspaceId: string, machineName: string } + export interface MachineExec { identifier: MachineIdentifier, cmd: string[], @@ -42,6 +44,14 @@ export class MachineExecClient { @inject(CheWorkspaceClient) protected readonly cheWorkspaceClient!: CheWorkspaceClient; + @inject(MachineExecWatcher) + protected readonly machineExecWatcher!: MachineExecWatcher; + + @postConstruct() + protected init() { + this.getConnection(); + } + async getExecId(machineExec: MachineExec): Promise { const connection = await this.getConnection(); const request = new rpc.RequestType(CREATE_METHOD_NAME); @@ -60,6 +70,9 @@ export class MachineExecClient { const execServerUrl = applySegmentsToUri(machineExecServerEndpoint, CONNECT_TERMINAL_SEGMENT); this.connection = await createConnection(execServerUrl); + + this.machineExecWatcher.init(this.connection); + return this.connection; } diff --git a/plugins/task-plugin/src/machine/machine-exec-watcher.ts b/plugins/task-plugin/src/machine/machine-exec-watcher.ts new file mode 100644 index 000000000..79c34932a --- /dev/null +++ b/plugins/task-plugin/src/machine/machine-exec-watcher.ts @@ -0,0 +1,70 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import * as rpc from 'vscode-ws-jsonrpc'; +import { injectable } from 'inversify'; +import * as theia from '@theia/plugin'; + +const EXIT_METHOD_NAME: string = 'onExecExit'; +const ERROR_METHOD_NAME: string = 'onExecError'; + +const EXIT_CODE_PATTERN = /exit code (\d+)/; +const SUCCESS_EXIT_CODE = 0; +const GENERAL_ERROR_EXIT_CODE = 1; + +interface ExecExitEvent { + id: number; + code: number; +} + +interface ExecErrorEvent { + id: number; + stack: string; +} + +@injectable() +export class MachineExecWatcher { + readonly exitEmitter: theia.EventEmitter; + + constructor() { + this.exitEmitter = new theia.EventEmitter(); + } + + init(connection: rpc.MessageConnection) { + + const exitNotification = new rpc.NotificationType(EXIT_METHOD_NAME); + connection.onNotification(exitNotification, (event: ExecExitEvent) => { + this.exitEmitter.fire({ id: event.id, code: SUCCESS_EXIT_CODE }); + }); + + const errorNotification = new rpc.NotificationType(ERROR_METHOD_NAME); + connection.onNotification(errorNotification, (event: ExecErrorEvent) => { + this.exitEmitter.fire({ id: event.id, code: this.toErrorCode(event) }); + }); + } + + get onExit(): theia.Event { + return this.exitEmitter.event; + } + + private toErrorCode(event: ExecErrorEvent): number { + const stack = event.stack; + if (!stack) { + return GENERAL_ERROR_EXIT_CODE; + } + + const matches = stack.match(EXIT_CODE_PATTERN); + if (matches && matches[1]) { + return parseInt(matches[1]); + } + + return GENERAL_ERROR_EXIT_CODE; + } +} diff --git a/plugins/task-plugin/src/machine/websocket.ts b/plugins/task-plugin/src/machine/websocket.ts index 2b55c3845..1374faa5a 100644 --- a/plugins/task-plugin/src/machine/websocket.ts +++ b/plugins/task-plugin/src/machine/websocket.ts @@ -77,7 +77,8 @@ export class ReconnectingWebSocket { } public close() { - this.ws.close(); + this.ws.removeAllListeners(); + this.ws.close(1000); } private reconnect(reason: string) { diff --git a/plugins/task-plugin/src/preview/task-events-handler.ts b/plugins/task-plugin/src/preview/task-events-handler.ts index 2c4633c73..3de09268e 100644 --- a/plugins/task-plugin/src/preview/task-events-handler.ts +++ b/plugins/task-plugin/src/preview/task-events-handler.ts @@ -46,8 +46,7 @@ export class CheTaskEventsHandler { if (task.definition.type !== CHE_TASK_TYPE) { return; } - // TODO handle stopped tasks, at the moment we can not track che task end events - console.log('Task is stopped ' + event.execution.task.name); + this.onDidEndTask(task); }, undefined, startPoint.getSubscriptions()); } @@ -58,7 +57,7 @@ export class CheTaskEventsHandler { return; } - this.tasksPreviewManager.showPreviews(); + this.tasksPreviewManager.onTaskStarted(task); const mode = this.taskPreviewMode.get(); switch (mode) { @@ -81,6 +80,16 @@ export class CheTaskEventsHandler { } } + onDidEndTask(task: theia.Task): void { + const cheTaskDefinition = task.definition as CheTaskDefinition; + const previewUrl = cheTaskDefinition.previewUrl; + if (!previewUrl) { + return; + } + + this.tasksPreviewManager.onTaskCompleted(task); + } + async askUser(message: string, url: string) { const item = await theia.window.showInformationMessage(message, {}, PREVIEW_URL_BUTTON_TEXT, GO_TO_BUTTON_TEXT); diff --git a/plugins/task-plugin/src/preview/tasks-preview-manager.ts b/plugins/task-plugin/src/preview/tasks-preview-manager.ts index d7cfd7f07..85172aa09 100644 --- a/plugins/task-plugin/src/preview/tasks-preview-manager.ts +++ b/plugins/task-plugin/src/preview/tasks-preview-manager.ts @@ -44,14 +44,28 @@ export class TasksPreviewManager { async showPreviews() { const executions = theia.tasks.taskExecutions; const tasks = executions.map(execution => execution.task); + const filteredTasks = tasks.filter(task => { + if (task.definition.previewUrl) { + return true; + } + return false; + }); - const context = startPoint.getContext(); - const previewsWidget = await this.previewUrlsWidgetFactory.createWidget({ tasks: tasks }); + const previewsWidget = await this.previewUrlsWidgetFactory.createWidget({ tasks: filteredTasks }); const panel = this.providePanel(); panel.webview.html = previewsWidget.getHtml(); - panel.webview.onDidReceiveMessage(message => this.onMessageReceived(message), undefined, context.subscriptions); - panel.onDidDispose(() => { this.currentPanel = undefined; }, undefined, context.subscriptions); + panel.reveal(undefined, undefined, true); + } + + onTaskStarted(task: theia.Task) { + this.showPreviews(); + } + + onTaskCompleted(task: theia.Task) { + if (this.currentPanel && this.currentPanel.visible) { + this.showPreviews(); + } } // tslint:disable-next-line:no-any @@ -73,19 +87,34 @@ export class TasksPreviewManager { private providePanel(): theia.WebviewPanel { if (this.currentPanel) { - // TODO improve way of updating webview panel - // depends on https://github.com/theia-ide/theia/issues/4342 and https://github.com/theia-ide/theia/issues/4339 - this.currentPanel.dispose(); + return this.currentPanel; } - return this.currentPanel = theia.window.createWebviewPanel(PREVIEW_URL_VIEW_TYPE, PREVIEW_URL_TITLE, { area: theia.WebviewPanelTargetArea.Bottom }, { + this.currentPanel = theia.window.createWebviewPanel(PREVIEW_URL_VIEW_TYPE, PREVIEW_URL_TITLE, { area: theia.WebviewPanelTargetArea.Bottom, preserveFocus: true }, { enableScripts: true, localResourceRoots: [theia.Uri.file(path.join(startPoint.getContext().extensionPath, 'resources'))] }); + + const context = startPoint.getContext(); + this.currentPanel.webview.onDidReceiveMessage(message => this.onMessageReceived(message), undefined, context.subscriptions); + this.currentPanel.onDidDispose(() => { this.currentPanel = undefined; }, undefined, context.subscriptions); + this.currentPanel.onDidChangeViewState(event => { + if (event.webviewPanel.active) { + this.showPreviews(); + } + }, undefined, context.subscriptions); + + return this.currentPanel; } private async setStatusBarPreviewUrlItem() { - const previewCommandSubscription = theia.commands.registerCommand(STATUS_BAR_PREVIEW, () => this.showPreviews()); + const previewCommandSubscription = theia.commands.registerCommand(STATUS_BAR_PREVIEW, () => { + if (this.currentPanel && this.currentPanel.visible) { + this.currentPanel.dispose(); + } else { + this.showPreviews(); + } + }); startPoint.getSubscriptions().push(previewCommandSubscription); const item = await theia.window.createStatusBarItem(theia.StatusBarAlignment.Left); diff --git a/plugins/task-plugin/src/task/che-task-runner.ts b/plugins/task-plugin/src/task/che-task-runner.ts index fcd3e21c2..84e347565 100644 --- a/plugins/task-plugin/src/task/che-task-runner.ts +++ b/plugins/task-plugin/src/task/che-task-runner.ts @@ -8,13 +8,15 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { injectable, inject } from 'inversify'; +import { injectable, inject, postConstruct } from 'inversify'; import * as che from '@eclipse-che/plugin'; import { CHE_TASK_TYPE } from './task-protocol'; import { MachineExecClient, MachineExec } from '../machine/machine-exec-client'; import { TerminalWidgetFactory } from '../machine/terminal-widget'; import { ProjectPathVariableResolver } from '../variable/project-path-variable-resolver'; import { CheWorkspaceClient } from '../che-workspace-client'; +import { MachineExecWatcher } from '../machine/machine-exec-watcher'; +import * as startPoint from '../task-plugin-backend'; // CHE task gets ID at creating in che task service // https://github.com/eclipse/che-theia/blob/c515f75044f9099820c3b18afb8de83f263d671a/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts#L89 @@ -35,6 +37,17 @@ export class CheTaskRunner { @inject(ProjectPathVariableResolver) protected readonly projectPathVariableResolver!: ProjectPathVariableResolver; + @inject(MachineExecWatcher) + protected readonly machineExecWatcher: MachineExecWatcher; + + @postConstruct() + protected init() { + const disposable = this.machineExecWatcher.onExit(event => { + che.task.fireTaskExited({ execId: event.id, code: event.code }); + }); + startPoint.getSubscriptions().push(disposable); + } + /** * Runs a task from the given task configuration which must have a target property specified. */ @@ -82,7 +95,8 @@ export class CheTaskRunner { ({ taskId: STUB_TASK_ID, ctx: ctx, - config: taskConfig + config: taskConfig, + execId }) }; } catch (error) { diff --git a/yarn.lock b/yarn.lock index 15ce39dd6..3ff953ba5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6846,9 +6846,9 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -"moxios@git://github.com/stoplightio/moxios.git#v1.3.0": +"moxios@git://github.com/stoplightio/moxios#v1.3.0": version "1.3.0" - resolved "git://github.com/stoplightio/moxios.git#9d702c8eafee4b02917d6bc400ae15f1e835cf51" + resolved "git://github.com/stoplightio/moxios#9d702c8eafee4b02917d6bc400ae15f1e835cf51" dependencies: class-autobind "^0.1.4"