diff --git a/package-lock.json b/package-lock.json index 63442723a..1423cdbe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "vscode-openshift-connector", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { "@kubernetes/client-node": "^0.16.1", diff --git a/package.json b/package.json index c99774731..e3068d49c 100644 --- a/package.json +++ b/package.json @@ -495,6 +495,12 @@ "title": "Open in Browser", "category": "OpenShift" }, + { + "command": "openshift.component.commands.command.run", + "title": "Run Command", + "category": "OpenShift", + "icon": "$(notebook-execute)" + }, { "command": "openshift.open.developerConsole", "title": "Open Console Dashboard", @@ -990,6 +996,10 @@ "command": "openshift.component.followLog", "when": "view == openshiftProjectExplorer" }, + { + "command": "openshift.component.commands.command.run", + "when": "view == openshiftComponentsView" + }, { "command": "openshift.open.developerConsole", "when": "view == openshiftProjectExplorer" @@ -1622,6 +1632,11 @@ "submenu": "serverlessfunction/removeConfig", "when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctionsWithBuild|localDeployFunctions)$/", "group": "c1@2" + }, + { + "command": "openshift.component.commands.command.run", + "when": "view == openshiftComponentsView && viewItem =~ /openshift\\-component-command.*\\.dev-run.*/", + "group": "inline" } ] }, diff --git a/src/componentsView.ts b/src/componentsView.ts index 099677684..90a378b2c 100644 --- a/src/componentsView.ts +++ b/src/componentsView.ts @@ -7,17 +7,142 @@ import * as path from 'path'; import * as vsc from 'vscode'; import { BaseTreeDataProvider } from './base/baseTreeDataProvider'; import { ComponentWorkspaceFolder, OdoWorkspace } from './odo/workspace'; +import { Command, CommandProvider } from './odo/componentTypeDescription'; import { Component } from './openshift/component'; import { vsCommand } from './vscommand'; +import { ThemeIcon } from 'vscode'; + +interface ComponentInfo extends ComponentWorkspaceFolder { + getParent(): ComponentInfo; + + getChildren() : ComponentInfo[]; + + toTreeItem() : ComponentWorkspaceFolderTreeItem; +} + +abstract class ComponentInfo implements ComponentInfo { + parent : ComponentInfo | null; + + constructor(parent : ComponentInfo, folder : ComponentWorkspaceFolder) { + this.parent = parent; + this.component = folder.component; + this.contextPath = folder.contextPath; + } + + getParent(): ComponentInfo { + return this.parent; + } + + getChildren(): ComponentInfo[] { + return []; + } +} + +class ComponentInfoCommand extends ComponentInfo implements CommandProvider { + command :Command; + + private static icon = new ThemeIcon('terminal-view-icon'); + + constructor(parent : ComponentInfo, command : Command) { + super(parent, parent); + this.command = command; + } + + getCommand(): Command { + return this.command; + } + + toTreeItem() : ComponentWorkspaceFolderTreeItem { + return { + label: this.command.id, + workspaceFolder: this, + tooltip: `Command: ${this.command.id}`, + contextValue: `openshift-component-command${Component.generateContextStateSuffixValue(this)}`, + iconPath: ComponentInfoCommand.icon, + collapsibleState: vsc.TreeItemCollapsibleState.None + }; + } +} + +class ComponentInfoCommands extends ComponentInfo { + private children : ComponentInfo[]; + + constructor (parent : ComponentInfo) { + super(parent, parent); + } + + getChildren(): ComponentInfo[] { + if (!this.children) { + const thisCommands = this.component.devfileData.devfile.commands; + if (thisCommands === undefined) { + this.children = []; + } else { + this.children = thisCommands.flatMap(c => new ComponentInfoCommand(this, c) ); + } + } + return this.children; + } + + toTreeItem() : ComponentWorkspaceFolderTreeItem { + return { + label: 'Commands', + workspaceFolder: this, + tooltip: 'Commands', + contextValue: 'openshift-component-commands', + collapsibleState: vsc.TreeItemCollapsibleState.Collapsed + }; + } +} + +class ComponentInfoRoot extends ComponentInfo { + private children : ComponentInfo[]; + + constructor (folder : ComponentWorkspaceFolder) { + super(null, folder); + } + + getParent(): ComponentInfo { + return this; + } + + getChildren(): ComponentInfo[] { + if (!this.children) { + const thisCommands = this.component.devfileData.devfile.commands; + if (thisCommands === undefined) { + this.children = []; + } else { + this.children = [ new ComponentInfoCommands(this) ]; + } + } + return this.children; + } + + toTreeItem() : ComponentWorkspaceFolderTreeItem { + const tooltip = ['Component', + `Name: ${this.component.devfileData.devfile.metadata.name}`, + `Context: ${this.contextPath}`, + ].join('\n'); + + return { + label: Component.renderLabel(this), + workspaceFolder: this, + tooltip, + contextValue: Component.generateContextValue(this), + iconPath: vsc.Uri.file(path.join(__dirname, '../../images/component', 'workspace.png')), + collapsibleState: vsc.TreeItemCollapsibleState.Collapsed + }; + } +} export interface ComponentWorkspaceFolderTreeItem extends vsc.TreeItem { workspaceFolder: ComponentWorkspaceFolder; } -export class ComponentsTreeDataProvider extends BaseTreeDataProvider { +export class ComponentsTreeDataProvider extends BaseTreeDataProvider { static dataProviderInstance: ComponentsTreeDataProvider; public odoWorkspace = new OdoWorkspace(); + private children : ComponentInfo[]; private constructor() { super(); @@ -30,8 +155,9 @@ export class ComponentsTreeDataProvider extends BaseTreeDataProvider { + createTreeView(id: string): vsc.TreeView { if (!this.treeView) { this.treeView = vsc.window.createTreeView(id, { treeDataProvider: this, @@ -64,25 +190,24 @@ export class ComponentsTreeDataProvider extends BaseTreeDataProvider { - const result = element ? [] : this.odoWorkspace.getComponents(); + getChildren(element?: ComponentInfo): vsc.ProviderResult { + if (element) { + return Promise.resolve(element.getChildren()); + } + + if (this.children) { + return Promise.resolve(this.children); + } + + const result = this.odoWorkspace.getComponents(); return Promise.resolve(result).then(async result1 => { await vsc.commands.executeCommand('setContext', 'openshift.component.explorer.init', !result1?.length && result1.length === 0); - return result; + this.children = result1.flatMap(f => new ComponentInfoRoot(f)); + return this.children; }) } } diff --git a/src/odo/command.ts b/src/odo/command.ts index 43901441e..2e6c0f04a 100644 --- a/src/odo/command.ts +++ b/src/odo/command.ts @@ -311,4 +311,8 @@ export class Command { undefined, [new CommandOption('-o json')]); } + + static runComponentCommand(commandId : string): CommandText { + return new CommandText('odo run', commandId); + } } diff --git a/src/odo/componentTypeDescription.ts b/src/odo/componentTypeDescription.ts index ea60985be..ff44648ca 100644 --- a/src/odo/componentTypeDescription.ts +++ b/src/odo/componentTypeDescription.ts @@ -125,6 +125,10 @@ export interface Command { id: string; } +export interface CommandProvider { + getCommand() : Command | undefined +} + export interface Exec { commandLine: string; component: string; diff --git a/src/openshift/component.ts b/src/openshift/component.ts index 42e827850..a74d10ec6 100644 --- a/src/openshift/component.ts +++ b/src/openshift/component.ts @@ -13,7 +13,7 @@ import * as YAML from 'yaml'; import { CliChannel } from '../cli'; import { Command } from '../odo/command'; import { ascDevfileFirst, ComponentTypeAdapter, ComponentTypeDescription } from '../odo/componentType'; -import { StarterProject } from '../odo/componentTypeDescription'; +import { StarterProject, CommandProvider } from '../odo/componentTypeDescription'; import { ComponentWorkspaceFolder } from '../odo/workspace'; import * as odo3 from '../odo3'; import sendTelemetry, { NewComponentCommandProps } from '../telemetry'; @@ -121,7 +121,7 @@ export class Component extends OpenShiftItem { return state; } - public static generateContextValue(folder: ComponentWorkspaceFolder): string { + public static generateContextStateSuffixValue(folder: ComponentWorkspaceFolder): string { const state = Component.componentStates.get(folder.contextPath); let contextSuffix = ''; if (state.devStatus) { @@ -133,7 +133,11 @@ export class Component extends OpenShiftItem { if (state.deployStatus) { contextSuffix = contextSuffix.concat('.').concat(state.deployStatus); } - return `openshift.component${contextSuffix}`; + return contextSuffix; + } + + public static generateContextValue(folder: ComponentWorkspaceFolder): string { + return `openshift.component${this.generateContextStateSuffixValue(folder)}`; } public static renderLabel(folder: ComponentWorkspaceFolder) { @@ -864,4 +868,24 @@ export class Component extends OpenShiftItem { } } } + + @vsCommand('openshift.component.commands.command.run', true) + static runComponentCommand(componentFolder: ComponentWorkspaceFolder): Promise { + const componentName = componentFolder.component.devfileData.devfile.metadata.name; + if ('getCommand' in componentFolder) { + const componentCommand = (componentFolder).getCommand(); + const command = Command.runComponentCommand(componentCommand.id); + if (Component.isUsingWebviewEditor()) { + DescribeViewLoader.loadView(`Component ${componentName}: Run '${componentCommand.id}' Command`, command, componentFolder); + } else { + void Component.odo.executeInTerminal( + command, + componentFolder.contextPath, + `OpenShift: Component ${componentName}: Run '${componentCommand.id}' Command`); + } + } else { + void window.showErrorMessage(`No Command found in Component '${componentName}`); + } + return; + } }