Skip to content

Commit b40dee3

Browse files
authored
Watch view implementation to track running 'odo watch' commands (#1612)
* [WIP] Watch view implementation to track running 'odo watch' commands Watch commands can be executed for different components at the same time and that is done in terminal veiw. Over time terminal is getting polluted with different commands and it is getting not easy to navigate and search for watch sessions to see the log, stop or check if watch is running already. This fix shows all components under watch in a view so devs can: 1. See the logs 2. Stop active watch commands 3. Navigate to log if component watch is running already Related to #1294.
1 parent e974c60 commit b40dee3

File tree

9 files changed

+241
-21
lines changed

9 files changed

+241
-21
lines changed

package.json

+24
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@
190190
"onCommand:openshift.component.push.palette",
191191
"onCommand:openshift.component.watch",
192192
"onCommand:openshift.component.watch.palette",
193+
"onCommand:openshift.component.watch.terminate",
194+
"onCommand:openshift.component.watch.showLog",
193195
"onCommand:openshift.catalog.listComponents",
194196
"onCommand:openshift.catalog.listServices",
195197
"onCommand:openshift.url.create",
@@ -434,6 +436,16 @@
434436
"title": "Link Service",
435437
"category": "OpenShift"
436438
},
439+
{
440+
"command": "openshift.component.watch.terminate",
441+
"title": "Stop",
442+
"category": "OpenShift"
443+
},
444+
{
445+
"command": "openshift.component.watch.showLog",
446+
"title": "Show Log",
447+
"category": "OpenShift"
448+
},
437449
{
438450
"command": "openshift.openshiftConsole",
439451
"title": "Open Console Dashboard",
@@ -712,6 +724,10 @@
712724
{
713725
"id": "openshiftProjectExplorer",
714726
"name": "Application Explorer"
727+
},
728+
{
729+
"id": "openshiftWatchView",
730+
"name": "Watch Sessions"
715731
}
716732
]
717733
},
@@ -1143,6 +1159,14 @@
11431159
{
11441160
"command": "openshift.explorer.login.credentialsLogin",
11451161
"when": "view == openshiftProjectExplorer && viewItem == loginRequired"
1162+
},
1163+
{
1164+
"command": "openshift.component.watch.terminate",
1165+
"when": "view == openshiftWatchView && viewItem == openshift.watch.process"
1166+
},
1167+
{
1168+
"command": "openshift.component.watch.showLog",
1169+
"when": "view == openshiftWatchView && viewItem == openshift.watch.process"
11461170
}
11471171
]
11481172
},

src/extension.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { TokenStore } from './util/credentialManager';
2020
import { registerCommands } from './vscommand';
2121
import { ToolsConfig } from './tools';
2222
import { extendClusterExplorer } from './k8s/clusterExplorer';
23+
import { WatchSessionsView } from './watch';
2324

2425
import path = require('path');
2526
import fsx = require('fs-extra');
@@ -68,6 +69,7 @@ export async function activate(extensionContext: ExtensionContext): Promise<any>
6869
commands.executeCommand('extension.vsKubernetesUseNamespace', context),
6970
),
7071
OpenShiftExplorer.getInstance(),
72+
WatchSessionsView.getInstance(),
7173
...Component.init(extensionContext)
7274
];
7375
disposable.forEach((value) => extensionContext.subscriptions.push(value));

src/odo.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface OpenShiftObject extends QuickPickItem {
4949
contextPath?: Uri;
5050
path?: string;
5151
builderImage?: BuilderImage;
52+
iconPath?: Uri;
5253
}
5354

5455
export enum ContextType {
@@ -76,10 +77,20 @@ export abstract class OpenShiftObjectImpl implements OpenShiftObject {
7677
public readonly icon: string,
7778
// eslint-disable-next-line no-shadow
7879
public readonly collapsibleState: TreeItemCollapsibleState = Collapsed,
79-
public contextPath: Uri = undefined,
80+
private contextPathValue: Uri = undefined,
8081
public readonly compType: string = undefined,
8182
public readonly builderImage: BuilderImage = undefined) {
8283
OdoImpl.data.setPathToObject(this);
84+
OdoImpl.data.setContextToObject(this);
85+
}
86+
87+
set contextPath(cp: Uri) {
88+
this.contextPathValue = cp;
89+
OdoImpl.data.setContextToObject(this);
90+
}
91+
92+
get contextPath(): Uri {
93+
return this.contextPathValue;
8394
}
8495

8596
get path(): string {
@@ -290,6 +301,7 @@ export interface Odo {
290301
deleteService(service: OpenShiftObject): Promise<OpenShiftObject>;
291302
deleteURL(url: OpenShiftObject): Promise<OpenShiftObject>;
292303
createComponentCustomUrl(component: OpenShiftObject, name: string, port: string, secure?: boolean): Promise<OpenShiftObject>;
304+
getOpenShiftObjectByContext(context: string): OpenShiftObject;
293305
readonly subject: Subject<OdoEvent>;
294306
}
295307

@@ -307,7 +319,7 @@ class OdoModel {
307319

308320
private pathToObject = new Map<string, OpenShiftObject>();
309321

310-
private contextToObject = new Map<Uri, OpenShiftObject>();
322+
private contextToObject = new Map<string, OpenShiftObject>();
311323

312324
private contextToSettings = new Map<string, odo.Component>();
313325

@@ -342,14 +354,14 @@ class OdoModel {
342354

343355
public setContextToObject(object: OpenShiftObject): void {
344356
if (object.contextPath) {
345-
if (!this.contextToObject.has(object.contextPath)) {
346-
this.contextToObject.set(object.contextPath, object );
357+
if (!this.contextToObject.has(object.contextPath.fsPath)) {
358+
this.contextToObject.set(object.contextPath.fsPath, object );
347359
}
348360
}
349361
}
350362

351363
public getObjectByContext(context: Uri): OpenShiftObject {
352-
return this.contextToObject.get(context);
364+
return this.contextToObject.get(context.fsPath);
353365
}
354366

355367
public setContextToSettings (settings: odo.Component): void {
@@ -392,7 +404,7 @@ class OdoModel {
392404
const array = await item.getParent().getChildren();
393405
array.splice(array.indexOf(item), 1);
394406
this.pathToObject.delete(item.path);
395-
this.contextToObject.delete(item.contextPath);
407+
this.contextToObject.delete(item.contextPath.fsPath);
396408
}
397409

398410
public deleteContext(context: string): void {
@@ -933,6 +945,10 @@ export class OdoImpl implements Odo {
933945
this.subject.next(new OdoEventImpl('changed', null));
934946
}
935947

948+
getOpenShiftObjectByContext(context: string): OpenShiftObject {
949+
return OdoImpl.data.getObjectByContext(Uri.file(context));
950+
}
951+
936952
async loadWorkspaceComponents(event: WorkspaceFoldersChangeEvent): Promise<void> {
937953
const clusters = (await this.getClusters());
938954
if(!clusters) return;
@@ -1009,7 +1025,6 @@ export class OdoImpl implements Odo {
10091025
if ((result2.stdout !== '' && sis.length > 0) || (result1.stdout !== '' && dcs.length > 0)) {
10101026
projectsToMigrate.push(project);
10111027
}
1012-
10131028
}
10141029
if (projectsToMigrate.length > 0) {
10151030
const choice = await window.showWarningMessage(`Found the resources in cluster that must be updated to work with latest release of OpenShift Connector Extension.`, 'Update', 'Help', 'Cancel');

src/openshift/component.ts

+52-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { window, commands, QuickPickItem, Uri, workspace, ExtensionContext, debug, DebugConfiguration, extensions, ProgressLocation, DebugSession, Disposable } from 'vscode';
99
import { ChildProcess , exec } from 'child_process';
1010
import { isURL } from 'validator';
11+
import { Subject } from 'rxjs';
1112
import OpenShiftItem, { selectTargetApplication, selectTargetComponent } from './openshiftItem';
1213
import { OpenShiftObject, ContextType } from '../odo';
1314
import { Command } from "../odo/command";
@@ -30,9 +31,17 @@ import treeKill = require('tree-kill');
3031

3132
const waitPort = require('wait-port');
3233

34+
export class ComponentEvent {
35+
readonly type: 'watchStarted' | 'watchTerminated';
36+
readonly component: OpenShiftObject;
37+
readonly process?: ChildProcess;
38+
}
39+
3340
export class Component extends OpenShiftItem {
3441
public static extensionContext: ExtensionContext;
3542
public static debugSessions: Map<string, DebugSession> = new Map();
43+
public static watchSessions: Map<string, ChildProcess> = new Map();
44+
public static readonly watchSubject: Subject<ComponentEvent> = new Subject<ComponentEvent>();
3645

3746
public static init(context: ExtensionContext): Disposable[] {
3847
Component.extensionContext = context;
@@ -57,6 +66,13 @@ export class Component extends OpenShiftItem {
5766
}
5867
}
5968

69+
static stopWatchSession(component: OpenShiftObject): void {
70+
const ws = Component.watchSessions.get(component.contextPath.fsPath);
71+
if (ws) {
72+
treeKill(ws.pid);
73+
}
74+
}
75+
6076
static async getOpenshiftData(context: OpenShiftObject): Promise<OpenShiftObject> {
6177
return Component.getOpenShiftCmdData(context,
6278
"In which Project you want to create a Component",
@@ -119,6 +135,7 @@ export class Component extends OpenShiftItem {
119135
await Component.unlinkAllComponents(component);
120136
}
121137
Component.stopDebugSession(component);
138+
Component.stopWatchSession(component);
122139
await Component.odo.deleteComponent(component);
123140

124141
}).then(() => `Component '${name}' successfully deleted`)
@@ -140,6 +157,7 @@ export class Component extends OpenShiftItem {
140157
if (value === 'Yes') {
141158
return Progress.execFunctionWithProgress(`Undeploying the Component '${component.getName()} '`, async () => {
142159
Component.stopDebugSession(component);
160+
Component.stopWatchSession(component);
143161
await Component.odo.undeployComponent(component);
144162
}).then(() => `Component '${name}' successfully undeployed`)
145163
.catch((err) => Promise.reject(new VsCommandError(`Failed to undeploy Component with error '${err}'`)));
@@ -451,16 +469,48 @@ export class Component extends OpenShiftItem {
451469
}
452470
}
453471

472+
static addWatchSession(component: OpenShiftObject, process: ChildProcess): void {
473+
Component.watchSessions.set(component.contextPath.fsPath, process);
474+
Component.watchSubject.next({
475+
type: 'watchStarted',
476+
component,
477+
process
478+
});
479+
}
480+
481+
static removeWatchSession(component: OpenShiftObject): void {
482+
Component.watchSessions.delete(component.contextPath.fsPath);
483+
Component.watchSubject.next({
484+
type: 'watchTerminated',
485+
component
486+
});
487+
}
488+
454489
@vsCommand('openshift.component.watch', true)
455490
@selectTargetComponent(
456491
'Select a Project',
457492
'Select an Application',
458493
'Select a Component you want to watch',
459494
(target) => target.contextValue === ContextType.COMPONENT_PUSHED
460495
)
461-
static watch(component: OpenShiftObject): Promise<void> {
496+
static async watch(component: OpenShiftObject): Promise<void> {
462497
if (!component) return null;
463-
Component.odo.executeInTerminal(Command.watchComponent(component.getParent().getParent().getName(), component.getParent().getName(), component.getName()), component.contextPath.fsPath, `OpenShift: Watch '${component.getName()}' Component`);
498+
if (component.compType !== SourceType.LOCAL && component.compType !== SourceType.BINARY) {
499+
window.showInformationMessage(`Watch is supported only for Components with local or binary source type.`)
500+
return null;
501+
}
502+
if (Component.watchSessions.get(component.contextPath.fsPath)) {
503+
const sel = await window.showInformationMessage(`Watch process is already running for '${component.getName()}'`, 'Show Log');
504+
if (sel === 'Show Log') {
505+
commands.executeCommand('openshift.component.watch.showLog', component.contextPath.fsPath);
506+
}
507+
} else {
508+
const process: ChildProcess = await Component.odo.spawn(Command.watchComponent(component.getParent().getParent().getName(), component.getParent().getName(), component.getName()), component.contextPath.fsPath);
509+
Component.addWatchSession(component, process);
510+
process.on('exit', () => {
511+
Component.removeWatchSession(component);
512+
});
513+
}
464514
}
465515

466516
@vsCommand('openshift.component.openUrl', true)

src/view/log/LogViewLoader.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { ExtenisonID } from '../../util/constants';
99
import { OpenShiftObject } from '../../odo';
1010
import * as odo from '../../odo';
1111
import treeKill = require('tree-kill');
12+
import { ChildProcess } from 'child_process';
1213

1314
export default class LogViewLoader {
1415

1516
static get extensionPath() {
1617
return vscode.extensions.getExtension(ExtenisonID).extensionPath
1718
}
1819

19-
static async loadView(title: string, cmdFunction: (prj, app, comp) => string, target: OpenShiftObject): Promise<vscode.WebviewPanel> {
20+
static async loadView(title: string, cmdFunction: (prj, app, comp) => string, target: OpenShiftObject, existingProcess?: ChildProcess): Promise<vscode.WebviewPanel> {
2021
const localResourceRoot = vscode.Uri.file(path.join(LogViewLoader.extensionPath, 'out', 'logViewer'));
2122

2223
const panel = vscode.window.createWebviewPanel('logView', title, vscode.ViewColumn.One, {
@@ -29,9 +30,9 @@ export default class LogViewLoader {
2930
const cmd = cmdFunction(target.getParent().getParent().getName(), target.getParent().getName(), target.getName());
3031

3132
// TODO: When webview is going to be ready?
32-
panel.webview.html = LogViewLoader.getWebviewContent(LogViewLoader.extensionPath, cmd);
33+
panel.webview.html = LogViewLoader.getWebviewContent(LogViewLoader.extensionPath, cmd.replace(/\\/g, '\\\\'));
3334

34-
const process = await odo.getInstance().spawn(cmd, target.contextPath.fsPath);
35+
const process = existingProcess? existingProcess : await odo.getInstance().spawn(cmd, target.contextPath.fsPath);
3536
process.stdout.on('data', (data) => {
3637
panel.webview.postMessage({action: 'add', data: `${data}`.trim().split('\n')});
3738
}).on('close', ()=>{
@@ -43,9 +44,11 @@ export default class LogViewLoader {
4344
recieveDisposable.dispose();
4445
}
4546
})
46-
const disposable = panel.onDidDispose(()=> {
47-
treeKill(process.pid);
48-
disposable.dispose();
47+
panel.onDidDispose(()=> {
48+
process.stdout.removeAllListeners();
49+
if(!existingProcess) {
50+
treeKill(process.pid);
51+
}
4952
});
5053
return panel;
5154
}

src/view/log/app/spinner.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ declare global {
7272
}
7373
}
7474

75+
const vscode = window.acquireVsCodeApi();
76+
7577
function stop() {
76-
const vscode = window.acquireVsCodeApi();
7778
vscode.postMessage({action: 'stop'});
7879
}
7980

0 commit comments

Comments
 (0)