From 36528e5014da4437bf837f9a6bcb330c46b736d8 Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Wed, 22 Nov 2023 22:21:47 +0530 Subject: [PATCH 1/4] add helm tree node Signed-off-by: msivasubramaniaan --- package.json | 69 +++++++++++++++++++---- src/explorer.ts | 65 +++++++++++++++++---- src/webview/helm-chart/helmChartLoader.ts | 12 ++-- 3 files changed, 117 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 8e2ced5fe..eac747729 100644 --- a/package.json +++ b/package.json @@ -242,7 +242,7 @@ "onCommand:clusters.openshift.deployment.openConsole", "onCommand:clusters.openshift.imagestream.openConsole", "onCommand:openshift.componentTypesView.registry.openInView", - "onCommand:openshift.componentTypesView.registry.openHelmChartsInView", + "onCommand:openshift.helm.openView", "onCommand:openshift.component.deleteConfigurationFiles", "onCommand:openshift.component.deleteSourceFolder", "onWalkthrough:openshiftWalkthrough", @@ -364,20 +364,35 @@ "category": "OpenShift" }, { - "command": "openshift.componentTypesView.registry.openHelmChartsInView", - "title": "Open Helm Charts", + "command": "openshift.helm.openView", + "title": "Open Helm View", "category": "OpenShift" }, { - "command": "openshift.componentTypesView.registry.helmChart.install", + "command": "openshift.helm.install", "title": "Open Helm Charts", "category": "OpenShift" }, { - "command": "openshift.componentTypesView.registry.helmChart.open", + "command": "openshift.helm.add", + "title": "Add Repository", + "category": "OpenShift" + }, + { + "command": "openshift.helm.open", "title": "Open Helm Charts", "category": "OpenShift" }, + { + "command": "openshift.helm.repo.delete", + "title": "Delete", + "category": "OpenShift" + }, + { + "command": "openshift.helm.repo.update", + "title": "Update", + "category": "OpenShift" + }, { "command": "openshift.project.delete", "title": "Delete", @@ -1171,15 +1186,27 @@ "when": "false" }, { - "command": "openshift.componentTypesView.registry.openHelmChartsInView", + "command": "openshift.helm.add", + "when": "false" + }, + { + "command": "openshift.helm.openView", + "when": "false" + }, + { + "command": "openshift.helm.install", + "when": "false" + }, + { + "command": "openshift.helm.open", "when": "false" }, { - "command": "openshift.componentTypesView.registry.helmChart.install", + "command": "openshift.helm.repo.delete", "when": "false" }, { - "command": "openshift.componentTypesView.registry.helmChart.open", + "command": "openshift.helm.repo.update", "when": "false" }, { @@ -1434,7 +1461,7 @@ ], "view/item/context/createService": [ { - "command": "openshift.componentTypesView.registry.openHelmChartsInView", + "command": "openshift.helm.openView", "when": "view == openshiftProjectExplorer && viewItem == openshift.k8sContext && isLoggedIn", "group": "c2" }, @@ -1751,6 +1778,26 @@ "command": "openshift.component.commands.command.run", "when": "view == openshiftComponentsView && viewItem =~ /openshift\\-component-command.*\\.dev-run.*/", "group": "inline" + }, + { + "command": "openshift.helm.add", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", + "group": "c1" + }, + { + "command": "openshift.helm.openView", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", + "group": "c2" + }, + { + "command": "openshift.helm.repo.delete", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", + "group": "c1" + }, + { + "command": "openshift.helm.repo.update", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", + "group": "c2" } ] }, @@ -1808,13 +1855,13 @@ { "id": "helmChart", "title": "Work with Helm Charts", - "description": "Browse the catalog to discover and install Helm Charts.\n[Browse Helm Chart](command:openshift.componentTypesView.registry.openHelmChartsInView)", + "description": "Browse the catalog to discover and install Helm Charts.\n[Browse Helm Chart](command:openshift.helm.openView)", "media": { "image": "images/walkthrough/helm.gif", "altText": "helm chart" }, "completionEvents": [ - "onCommand:openshift.componentTypesView.registry.openHelmChartsInView" + "onCommand:openshift.helm.openView" ] }, { diff --git a/src/explorer.ts b/src/explorer.ts index f01c73a88..531e5e650 100644 --- a/src/explorer.ts +++ b/src/explorer.ts @@ -31,12 +31,20 @@ import { Progress } from './util/progress'; import { FileContentChangeNotifier, WatchUtil } from './util/watch'; import { vsCommand } from './vscommand'; import { CustomResourceDefinitionStub } from './webview/common/createServiceTypes'; +import { HelmRepo } from './webview/helm-chart/helmChartType'; -type ExplorerItem = KubernetesObject | Helm.HelmRelease | Context | TreeItem; +type ExplorerItem = KubernetesObject | Helm.HelmRelease | Context | TreeItem | OpenShiftObject | HelmRepo; + +export type OpenShiftObject = { + kind: string, + metadata: { + name: string + }, +} type PackageJSON = { - version: string; - bugs: string; + version: string; + bugs: string; }; const CREATE_OR_SET_PROJECT_ITEM = { @@ -108,8 +116,8 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos return element; } - if('label' in element) { - return { + if ('label' in element) { + return { contextValue: 'openshift.openConfigFile', label: element.label, collapsibleState: TreeItemCollapsibleState.None, @@ -122,7 +130,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos // check if element is Context instance if ('name' in element && 'cluster' in element && 'user' in element) { // Context instance could be without namespace void commands.executeCommand('setContext', 'isLoggedIn', true); - return { + return { contextValue: 'openshift.k8sContext', label: this.kubeConfig.getCluster(element.cluster).server, collapsibleState: TreeItemCollapsibleState.Collapsed, @@ -130,6 +138,16 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos }; } + if ('name' in element && 'url' in element) { + return { + contextValue: 'openshift.helm.repo', + label: element.name, + tooltip: element.url, + collapsibleState: TreeItemCollapsibleState.None, + iconPath: new ThemeIcon('repo') + }; + } + // It's a Helm installation if ('chart' in element) { return { @@ -150,6 +168,14 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos collapsibleState: TreeItemCollapsibleState.Collapsed, iconPath: path.resolve(__dirname, '../../images/context/project-node.png') } + } else if (element.kind === 'helm') { + return { + contextValue: 'openshift.helm.repos', + label: element.metadata.name, + collapsibleState: TreeItemCollapsibleState.Collapsed, + description: 'Repositories', + iconPath: path.resolve(__dirname, '../../images/context/helm.png') + } } return { contextValue: 'openshift.k8sObject', @@ -181,7 +207,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos const config = getKubeConfigFiles(); const canCreateNamespace = await Oc.Instance.canCreateNamespace(); void commands.executeCommand('setContext', 'canCreateNamespace', canCreateNamespace); - result.unshift({label: process.env.KUBECONFIG ? 'Custom KubeConfig' : 'Default KubeConfig', description: config.join(':')}) + result.unshift({ label: process.env.KUBECONFIG ? 'Custom KubeConfig' : 'Default KubeConfig', description: config.join(':') }) } } catch (err) { // ignore because ether server is not accessible or user is logged out @@ -198,6 +224,12 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos // (3) there is namespace set in context and namespace exists in the cluster // (4) there is namespace set in context and namespace does not exist in the cluster const namespaces = await Odo.Instance.getProjects(); + const helmContext = { + kind: 'helm', + metadata: { + name: 'Helm' + }, + } as OpenShiftObject if (this.kubeContext.namespace) { if (namespaces.find(item => item.name === this.kubeContext.namespace)) { result = [{ @@ -226,8 +258,16 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos result = [CREATE_OR_SET_PROJECT_ITEM] } } + result.push(helmContext); + } else if ('kind' in element && element.kind === 'helm') { + const cliData = await Helm.getHelmRepos(); + if (!cliData.error && !cliData.stderr) { + const helmRepos = JSON.parse(cliData.stdout) as HelmRepo[]; + if (helmRepos?.length > 0) { + result = [...helmRepos]; + } + } } else { - let serviceKinds: CustomResourceDefinitionStub[] = []; try { serviceKinds = await getServiceKindStubs(); @@ -249,6 +289,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos result = await Promise.all(toCollect).then(listOfLists => listOfLists.flatMap(a => a as ExplorerItem[])); } + // don't show Open In Developer Dashboard if not openshift cluster const isOpenshiftCluster = await Oc.Instance.isOpenShiftCluster(); void commands.executeCommand('setContext', 'isOpenshiftCluster', isOpenshiftCluster); @@ -272,7 +313,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos @vsCommand('openshift.resource.load') public static loadResource(component: KubernetesObject) { - void commands.executeCommand('extension.vsKubernetesLoad', {namespace: component.metadata.namespace, kindName: `${component.kind}/${component.metadata.name}`}); + void commands.executeCommand('extension.vsKubernetesLoad', { namespace: component.metadata.namespace, kindName: `${component.kind}/${component.metadata.name}` }); } @vsCommand('openshift.resource.unInstall') @@ -285,13 +326,13 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos @vsCommand('openshift.resource.openInConsole') public static openInConsole(component: KubernetesObject) { - void commands.executeCommand('extension.vsKubernetesLoad', {namespace: component.metadata.namespace, kindName: `${component.kind}/${component.metadata.name}`}); + void commands.executeCommand('extension.vsKubernetesLoad', { namespace: component.metadata.namespace, kindName: `${component.kind}/${component.metadata.name}` }); } @vsCommand('openshift.explorer.reportIssue') static async reportIssue(): Promise { const extensionPath = path.resolve(__dirname, '..', '..'); - const templatePath = path.join(extensionPath,'resources', 'issueReport.md'); + const templatePath = path.join(extensionPath, 'resources', 'issueReport.md'); const template = fs.readFileSync(templatePath, 'utf-8'); return commands.executeCommand('workbench.action.openIssueReporter', { extensionId: 'redhat.vscode-openshift-connector', @@ -301,7 +342,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos @vsCommand('openshift.open.configFile') async openConfigFile(context: TreeItem): Promise { - if(context.description && typeof context.description === 'string'){ + if (context.description && typeof context.description === 'string') { await commands.executeCommand('vscode.open', Uri.file(context.description)); } } diff --git a/src/webview/helm-chart/helmChartLoader.ts b/src/webview/helm-chart/helmChartLoader.ts index 0770d7e5b..b0c48ca9f 100644 --- a/src/webview/helm-chart/helmChartLoader.ts +++ b/src/webview/helm-chart/helmChartLoader.ts @@ -30,7 +30,7 @@ vscode.window.onDidChangeActiveColorTheme((editor: vscode.ColorTheme) => { }); export class HelmCommand { - @vsCommand('openshift.componentTypesView.registry.helmChart.install') + @vsCommand('openshift.helm.install') static async installHelmChart(event: any) { await panel.webview.postMessage({ action: 'installStatus', @@ -63,9 +63,9 @@ export class HelmCommand { }); } - @vsCommand('openshift.componentTypesView.registry.helmChart.open') + @vsCommand('openshift.helm.open') static async openedHelmChart(chartName: any): Promise { - const openedHelmChart = new ExtCommandTelemetryEvent('openshift.componentTypesView.registry.helmChart.open'); + const openedHelmChart = new ExtCommandTelemetryEvent('openshift.helm.open'); openedHelmChart.send(chartName); return Promise.resolve(); } @@ -80,11 +80,11 @@ function helmChartMessageListener(event: any): void { }) break; case 'install': { - void vscode.commands.executeCommand('openshift.componentTypesView.registry.helmChart.install', event); + void vscode.commands.executeCommand('openshift.helm.install', event); break; } case 'openChart': { - void vscode.commands.executeCommand('openshift.componentTypesView.registry.helmChart.open', event.chartName); + void vscode.commands.executeCommand('openshift.helm.open', event.chartName); break; } case 'validateName': { @@ -163,7 +163,7 @@ export default class HelmChartLoader { return panel; } - @vsCommand('openshift.componentTypesView.registry.openHelmChartsInView') + @vsCommand('openshift.helm.openView') public static async openHelmChartInWebview(): Promise { await HelmChartLoader.loadView('Helm Charts'); } From f730f9e73406773cf35e312dd8f3cd395426c16a Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Thu, 23 Nov 2023 21:09:24 +0530 Subject: [PATCH 2/4] add,delete,update and sync functionalities were added Signed-off-by: msivasubramaniaan --- build/esbuild.mjs | 1 + package.json | 36 +- src/explorer.ts | 4 +- src/extension.ts | 1 + src/helm/helm.ts | 45 +- .../helm-chart => helm}/helmChartType.ts | 0 src/helm/helmCommands.ts | 12 +- src/helm/manageRepository.ts | 128 ++++++ src/webview/common-ext/utils.ts | 34 +- src/webview/common/propertyTypes.ts | 4 +- src/webview/helm-chart/app/helmListItem.tsx | 2 +- src/webview/helm-chart/app/helmModal.tsx | 2 +- src/webview/helm-chart/app/helmSearch.tsx | 2 +- src/webview/helm-chart/helmChartLoader.ts | 2 +- .../app/addRepository.tsx | 165 +++++++ .../helm-manage-repository/app/home.scss | 209 +++++++++ .../helm-manage-repository/app/home.tsx | 25 ++ .../helm-manage-repository/app/index.html | 52 +++ .../helm-manage-repository/app/index.tsx | 12 + .../app/showRepositories.tsx | 423 ++++++++++++++++++ .../helm-manage-repository/app/tsconfig.json | 29 ++ .../app/vsCodeMessage.ts | 37 ++ .../manageRepositoryLoader.ts | 179 ++++++++ test/integration/helm.test.ts | 2 +- tsconfig.json | 1 + 25 files changed, 1348 insertions(+), 59 deletions(-) rename src/{webview/helm-chart => helm}/helmChartType.ts (100%) create mode 100644 src/helm/manageRepository.ts create mode 100644 src/webview/helm-manage-repository/app/addRepository.tsx create mode 100644 src/webview/helm-manage-repository/app/home.scss create mode 100644 src/webview/helm-manage-repository/app/home.tsx create mode 100644 src/webview/helm-manage-repository/app/index.html create mode 100644 src/webview/helm-manage-repository/app/index.tsx create mode 100644 src/webview/helm-manage-repository/app/showRepositories.tsx create mode 100644 src/webview/helm-manage-repository/app/tsconfig.json create mode 100644 src/webview/helm-manage-repository/app/vsCodeMessage.ts create mode 100644 src/webview/helm-manage-repository/manageRepositoryLoader.ts diff --git a/build/esbuild.mjs b/build/esbuild.mjs index 9c2697418..dea1dfd88 100644 --- a/build/esbuild.mjs +++ b/build/esbuild.mjs @@ -14,6 +14,7 @@ const webviews = [ 'create-component', 'devfile-registry', 'helm-chart', + 'helm-manage-repository', 'welcome', 'feedback', 'serverless-function', diff --git a/package.json b/package.json index eac747729..cf87922d4 100644 --- a/package.json +++ b/package.json @@ -374,8 +374,8 @@ "category": "OpenShift" }, { - "command": "openshift.helm.add", - "title": "Add Repository", + "command": "openshift.helm.manageRepository", + "title": "Manage Repositories", "category": "OpenShift" }, { @@ -383,16 +383,6 @@ "title": "Open Helm Charts", "category": "OpenShift" }, - { - "command": "openshift.helm.repo.delete", - "title": "Delete", - "category": "OpenShift" - }, - { - "command": "openshift.helm.repo.update", - "title": "Update", - "category": "OpenShift" - }, { "command": "openshift.project.delete", "title": "Delete", @@ -1186,7 +1176,7 @@ "when": "false" }, { - "command": "openshift.helm.add", + "command": "openshift.helm.manageRepository", "when": "false" }, { @@ -1201,14 +1191,6 @@ "command": "openshift.helm.open", "when": "false" }, - { - "command": "openshift.helm.repo.delete", - "when": "false" - }, - { - "command": "openshift.helm.repo.update", - "when": "false" - }, { "command": "openshift.componentTypesView.registry.openInBrowser", "when": "false" @@ -1780,7 +1762,7 @@ "group": "inline" }, { - "command": "openshift.helm.add", + "command": "openshift.helm.manageRepository", "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", "group": "c1" }, @@ -1788,16 +1770,6 @@ "command": "openshift.helm.openView", "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", "group": "c2" - }, - { - "command": "openshift.helm.repo.delete", - "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", - "group": "c1" - }, - { - "command": "openshift.helm.repo.update", - "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", - "group": "c2" } ] }, diff --git a/src/explorer.ts b/src/explorer.ts index 531e5e650..ce160fc6b 100644 --- a/src/explorer.ts +++ b/src/explorer.ts @@ -31,7 +31,7 @@ import { Progress } from './util/progress'; import { FileContentChangeNotifier, WatchUtil } from './util/watch'; import { vsCommand } from './vscommand'; import { CustomResourceDefinitionStub } from './webview/common/createServiceTypes'; -import { HelmRepo } from './webview/helm-chart/helmChartType'; +import { HelmRepo } from './helm/helmChartType'; type ExplorerItem = KubernetesObject | Helm.HelmRelease | Context | TreeItem | OpenShiftObject | HelmRepo; @@ -264,7 +264,7 @@ export class OpenShiftExplorer implements TreeDataProvider, Dispos if (!cliData.error && !cliData.stderr) { const helmRepos = JSON.parse(cliData.stdout) as HelmRepo[]; if (helmRepos?.length > 0) { - result = [...helmRepos]; + result = [...helmRepos.sort(Helm.ascRepoName)]; } } } else { diff --git a/src/extension.ts b/src/extension.ts index f9d4ec4d4..1060c9327 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -91,6 +91,7 @@ export async function activate(extensionContext: ExtensionContext): Promise diff --git a/src/helm/helm.ts b/src/helm/helm.ts index 94b1f6044..130406a69 100644 --- a/src/helm/helm.ts +++ b/src/helm/helm.ts @@ -4,6 +4,7 @@ *-----------------------------------------------------------------------------------------------*/ import { CliChannel } from '../cli'; import { CliExitData } from '../util/childProcessUtil'; +import { HelmRepo } from './helmChartType'; import * as HelmCommands from './helmCommands'; export type HelmRelease = { @@ -22,7 +23,7 @@ export type HelmRelease = { * @returns a list of all Helm releases in the current namespace on the current cluster */ export async function getHelmReleases(): Promise { - const res = await CliChannel.getInstance().executeTool(HelmCommands.listHelmReleases()); + const res = await CliChannel.getInstance().executeTool(HelmCommands.listHelmReleases(), undefined, false); return JSON.parse(res.stdout) as HelmRelease[]; } @@ -31,21 +32,32 @@ export async function getHelmReleases(): Promise { * * @returns the CLI output data from running the necessary command */ -export async function addHelmRepo(): Promise { - return await CliChannel.getInstance().executeTool(HelmCommands.addHelmRepo()); +export async function addHelmRepo(repoName: string, url: string): Promise { + return await CliChannel.getInstance().executeTool(HelmCommands.addHelmRepo(repoName, url), undefined, false); +} + +/** + * Delete the OpenShift Helm repo to the cluster. + * + * @returns the CLI output data from running the necessary command + */ +export async function deleteHelmRepo(repoName: string): Promise { + return await CliChannel.getInstance().executeTool(HelmCommands.deleteRepo(repoName), undefined, false); } export async function getHelmRepos(): Promise { - return await CliChannel.getInstance().executeTool(HelmCommands.getRepos()); + return await CliChannel.getInstance().executeTool(HelmCommands.getRepos(), undefined, false); } /** * Updates the content of all the Helm repos. * + * @param repo name + * * @returns the CLI output data from running the necessary command */ -export async function updateHelmRepo(): Promise { - return await CliChannel.getInstance().executeTool(HelmCommands.updateHelmRepo()); +export async function updateHelmRepo(renameRepo: string): Promise { + return await CliChannel.getInstance().executeTool(HelmCommands.updateHelmRepo(renameRepo), undefined, false); } /** @@ -63,7 +75,7 @@ export async function installHelmChart( version: string, ): Promise { return await CliChannel.getInstance().executeTool( - HelmCommands.installHelmChart(name, repoName, chartName, version), + HelmCommands.installHelmChart(name, repoName, chartName, version), undefined, false ); } @@ -74,5 +86,22 @@ export async function installHelmChart( * @returns the CLI output data from running the necessary command */ export async function unInstallHelmChart(name: string): Promise { - return await CliChannel.getInstance().executeTool(HelmCommands.unInstallHelmChart(name)); + return await CliChannel.getInstance().executeTool(HelmCommands.unInstallHelmChart(name), undefined, false); +} + +/** + * sort the repo list. + * + * @param helm repo + * @returns the CLI output data from running the necessary command + */ +export function ascRepoName(oldRepo: HelmRepo, newRepo: HelmRepo) { + const oldURLCheck = oldRepo.url.toLowerCase().includes('charts.openshift.io'); + const newURLCheck = newRepo.url.toLowerCase().includes('charts.openshift.io'); + if (oldURLCheck && !newURLCheck) { + return -1; + } else if (newURLCheck && !oldURLCheck) { + return 1; + } + return oldRepo.name.localeCompare(newRepo.name); } diff --git a/src/webview/helm-chart/helmChartType.ts b/src/helm/helmChartType.ts similarity index 100% rename from src/webview/helm-chart/helmChartType.ts rename to src/helm/helmChartType.ts diff --git a/src/helm/helmCommands.ts b/src/helm/helmCommands.ts index 00a2bffbc..94ba19e07 100644 --- a/src/helm/helmCommands.ts +++ b/src/helm/helmCommands.ts @@ -5,12 +5,16 @@ import { CommandOption, CommandText } from '../base/command'; -export function addHelmRepo(): CommandText { - return new CommandText('helm', 'repo add openshift https://charts.openshift.io/'); +export function addHelmRepo(repoName: string, url: string): CommandText { + return new CommandText('helm', `repo add ${repoName} ${url}`); } -export function updateHelmRepo(): CommandText { - return new CommandText('helm', 'repo update'); +export function deleteRepo(repoName: string): CommandText { + return new CommandText('helm', `repo remove ${repoName}`); +} + +export function updateHelmRepo(repoName: string): CommandText { + return new CommandText('helm', `repo update ${repoName}`); } export function getRepos(): CommandText { diff --git a/src/helm/manageRepository.ts b/src/helm/manageRepository.ts new file mode 100644 index 000000000..12e27b5d1 --- /dev/null +++ b/src/helm/manageRepository.ts @@ -0,0 +1,128 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import sendTelemetry from '../telemetry'; +import * as Helm from '../../src/helm/helm'; +import { HelmRepo } from './helmChartType'; + +export class ManageRepository { + + private static instance: ManageRepository; + + static getInstance(): ManageRepository { + if (!ManageRepository.instance) { + ManageRepository.instance = new ManageRepository(); + } + return ManageRepository.instance; + } + + public async updateRepo(repoName: string): Promise { + await sendTelemetry('openshift.helm.manageRepo.update', { + repoName + }); + const result = await Helm.updateHelmRepo(repoName); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.update.error', { + error + }); + void vscode.window.showErrorMessage(error); + return false; + } + void vscode.window.showInformationMessage(result.stdout.substring(result.stdout.toLowerCase().lastIndexOf('successfully'))); + return true; + } + + public async deleteRepo(name: string): Promise { + await sendTelemetry('openshift.helm.manageRepo.delete', { + name + }); + const result = await Helm.deleteHelmRepo(name); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.delete.error', { + error + }); + void vscode.window.showErrorMessage(error); + return false; + } + return true; + } + + public async editRepo(oldRepo: HelmRepo, newName: string, newURL: string): Promise { + await sendTelemetry('openshift.helm.manageRepo.edit', { + oldRepo, + newName, + newURL + }); + + // delete old repo + let result = await Helm.deleteHelmRepo(oldRepo.name); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.delete.oldRepo.error', { + error + }); + void vscode.window.showErrorMessage(error); + return false; + } + + //add new repo + result = await Helm.addHelmRepo(newName, newURL); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.edit.newRepo.error', { + error + }); + await Helm.addHelmRepo(oldRepo.name, oldRepo.url); + void vscode.window.showErrorMessage(error); + return false; + } + await sendTelemetry('openshift.helm.manageRepo.edit.success', { + message: `Repo ${oldRepo.name} edited successfully` + }); + return true; + } + + public async addRepo(name: string, url: string): Promise { + await sendTelemetry('openshift.helm.manageRepo.add', { + name, url + }); + const result = await Helm.addHelmRepo(name, url); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.add.error', { + error + }); + void vscode.window.showErrorMessage(error); + return false; + } + await sendTelemetry('openshift.helm.manageRepo.add.success', { + name, + message: 'Repo added successfully' + }); + void vscode.window.showInformationMessage(`Repository ${name} added successfully`); + return true; + } + + public async list(): Promise { + await sendTelemetry('openshift.helm.manageRepo.list'); + const result = await Helm.getHelmRepos(); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.list.error', { + error + }); + void vscode.window.showErrorMessage(error); + return []; + } + const helmRepos = JSON.parse(result.stdout) as HelmRepo[]; + await sendTelemetry('openshift.helm.manageRepo.list.success', { + helmRepos + }); + return helmRepos; + } +} diff --git a/src/webview/common-ext/utils.ts b/src/webview/common-ext/utils.ts index 7267ee367..9227e0cd1 100644 --- a/src/webview/common-ext/utils.ts +++ b/src/webview/common-ext/utils.ts @@ -9,7 +9,8 @@ import { extensions, Uri, WebviewPanel, WebviewView } from 'vscode'; import * as NameValidator from '../../openshift/nameValidator'; import { ExtensionID } from '../../util/constants'; import { gitUrlParse } from '../../util/gitParse'; -import { validateGitURLProps } from '../common/propertyTypes'; +import { validateURLProps } from '../common/propertyTypes'; +import validator from 'validator'; export type Message = { action: string; data: any; @@ -60,13 +61,34 @@ function isGitURL(host: string): boolean { ].includes(host); } -export function validateGitURL(event: Message): validateGitURLProps { +export function validateURL(event: Message): validateURLProps { + if (typeof event.data === 'string' && (event.data).trim().length === 0) { + return { + url: event.data, + error: true, + helpText: 'Please enter a URL.' + } as validateURLProps + } else if (!validator.isURL(event.data)) { + return { + url: event.data, + error: true, + helpText: 'Entered URL is invalid' + } as validateURLProps + } + return { + url: event.data, + error: false, + helpText: 'URL is valid' + } as validateURLProps +} + +export function validateGitURL(event: Message): validateURLProps { if (typeof event.data === 'string' && (event.data).trim().length === 0) { return { url: event.data, error: true, helpText: 'Please enter a Git URL.' - } as validateGitURLProps + } as validateURLProps } try { const parse = gitUrlParse(event.data); @@ -79,20 +101,20 @@ export function validateGitURL(event: Message): validateGitURLProps { url: event.data, error: false, helpText: 'The git repo URL is valid.' - } as validateGitURLProps + } as validateURLProps } return { url: event.data, error: true, helpText: 'URL is missing organization or repo name.' - } as validateGitURLProps + } as validateURLProps } catch (e) { return { url: event.data, error: true, helpText: 'Invalid Git URL.' - } as validateGitURLProps + } as validateURLProps } } diff --git a/src/webview/common/propertyTypes.ts b/src/webview/common/propertyTypes.ts index 67904c93a..5275a97f6 100644 --- a/src/webview/common/propertyTypes.ts +++ b/src/webview/common/propertyTypes.ts @@ -6,7 +6,7 @@ import { Uri } from 'vscode'; import { ComponentTypeDescription, Registry } from '../../odo/componentType'; import { StarterProject } from '../../odo/componentTypeDescription'; -import { ChartResponse } from '../helm-chart/helmChartType'; +import { ChartResponse } from '../../helm/helmChartType'; export interface DefaultProps { analytics?: import('@segment/analytics-next').Analytics; @@ -87,7 +87,7 @@ export interface RunFunctionPageProps extends DefaultProps { onRunSubmit: (folderPath: Uri, build: boolean) => void; } -export interface validateGitURLProps { +export interface validateURLProps { url: string; error: boolean; helpText: string; diff --git a/src/webview/helm-chart/app/helmListItem.tsx b/src/webview/helm-chart/app/helmListItem.tsx index 3cfc05371..0cb44843a 100644 --- a/src/webview/helm-chart/app/helmListItem.tsx +++ b/src/webview/helm-chart/app/helmListItem.tsx @@ -5,7 +5,7 @@ import { Box, Button, Chip, Link, Stack, SvgIcon, Tooltip, Typography } from '@mui/material'; import * as React from 'react'; import HelmIcon from '../../../../images/helm/helm.svg'; -import { Chart, ChartResponse } from '../helmChartType'; +import { Chart, ChartResponse } from '../../../helm/helmChartType'; import { VSCodeMessage } from '../vsCodeMessage'; import { Launch } from '@mui/icons-material'; diff --git a/src/webview/helm-chart/app/helmModal.tsx b/src/webview/helm-chart/app/helmModal.tsx index 52a71f622..c4ed111d5 100644 --- a/src/webview/helm-chart/app/helmModal.tsx +++ b/src/webview/helm-chart/app/helmModal.tsx @@ -9,7 +9,7 @@ import { Close } from '@mui/icons-material'; import { useMediaQuery, Paper, IconButton, FormControl, InputLabel, Select, MenuItem, FormHelperText, Stack, TextField, Alert } from '@mui/material'; import { HelmListItem } from './helmListItem'; import React from 'react'; -import { Chart, ChartResponse } from '../helmChartType'; +import { Chart, ChartResponse } from '../../../helm/helmChartType'; import { VSCodeMessage } from '../vsCodeMessage'; type Message = { diff --git a/src/webview/helm-chart/app/helmSearch.tsx b/src/webview/helm-chart/app/helmSearch.tsx index 3da938803..8a4e1c49c 100644 --- a/src/webview/helm-chart/app/helmSearch.tsx +++ b/src/webview/helm-chart/app/helmSearch.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Checkbox, Divider, FormControlLabel, FormGroup, IconButton, InputAdornment, Modal, Pagination, Stack, TextField, Tooltip, Typography } from '@mui/material'; import { Close, Search } from '@mui/icons-material'; import { HelmListItem } from './helmListItem'; -import { ChartResponse, HelmRepo } from '../helmChartType'; +import { ChartResponse, HelmRepo } from '../../../helm/helmChartType'; import { VSCodeMessage } from '../vsCodeMessage'; import { LoadScreen } from '../../common/loading'; import { HelmModal } from './helmModal'; diff --git a/src/webview/helm-chart/helmChartLoader.ts b/src/webview/helm-chart/helmChartLoader.ts index b0c48ca9f..f558f7b92 100644 --- a/src/webview/helm-chart/helmChartLoader.ts +++ b/src/webview/helm-chart/helmChartLoader.ts @@ -11,10 +11,10 @@ import sendTelemetry, { ExtCommandTelemetryEvent } from '../../telemetry'; import { ExtensionID } from '../../util/constants'; import { vsCommand } from '../../vscommand'; import { loadWebviewHtml } from '../common-ext/utils'; -import { ChartResponse, HelmRepo } from './helmChartType'; import fetch = require('make-fetch-happen'); import { validateName } from '../common-ext/createComponentHelpers'; import { Progress } from '../../util/progress'; +import { ChartResponse, HelmRepo } from '../../helm/helmChartType'; let panel: vscode.WebviewPanel; const helmCharts: ChartResponse[] = []; diff --git a/src/webview/helm-manage-repository/app/addRepository.tsx b/src/webview/helm-manage-repository/app/addRepository.tsx new file mode 100644 index 000000000..608de9469 --- /dev/null +++ b/src/webview/helm-manage-repository/app/addRepository.tsx @@ -0,0 +1,165 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import * as React from 'react'; +import { Button, Stack, TextField } from '@mui/material'; +import { DefaultProps } from '../../common/propertyTypes'; +import './home.scss'; +import { VSCodeMessage } from './vsCodeMessage'; + +export class AddRepository extends React.Component { + + constructor(props: DefaultProps | Readonly) { + super(props); + this.state = { + input: { + name: '', + error: false, + helpText: '' + }, + repo: { + url: '', + error: false, + helpText: '' + } + } + } + + componentDidMount(): void { + VSCodeMessage.onMessage((message) => { + // eslint-disable-next-line no-console + console.log('Data::', message.data); + if (message.data.action === 'validateURL') { + this.setState({ + repo: { + url: message.data.url, + error: message.data.error, + helpText: message.data.helpText + } + }) + } else if (message.data.action === 'validateName') { + this.setState({ + input: { + name: message.data.name, + error: message.data.error, + helpText: message.data.helpText + } + }) + } + }); + } + + handleButtonDisable(): boolean { + return this.state.input.name?.length === 0 || this.state.input.error + || this.state.repo.url?.length === 0 || this.state.repo.error + } + + validateGitURL = (value: string): void => { + VSCodeMessage.postMessage({ + action: 'validateURL', + data: value + }) + } + + validateName = (value: string): void => { + VSCodeMessage.postMessage({ + action: 'validateName', + data: value + }) + } + + addRepo = (repoName: string, repoURL: string): void => { + VSCodeMessage.postMessage({ + action: 'addRepo', + data: { + repoName, + repoURL + } + }) + } + + + render(): React.ReactNode { + const { input, repo } = this.state; + return ( + + + + this.validateName(e.target.value)} + id='git-name' + placeholder='Provide helm repository name' + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + height: '7px !important', + } + }} + helperText={input.helpText} /> + + + + this.validateGitURL(e.target.value)} + id='git-url' + placeholder='Provide helm repository URL' + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + height: '7px !important', + } + }} + helperText={repo.helpText} /> + + + + + + ) + } +} diff --git a/src/webview/helm-manage-repository/app/home.scss b/src/webview/helm-manage-repository/app/home.scss new file mode 100644 index 000000000..7af54a561 --- /dev/null +++ b/src/webview/helm-manage-repository/app/home.scss @@ -0,0 +1,209 @@ +body { + &.vscode-light { + background-color: var(--color-background--darken-05); + } +} + +button { + padding: 0; +} + +.margin { + margin: 3rem; +} + +.mainContainer, +.formContainer, +.form { + display: flex; + flex-direction: column; + font-family: var(--vscode-font-family) !important; +} + +.title { + width: 100%; + margin-bottom: 1rem; +} + +.subTitle { + margin-bottom: 2rem; + width: auto; + word-spacing: 2px; + text-align: left; +} + +.buttonStyle { + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + color: var(--vscode-button-foreground); + width: 3rem; + height: 2.5rem !important; + &:hover { + background-color: '#BE0000' !important; + } + + text-transform: none; +} + +.MuiButton-root.Mui-disabled { + -webkit-text-fill-color: white !important; +} + +.labelStyle { + text-align: center; + text-transform: none; + height: 2.5rem !important; + background-color: var(--vscode-button-background) !important; + border: 1px solid var(--vscode-settings-textInputForeground); +} + +.strategyContainer { + display: flex; + flex-direction: row; + border-top: 3px solid; + max-width: 55rem; + margin-top: 2rem; + padding-top: 0.5rem; + padding-left: 1rem; + min-height: 2rem; +} + +.cardContainer { + display: flex; + flex-direction: column; + margin-top: 1rem; +} + +.strategySuccess { + border-color: green; + background-color: darkseagreen !important; +} + +.strategyWarning { + border-color: orange; + background-color: burlywood !important; +} + +.MuiTypography-root { + font-family: var(--vscode-font-family) !important; +} + +.MuiFormHelperText-root { + color: var(--vscode-foreground) !important; + font-family: var(--vscode-font-family) !important; + margin-left: 0px !important; +} + +.Mui-error { + color: #EE0000 !important; +} + +.MuiSelect-select, +.MuiAutocomplete-inputRoot { + color: var(--vscode-dropdown-foreground) !important; +} + +.MuiMenu-paper { + background-color: var(--vscode-dropdown-background) !important; + color: var(--vscode-dropdown-foreground) !important; +} + +.MuiInputLabel-root, +.MuiFormLabel-root { + background-color: transparent; + color: var(--vscode-settings-textInputForeground) !important; +} + +.MuiAutocomplete-inputRoot { + padding: 0 !important; + padding-left: 5px !important; + height: 2.5rem !important; +} + +.MuiMenuItem-root { + background-color: var(--vscode-dropdown-background) !important; + color: var(--vscode-dropdown-foreground) !important; +} + +.MuiMenuItem-root:hover { + background-color: var(--vscode-button-hoverBackground) !important; +} + +.MuiFormControl-root { + border: none; + &:hover { + border: none; + } +} + +.MuiOutlinedInput-notchedOutline { + border-color: var(--vscode-settings-textInputForeground) !important; +} + +.MuiFormControl-fullWidth { + max-width: 100% !important; +} + +.MuiSvgIcon-root, +.MuiStepLabel-label { + color: var(--vscode-button-foreground) !important; +} + +.successicon { + color: #198754 !important; +} + +.erroricon { + color: #ff3333 !important; +} + +.updateIcon { + color: #0288d1 !important; +} + +.disabledicon { + color: var(--vscode-settings-textInputForeground) !important; +} + +.MuiCircularProgress-svg { + color: #EE0000 !important; +} + +.MuiOutlinedInput-root.Mui-disabled { + border: 0px solid var(--vscode-tab-unfocusedActiveBorder) !important; +} + +.Mui-disabled { + -webkit-text-fill-color: var(--vscode-button-foreground) !important; +} + +.Mui-active { + color: var(--vscode-button-hoverBackground) !important; +} + +.MuiAutocomplete-noOptions { + background-color: var(--vscode-settings-textInputBackground); + color: var(--vscode-settings-textInputForeground) !important; +} + +li:hover { + background-color: var(--vscode-button-hoverBackground) !important; +} + +.MuiDialog-paper { + border: 1px groove var(--vscode-activityBar-activeBorder) !important; + border-radius: 1rem !important; + margin: auto !important; + background-color: #101418 !important; + color: var(--vscode-settings-textInputForeground) !important +} + +.MuiTableCell-body { + color: var(--vscode-button-foreground) !important +} + +.MuiTablePagination-toolbar { + background-color: var(--vscode-button-secondaryHoverBackground) !important; + color: var(--vscode-settings-textInputForeground) !important +} diff --git a/src/webview/helm-manage-repository/app/home.tsx b/src/webview/helm-manage-repository/app/home.tsx new file mode 100644 index 000000000..56e4fee96 --- /dev/null +++ b/src/webview/helm-manage-repository/app/home.tsx @@ -0,0 +1,25 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import * as React from 'react'; +import { DefaultProps } from '../../common/propertyTypes'; +import './home.scss'; +import { ShowRepositories } from './showRepositories'; +import { VSCodeMessage } from './vsCodeMessage'; + +export class ManageRepository extends React.Component { + + constructor(props: DefaultProps | Readonly) { + super(props); + VSCodeMessage.postMessage({ + action: 'getRepositoryList' + }); + } + + render(): React.ReactNode { + return ( + + ) + } +} diff --git a/src/webview/helm-manage-repository/app/index.html b/src/webview/helm-manage-repository/app/index.html new file mode 100644 index 000000000..cb9b0ca63 --- /dev/null +++ b/src/webview/helm-manage-repository/app/index.html @@ -0,0 +1,52 @@ + + + + + + + Serverless Function + + + + +
+ + + + diff --git a/src/webview/helm-manage-repository/app/index.tsx b/src/webview/helm-manage-repository/app/index.tsx new file mode 100644 index 000000000..769c62ea9 --- /dev/null +++ b/src/webview/helm-manage-repository/app/index.tsx @@ -0,0 +1,12 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import * as ReactDOM from 'react-dom'; +import * as React from 'react'; +import { ManageRepository } from './home'; + +ReactDOM.render( + , + document.getElementById('root'), +); diff --git a/src/webview/helm-manage-repository/app/showRepositories.tsx b/src/webview/helm-manage-repository/app/showRepositories.tsx new file mode 100644 index 000000000..8b403e8d4 --- /dev/null +++ b/src/webview/helm-manage-repository/app/showRepositories.tsx @@ -0,0 +1,423 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import { Add, Cancel, Delete, Done, Edit, Sync } from '@mui/icons-material'; +import { Box, Button, CircularProgress, Container, Dialog, DialogActions, DialogContent, IconButton, Stack, styled, Table, TableBody, TableCell, tableCellClasses, TableContainer, TableHead, TablePagination, TableRow, TextField, Tooltip, Typography } from '@mui/material'; +import * as React from 'react'; +import { DefaultProps } from '../../common/propertyTypes'; +import { HelmRepo } from '../../../helm/helmChartType'; +import { AddRepository } from './addRepository'; +import { VSCodeMessage } from './vsCodeMessage'; + +export class ShowRepositories extends React.Component { + constructor(props: DefaultProps | Readonly) { + super(props); + this.state = { + repositories: [], + openAddDialog: false, + openEditDialog: false, + openDeleteDialog: false, + openedRepo: undefined, + newRepoName: { + name: '', + error: false, + helpText: '' + }, + newRepoURL: { + url: '', + error: false, + helpText: '' + }, + page: 0, + rowsPerPage: 10 + } + } + + componentDidMount(): void { + VSCodeMessage.onMessage((message) => { + if (message.data.action === 'getRepositoryList') { + this.setState({ + repositories: message.data.repositories + }); + } else if (message.data.action === 'validateNewName') { + this.setState({ + newRepoName: { + name: message.data.name, + error: message.data.error, + helpText: message.data.helpText + } + }) + } else if (message.data.action === 'validateNewURL') { + this.setState({ + newRepoURL: { + url: message.data.url, + error: message.data.error, + helpText: message.data.helpText + } + }) + } else if (message.data.action === 'addRepo') { + if (message.data.status) { + this.setState({ + openAddDialog: false + }); + VSCodeMessage.postMessage({ + action: 'getRepositoryList' + }); + } + } + }); + } + + updateRepo = (repoName: string): void => { + VSCodeMessage.postMessage({ + action: 'updateRepo', + data: repoName + }); + } + + handleDialog = (repo: HelmRepo, isEdit = true): void => { + if (!repo) { + this.setState({ + openAddDialog: !this.state.openAddDialog + }) + } else { + this.setState({ + openedRepo: repo, + newRepoName: { + name: repo.name, + error: false, + helpText: '' + }, + newRepoURL: { + url: repo.url, + error: false, + helpText: '' + }, + openDeleteDialog: isEdit ? false : !this.state.openDeleteDialog, + openEditDialog: isEdit ? !this.state.openEditDialog : false + }); + } + } + + delete = (repo: HelmRepo): void => { + this.CloseDialog(); + VSCodeMessage.postMessage({ + action: 'deleteRepo', + data: { + name: repo.name + } + }); + } + + CloseDialog = (): void => { + this.setState({ + openAddDialog: false, + openEditDialog: false, + openDeleteDialog: false, + openedRepo: undefined, + newRepoName: { + name: '', + error: false, + helpText: '' + }, + newRepoURL: { + url: '', + error: false, + helpText: '' + } + }); + } + + validateName = (value: string): void => { + VSCodeMessage.postMessage({ + action: 'validateNewName', + data: value + }); + } + + validateGitURL = (value: string): void => { + VSCodeMessage.postMessage({ + action: 'validateNewURL', + data: value + }) + } + + handleDisable = (): boolean => { + return this.state.newRepoName.name.length === 0 || // + this.state.newRepoURL.url.length === 0 || this.state.newRepoName.error || this.state.newRepoURL.error + } + + rename(oldRepo: HelmRepo, newName: string, newURL: string): void { + this.CloseDialog(); + VSCodeMessage.postMessage({ + action: 'renameRepo', + data: { + oldRepo, + newName, + newURL + } + }); + } + + StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: 'var(--vscode-button-background)', + color: 'var(--vscode-button-foreground)' + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14, + }, + })); + + StyledTableRow = styled(TableRow)(() => ({ + '&:nth-of-type(odd)': { + backgroundColor: 'var(--vscode-button-secondaryBackground)' + }, + + '&:nth-of-type(even)': { + backgroundColor: 'var(--vscode-button-secondaryHoverBackground)' + } + })); + + handleChangeRowsPerPage = (event: React.ChangeEvent) => { + this.setState({ + page: 0, + rowsPerPage: parseInt(event.target.value, 10) + }); + }; + + handleChangePage = (_event: unknown, newPage: number) => { + this.setState({ + page: newPage + }); + }; + + render(): React.ReactNode { + const { newRepoName, newRepoURL, openedRepo, openAddDialog, openDeleteDialog, openEditDialog, page, repositories, rowsPerPage } = this.state; + return ( +
+
+ Helm Repositories +
+
+ Manage helm repositories were added on disk at the default location (~/Library/helm/repositories.yaml) Once added. +
+ + + <> + { + repositories.length > 0 ? + <> + + + + + + Repository Name + Actions + + + + {repositories.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((repo: HelmRepo) => ( + + +
+ <> + { + repo.name.length > 24 ? + {repo.name} + : {repo.name} + } + +
+
+ + + + this.handleDialog(repo)} + > + + + + + this.handleDialog(repo, false)} + > + + + + + this.updateRepo(repo.name)} + > + + + + + +
+ ))} +
+
+
+ { + repositories.length > 10 && + + } + +
+ + + + + + + : + + } + +
+
+ { + openedRepo && + this.CloseDialog()}> + + + {`Are you sure want to delete the repository '${openedRepo.name}' ?`} + + + + + + + + } + { + openedRepo && + this.CloseDialog()}> + + + + + this.validateName(e.target.value)} + id='repo-new-name' + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + height: '7px !important', + } + }} + helperText={newRepoName.helpText} /> + + + + this.validateGitURL(e.target.value)} + id='repo-new-name' + sx={{ + input: { + color: 'var(--vscode-settings-textInputForeground)', + height: '7px !important', + } + }} + helperText={newRepoURL.helpText} /> + + + + + + + + + } + this.CloseDialog()}> + + + + +
+ ) + } +} diff --git a/src/webview/helm-manage-repository/app/tsconfig.json b/src/webview/helm-manage-repository/app/tsconfig.json new file mode 100644 index 000000000..9d75542c3 --- /dev/null +++ b/src/webview/helm-manage-repository/app/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "target": "es6", + "outDir": "configViewer", + "lib": [ + "es6", + "dom" + ], + "jsx": "react", + "sourceMap": true, + "noUnusedLocals": true, + // "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true, + "typeRoots": [ + "../../../../node_modules/@types", + "../../@types" + ], + "baseUrl": ".", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "exclude": [ + "node_modules" + ] +} diff --git a/src/webview/helm-manage-repository/app/vsCodeMessage.ts b/src/webview/helm-manage-repository/app/vsCodeMessage.ts new file mode 100644 index 000000000..645cb80c0 --- /dev/null +++ b/src/webview/helm-manage-repository/app/vsCodeMessage.ts @@ -0,0 +1,37 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +declare const acquireVsCodeApi: () => VSCodeApi; + +interface VSCodeApi { + getState: () => any; + setState: (newState: any) => any; + postMessage: (message: any) => void; +} + +class VSCodeWrapper { + private readonly vscodeApi: VSCodeApi = acquireVsCodeApi(); + + /** + * Send message to the extension framework. + * @param message + */ + public postMessage(message: any): void { + this.vscodeApi.postMessage(message); + } + + /** + * Add listener for messages from extension framework. + * @param callback called when the extension sends a message + * @returns function to clean up the message eventListener. + */ + public onMessage(callback: (message: any) => void): () => void { + window.addEventListener('message', callback); + return () => window.removeEventListener('message', callback); + } +} + +// Singleton to prevent multiple fetches of VsCodeAPI. +export const VSCodeMessage: VSCodeWrapper = new VSCodeWrapper(); diff --git a/src/webview/helm-manage-repository/manageRepositoryLoader.ts b/src/webview/helm-manage-repository/manageRepositoryLoader.ts new file mode 100644 index 000000000..2fe44923b --- /dev/null +++ b/src/webview/helm-manage-repository/manageRepositoryLoader.ts @@ -0,0 +1,179 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import { ChildProcess } from 'child_process'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { ManageRepository } from '../../helm/manageRepository'; +import { ExtensionID } from '../../util/constants'; +import { Progress } from '../../util/progress'; +import { loadWebviewHtml, Message, validateName, validateURL } from '../common-ext/utils'; +import { vsCommand } from '../../vscommand'; +import { OpenShiftExplorer } from '../../explorer'; +import { ascRepoName } from '../../helm/helm'; + +export default class ManageRepositoryViewLoader { + + static panel: vscode.WebviewPanel; + + public static processMap: Map = new Map(); + + private static get extensionPath(): string { + return vscode.extensions.getExtension(ExtensionID).extensionPath; + } + + /** + * Returns a webview panel with the "Add Service Binding" UI, + * or if there is an existing view for the given contextPath, focuses that view and returns null. + * + * @param contextPath the path to the component that's being binded to a service + * @param availableServices the list of all bindable services on the cluster + * @param listenerFactory the listener function to receive and process messages from the webview + * @return the webview as a promise + */ + static async loadView( + title: string + ): Promise { + if (ManageRepositoryViewLoader.panel) { + ManageRepositoryViewLoader.panel.reveal(); + return; + } + const localResourceRoot = vscode.Uri.file( + path.join(ManageRepositoryViewLoader.extensionPath, 'out', 'helmManageRepositoryViewer'), + ); + + const panel = vscode.window.createWebviewPanel('helmRepositoryView', title, vscode.ViewColumn.One, { + enableScripts: true, + localResourceRoots: [localResourceRoot], + retainContextWhenHidden: true, + }); + + const messageHandlerDisposable = panel.webview.onDidReceiveMessage( + ManageRepositoryViewLoader.messageHandler, + ); + + panel.onDidDispose(() => { + messageHandlerDisposable.dispose(); + ManageRepositoryViewLoader.panel = undefined; + }); + + panel.iconPath = vscode.Uri.file( + path.join(ManageRepositoryViewLoader.extensionPath, 'images/context/cluster-node.png'), + ); + + panel.webview.html = await loadWebviewHtml('helmManageRepositoryViewer', panel); + ManageRepositoryViewLoader.panel = panel; + + return panel; + } + + static async messageHandler(message: Message) { + const action = message.action; + switch (action) { + case 'validateURL': + case 'validateNewURL': { + const flag = validateURL(message); + if (!flag.error) { + const repoUrls = (await ManageRepository.getInstance().list()).map((repo) => repo.url); + if (repoUrls.includes(message.data)) { + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + url: message.data, + error: true, + helpText: `Repository URL ${message.data} already exists` + }); + break; + } + } + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + url: message.data, + error: flag.error, + helpText: flag.helpText + }); + break; + } + case 'validateName': + case 'validateNewName': { + const flag = validateName(message.data); + const repoNames = (await ManageRepository.getInstance().list()).map((repo) => repo.name); + if (repoNames.includes(message.data)) { + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + name: message.data, + error: true, + helpText: `Repository ${message.data} already exists` + }); + } else { + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + name: message.data, + error: !flag ? false : true, + helpText: !flag ? '' : flag + }); + } + break; + } + case 'addRepo': { + let addRepoStatus: boolean; + await Progress.execFunctionWithProgress(`Adding repository ${message.data.repoName}`, async () => { + addRepoStatus = await ManageRepository.getInstance().addRepo(message.data.repoName, message.data.repoURL); + }); + if (addRepoStatus) { + OpenShiftExplorer.getInstance().refresh(); + } + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + status: addRepoStatus + }); + break; + } + case 'getRepositoryList': { + const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action, + repositories + }); + break; + } + case 'updateRepo': { + await Progress.execFunctionWithProgress(`Updating the repository ${message.data} with latest`, async () => { + await ManageRepository.getInstance().updateRepo(message.data); + }); + break; + } + case 'deleteRepo': { + const status = await ManageRepository.getInstance().deleteRepo(message.data.name); + if (status) { + OpenShiftExplorer.getInstance().refresh(); + const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action: 'getRepositoryList', + repositories + }); + } + break; + } + case 'renameRepo': { + const renameRepoStatus = await ManageRepository.getInstance().editRepo(message.data.oldRepo, message.data.newName, message.data.newURL); + if (renameRepoStatus) { + OpenShiftExplorer.getInstance().refresh(); + const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action: 'getRepositoryList', + repositories + }); + } + break; + } + default: + break; + } + } + + @vsCommand('openshift.helm.manageRepository') + public static async openHelmChartInWebview(): Promise { + await ManageRepositoryViewLoader.loadView('Manage Helm Repositories'); + } +} diff --git a/test/integration/helm.test.ts b/test/integration/helm.test.ts index c582a1eab..a8134a84a 100644 --- a/test/integration/helm.test.ts +++ b/test/integration/helm.test.ts @@ -50,7 +50,7 @@ suite('helm integration', function () { }); test('installs OpenShift repo', async function () { - await Helm.addHelmRepo(); + await Helm.addHelmRepo('openshift','https://charts.openshift.io/'); const repoListOutput = ( await CliChannel.getInstance().executeTool(new CommandText('helm', 'repo list')) ).stdout; diff --git a/tsconfig.json b/tsconfig.json index 8c505f8b7..46ef9c25c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,7 @@ "src/webview/welcome/app", "src/webview/git-import/app", "src/webview/helm-chart/app", + "src/webview/helm-manage-repository/app", "src/webview/feedback/app", "src/webview/serverless-function/app", "src/webview/serverless-manage-repository/app", From f555756d7693701f1348ee1310aee4492d2eb7ce Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Fri, 24 Nov 2023 19:40:54 +0530 Subject: [PATCH 3/4] added context menu for add,edit and delete Signed-off-by: msivasubramaniaan --- package.json | 60 ++++- src/helm/helm.ts | 6 +- src/helm/helmCommands.ts | 2 +- src/helm/manageRepository.ts | 252 ++++++++++++++---- src/vscommand.ts | 2 +- src/webview/helm-chart/helmChartLoader.ts | 100 +++---- .../app/addRepository.tsx | 2 +- .../app/showRepositories.tsx | 14 +- .../manageRepositoryLoader.ts | 40 +-- test/integration/helm.test.ts | 6 +- 10 files changed, 330 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index cf87922d4..9f9952c76 100644 --- a/package.json +++ b/package.json @@ -370,7 +370,7 @@ }, { "command": "openshift.helm.install", - "title": "Open Helm Charts", + "title": "Install", "category": "OpenShift" }, { @@ -383,6 +383,26 @@ "title": "Open Helm Charts", "category": "OpenShift" }, + { + "command": "openshift.helm.add", + "title": "Add repository", + "category": "OpenShift" + }, + { + "command": "openshift.helm.edit", + "title": "Edit repository", + "category": "OpenShift" + }, + { + "command": "openshift.helm.delete", + "title": "Delete repository", + "category": "OpenShift" + }, + { + "command": "openshift.helm.sync", + "title": "Sync repository", + "category": "OpenShift" + }, { "command": "openshift.project.delete", "title": "Delete", @@ -1187,10 +1207,26 @@ "command": "openshift.helm.install", "when": "false" }, + { + "command": "openshift.helm.add", + "when": "false" + }, { "command": "openshift.helm.open", "when": "false" }, + { + "command": "openshift.helm.edit", + "when": "false" + }, + { + "command": "openshift.helm.delete", + "when": "false" + }, + { + "command": "openshift.helm.sync", + "when": "false" + }, { "command": "openshift.componentTypesView.registry.openInBrowser", "when": "false" @@ -1762,14 +1798,34 @@ "group": "inline" }, { - "command": "openshift.helm.manageRepository", + "command": "openshift.helm.add", "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", "group": "c1" }, + { + "command": "openshift.helm.manageRepository", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", + "group": "c2" + }, { "command": "openshift.helm.openView", "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repos", + "group": "c3" + }, + { + "command": "openshift.helm.edit", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", + "group": "c1" + }, + { + "command": "openshift.helm.delete", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", "group": "c2" + }, + { + "command": "openshift.helm.sync", + "when": "view == openshiftProjectExplorer && viewItem == openshift.helm.repo", + "group": "c3" } ] }, diff --git a/src/helm/helm.ts b/src/helm/helm.ts index 130406a69..ae0cfd4d7 100644 --- a/src/helm/helm.ts +++ b/src/helm/helm.ts @@ -50,14 +50,14 @@ export async function getHelmRepos(): Promise { } /** - * Updates the content of all the Helm repos. + * sync the repository to get latest * * @param repo name * * @returns the CLI output data from running the necessary command */ -export async function updateHelmRepo(renameRepo: string): Promise { - return await CliChannel.getInstance().executeTool(HelmCommands.updateHelmRepo(renameRepo), undefined, false); +export async function syncHelmRepo(repoName: string): Promise { + return await CliChannel.getInstance().executeTool(HelmCommands.syncHelmRepo(repoName), undefined, false); } /** diff --git a/src/helm/helmCommands.ts b/src/helm/helmCommands.ts index 94ba19e07..65b33092e 100644 --- a/src/helm/helmCommands.ts +++ b/src/helm/helmCommands.ts @@ -13,7 +13,7 @@ export function deleteRepo(repoName: string): CommandText { return new CommandText('helm', `repo remove ${repoName}`); } -export function updateHelmRepo(repoName: string): CommandText { +export function syncHelmRepo(repoName: string): CommandText { return new CommandText('helm', `repo update ${repoName}`); } diff --git a/src/helm/manageRepository.ts b/src/helm/manageRepository.ts index 12e27b5d1..8a372c7b2 100644 --- a/src/helm/manageRepository.ts +++ b/src/helm/manageRepository.ts @@ -7,6 +7,14 @@ import * as vscode from 'vscode'; import sendTelemetry from '../telemetry'; import * as Helm from '../../src/helm/helm'; import { HelmRepo } from './helmChartType'; +import { OpenShiftExplorer } from '../explorer'; +import { vsCommand } from '../vscommand'; +import { ascRepoName } from '../../src/helm/helm'; +import ManageRepositoryViewLoader from '../webview/helm-manage-repository/manageRepositoryLoader'; +import HelmChartLoader from '../webview/helm-chart/helmChartLoader'; +import { inputValue } from '../util/inputValue'; +import validator from 'validator'; +import { Progress } from '../util/progress'; export class ManageRepository { @@ -19,14 +27,185 @@ export class ManageRepository { return ManageRepository.instance; } - public async updateRepo(repoName: string): Promise { - await sendTelemetry('openshift.helm.manageRepo.update', { + /** + * sync the repository + * + * @param repository + */ + @vsCommand('openshift.helm.sync') + public async sync(repo: HelmRepo): Promise { + await Progress.execFunctionWithProgress(`pulling ${repo.name} repository with latest`, async () => { + const flag = await ManageRepository.getInstance().syncRepo(repo.name); + if (flag) { + await ManageRepository.getInstance().refresh(); + } + }); + } + + /** + * edit the repository + * + * @param repository + * @returns true if repo edited successfully + */ + @vsCommand('openshift.helm.add') + public async add(_repo = undefined, newName: string, newURL: string, isWebview = false): Promise { + return await vscode.commands.executeCommand('openshift.helm.edit', undefined, newName, newURL, true, isWebview); + } + + /** + * edit the repository + * + * @param repository + * @returns true if repo edited successfully + */ + @vsCommand('openshift.helm.edit') + public async edit(repo: HelmRepo, newName: string, newURL: string, isAdd = false, isWebview = false): Promise { + enum listOfStep { + enterRepositoryName, + enterRepositoryURL + }; + let step: listOfStep = listOfStep.enterRepositoryName; + let repoName: string = repo?.name; + let repoURL: string = repo?.url; + const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); + if (!isWebview) { + while (step !== undefined) { + switch (step) { + case listOfStep.enterRepositoryName: { + // ask for repository + repoName = await inputValue(repo ? 'Edit repository name' : 'Provide repository name', + repoName, + false, + (value: string) => { + const trimmedValue = value.trim(); + if (trimmedValue.length === 0) { + return 'Repository name cannot be empty'; + } else if (!validator.isLength(trimmedValue, { min: 2, max: 63 })) { + return 'Repository name should be between 2-63 characters' + } else if (!validator.matches(trimmedValue, '^[a-z]([-a-z0-9]*[a-z0-9])*$')) { + return 'Repository name can have only alphabet characters and numbers'; + } + if (repositories?.find((repository) => repository.name !== repoName && repository.name === value)) { + return `Repository name '${value}' is already used`; + } + }, + 'Repository Name' + ); + if (!repoName) return null; // Back or cancel + step = listOfStep.enterRepositoryURL; + break; + } + case listOfStep.enterRepositoryURL: { + repoURL = await inputValue(repo ? 'Edit repository URL' : 'Provide repository URL', + repoURL, + false, + (value: string) => { + try { + const trimmedValue = value.trim(); + if (!validator.isURL(trimmedValue)) { + return 'Entered URL is invalid'; + } + if (repositories?.find((registry) => registry.url !== repoURL && new URL(registry.url).hostname === new URL(value).hostname)) { + return `Repository with entered URL '${value}' already exists`; + } + } catch (Error) { + return 'Entered URL is invalid'; + } + }, + 'Repository URL' + ); + if (repoURL === null) return null; // Cancel + if (!repoURL) step = listOfStep.enterRepositoryName; // Back + else step = undefined; + break; + } + default: { + return; // Shouldn't happen. Exit + } + } + } + } else { + repoName = newName; + repoURL = newURL; + } + + let flag: boolean; + + if (isAdd) { + await Progress.execFunctionWithProgress(`Adding repository ${repoName}`, async () => { + flag = await ManageRepository.getInstance().addRepo(repoName, repoURL); + }); + } else { + flag = await ManageRepository.getInstance().editRepo(repo, repoName, repoURL, true); + } + + if (flag) { + await ManageRepository.getInstance().refresh(); + const message = isAdd ? 'added' : 'updated'; + void vscode.window.showInformationMessage(`Helm Repository ${repoName} ${message} successfully`); + } + return flag; + } + + /** + * delete the repository + * + * @param repository + * @returns true if repo deleted successfully + */ + @vsCommand('openshift.helm.delete') + public async delete(repo: HelmRepo, isWebview = false): Promise { + const yesNo = isWebview ? 'Yes' : await vscode.window.showInformationMessage( + `Are you sure want to delete ${repo.name} helm repository'?`, + 'Yes', + 'No', + ); + if (yesNo === 'Yes') { + const flag = await ManageRepository.getInstance().deleteRepo(repo); + if (flag) { + await ManageRepository.getInstance().refresh(); + void vscode.window.showInformationMessage(`Helm Repository ${repo.name} deleted successfully`); + } + } + } + + public async list(): Promise { + await sendTelemetry('openshift.helm.manageRepo.list'); + const result = await Helm.getHelmRepos(); + if (result.stderr || result.error) { + const error = result.stderr || result.error?.message; + await sendTelemetry('openshift.helm.manageRepo.list.error', { + error + }); + void vscode.window.showErrorMessage(error); + return []; + } + const helmRepos = JSON.parse(result.stdout) as HelmRepo[]; + await sendTelemetry('openshift.helm.manageRepo.list.success', { + helmRepos + }); + return helmRepos; + } + + private async refresh() { + OpenShiftExplorer.getInstance().refresh(); + const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); + void ManageRepositoryViewLoader.panel?.webview.postMessage({ + action: 'getRepositoryList', + repositories + }); + await HelmChartLoader.getHelmCharts(); + } + + private async syncRepo(repoName: string): Promise { + await sendTelemetry('openshift.helm.manageRepo.sync', { repoName }); - const result = await Helm.updateHelmRepo(repoName); + const result = await Helm.syncHelmRepo(repoName); if (result.stderr || result.error) { const error = result.stderr || result.error?.message; - await sendTelemetry('openshift.helm.manageRepo.update.error', { + await sendTelemetry('openshift.helm.manageRepo.sync.error', { error }); void vscode.window.showErrorMessage(error); @@ -36,11 +215,12 @@ export class ManageRepository { return true; } - public async deleteRepo(name: string): Promise { + private async deleteRepo(repo: HelmRepo): Promise { + const repoName = repo.name; await sendTelemetry('openshift.helm.manageRepo.delete', { - name + repoName }); - const result = await Helm.deleteHelmRepo(name); + const result = await Helm.deleteHelmRepo(repoName); if (result.stderr || result.error) { const error = result.stderr || result.error?.message; await sendTelemetry('openshift.helm.manageRepo.delete.error', { @@ -52,7 +232,7 @@ export class ManageRepository { return true; } - public async editRepo(oldRepo: HelmRepo, newName: string, newURL: string): Promise { + private async editRepo(oldRepo: HelmRepo, newName: string, newURL: string, isEdit = false): Promise { await sendTelemetry('openshift.helm.manageRepo.edit', { oldRepo, newName, @@ -60,34 +240,23 @@ export class ManageRepository { }); // delete old repo - let result = await Helm.deleteHelmRepo(oldRepo.name); - if (result.stderr || result.error) { - const error = result.stderr || result.error?.message; - await sendTelemetry('openshift.helm.manageRepo.delete.oldRepo.error', { - error - }); - void vscode.window.showErrorMessage(error); - return false; - } - - //add new repo - result = await Helm.addHelmRepo(newName, newURL); - if (result.stderr || result.error) { - const error = result.stderr || result.error?.message; - await sendTelemetry('openshift.helm.manageRepo.edit.newRepo.error', { - error + let result = await ManageRepository.getInstance().deleteRepo(oldRepo); + if (result) { + //add new repo + result = await ManageRepository.getInstance().addRepo(newName, newURL); + if (!result) { + await ManageRepository.getInstance().addRepo(oldRepo.name, oldRepo.url); + return false; + } + await sendTelemetry('openshift.helm.manageRepo.edit.success', { + message: `Repo ${newName} updated successfully` }); - await Helm.addHelmRepo(oldRepo.name, oldRepo.url); - void vscode.window.showErrorMessage(error); - return false; + return true; } - await sendTelemetry('openshift.helm.manageRepo.edit.success', { - message: `Repo ${oldRepo.name} edited successfully` - }); - return true; + return result; } - public async addRepo(name: string, url: string): Promise { + private async addRepo(name: string, url: string): Promise { await sendTelemetry('openshift.helm.manageRepo.add', { name, url }); @@ -104,25 +273,6 @@ export class ManageRepository { name, message: 'Repo added successfully' }); - void vscode.window.showInformationMessage(`Repository ${name} added successfully`); return true; } - - public async list(): Promise { - await sendTelemetry('openshift.helm.manageRepo.list'); - const result = await Helm.getHelmRepos(); - if (result.stderr || result.error) { - const error = result.stderr || result.error?.message; - await sendTelemetry('openshift.helm.manageRepo.list.error', { - error - }); - void vscode.window.showErrorMessage(error); - return []; - } - const helmRepos = JSON.parse(result.stdout) as HelmRepo[]; - await sendTelemetry('openshift.helm.manageRepo.list.success', { - helmRepos - }); - return helmRepos; - } } diff --git a/src/vscommand.ts b/src/vscommand.ts index 3a00c4ce5..99966d68d 100644 --- a/src/vscommand.ts +++ b/src/vscommand.ts @@ -34,7 +34,7 @@ export class VsCommandError extends Error { const vsCommands: VsCommand[] = []; function displayResult(result: string | undefined): void { - if (result && `${result}`) { + if (result && typeof result === 'string' && `${result}`) { void window.showInformationMessage(`${result}`); } } diff --git a/src/webview/helm-chart/helmChartLoader.ts b/src/webview/helm-chart/helmChartLoader.ts index f558f7b92..77445aed0 100644 --- a/src/webview/helm-chart/helmChartLoader.ts +++ b/src/webview/helm-chart/helmChartLoader.ts @@ -159,7 +159,7 @@ export default class HelmChartLoader { }); panel.webview.onDidReceiveMessage(helmChartMessageListener); } - await getHelmCharts(); + await HelmChartLoader.getHelmCharts(); return panel; } @@ -167,58 +167,58 @@ export default class HelmChartLoader { public static async openHelmChartInWebview(): Promise { await HelmChartLoader.loadView('Helm Charts'); } -} -async function getHelmCharts(): Promise { - if (helmCharts.length === 0) { - const cliData = await Helm.getHelmRepos(); - if (!cliData.error && !cliData.stderr) { - const helmRepos = JSON.parse(cliData.stdout) as HelmRepo[]; - void panel?.webview.postMessage( - { - action: 'getHelmRepos', - data: { - helmRepos + public static async getHelmCharts(): Promise { + //if (helmCharts.length === 0) { + const cliData = await Helm.getHelmRepos(); + if (!cliData.error && !cliData.stderr) { + const helmRepos = JSON.parse(cliData.stdout) as HelmRepo[]; + void panel?.webview.postMessage( + { + action: 'getHelmRepos', + data: { + helmRepos + } } - } - ); - helmRepos.forEach((helmRepo: HelmRepo) => { - let url = helmRepo.url; - url = url.endsWith('/') ? url : url.concat('/'); - url = url.concat('index.yaml'); - void fetchURL(helmRepo, url); - }); - } + ); + helmRepos.forEach((helmRepo: HelmRepo) => { + let url = helmRepo.url; + url = url.endsWith('/') ? url : url.concat('/'); + url = url.concat('index.yaml'); + void HelmChartLoader.fetchURL(helmRepo, url); + }); + } + //} } -} -async function fetchURL(repo: HelmRepo, url: string) { - const signupResponse = await fetch(url, { - method: 'GET' - }); - const yamlResponse = JSYAML.load(await signupResponse.text()) as any; - const entries = yamlResponse.entries; - Object.keys(entries).forEach((key) => { - const res: ChartResponse = { - repoName: '', - repoURL: '', - chartName: '', - chartVersions: [], - displayName: '' - }; - res.repoName = repo.name; - res.repoURL = repo.url; - res.chartName = key; - res.chartVersions = entries[key].reverse(); - res.displayName = res.chartVersions[0].annotations ? res.chartVersions[0].annotations['charts.openshift.io/name'] : res.chartVersions[0].name; - helmCharts.push(res); - }); - void panel?.webview.postMessage( - { - action: 'getHelmCharts', - data: { - helmCharts + static async fetchURL(repo: HelmRepo, url: string) { + const signupResponse = await fetch(url, { + method: 'GET' + }); + const yamlResponse = JSYAML.load(await signupResponse.text()) as any; + const entries = yamlResponse.entries; + Object.keys(entries).forEach((key) => { + const res: ChartResponse = { + repoName: '', + repoURL: '', + chartName: '', + chartVersions: [], + displayName: '' + }; + res.repoName = repo.name; + res.repoURL = repo.url; + res.chartName = key; + res.chartVersions = entries[key].reverse(); + res.displayName = res.chartVersions[0].annotations ? res.chartVersions[0].annotations['charts.openshift.io/name'] : res.chartVersions[0].name; + helmCharts.push(res); + }); + void panel?.webview.postMessage( + { + action: 'getHelmCharts', + data: { + helmCharts + } } - } - ); + ); + } } diff --git a/src/webview/helm-manage-repository/app/addRepository.tsx b/src/webview/helm-manage-repository/app/addRepository.tsx index 608de9469..0868904b6 100644 --- a/src/webview/helm-manage-repository/app/addRepository.tsx +++ b/src/webview/helm-manage-repository/app/addRepository.tsx @@ -6,8 +6,8 @@ import * as React from 'react'; import { Button, Stack, TextField } from '@mui/material'; import { DefaultProps } from '../../common/propertyTypes'; -import './home.scss'; import { VSCodeMessage } from './vsCodeMessage'; +import './home.scss'; export class AddRepository extends React.Component { + syncRepo = (repo: HelmRepo): void => { VSCodeMessage.postMessage({ - action: 'updateRepo', - data: repoName + action: 'syncRepo', + data: repo }); } @@ -124,7 +124,7 @@ export class ShowRepositories extends React.Component - - this.updateRepo(repo.name)} + + this.syncRepo(repo)} > diff --git a/src/webview/helm-manage-repository/manageRepositoryLoader.ts b/src/webview/helm-manage-repository/manageRepositoryLoader.ts index 2fe44923b..833193dbc 100644 --- a/src/webview/helm-manage-repository/manageRepositoryLoader.ts +++ b/src/webview/helm-manage-repository/manageRepositoryLoader.ts @@ -7,10 +7,8 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { ManageRepository } from '../../helm/manageRepository'; import { ExtensionID } from '../../util/constants'; -import { Progress } from '../../util/progress'; import { loadWebviewHtml, Message, validateName, validateURL } from '../common-ext/utils'; import { vsCommand } from '../../vscommand'; -import { OpenShiftExplorer } from '../../explorer'; import { ascRepoName } from '../../helm/helm'; export default class ManageRepositoryViewLoader { @@ -116,16 +114,10 @@ export default class ManageRepositoryViewLoader { break; } case 'addRepo': { - let addRepoStatus: boolean; - await Progress.execFunctionWithProgress(`Adding repository ${message.data.repoName}`, async () => { - addRepoStatus = await ManageRepository.getInstance().addRepo(message.data.repoName, message.data.repoURL); - }); - if (addRepoStatus) { - OpenShiftExplorer.getInstance().refresh(); - } + const status = await vscode.commands.executeCommand('openshift.helm.add', undefined, message.data.repoName, message.data.repoURL, true); void ManageRepositoryViewLoader.panel?.webview.postMessage({ action, - status: addRepoStatus + status }); break; } @@ -137,34 +129,16 @@ export default class ManageRepositoryViewLoader { }); break; } - case 'updateRepo': { - await Progress.execFunctionWithProgress(`Updating the repository ${message.data} with latest`, async () => { - await ManageRepository.getInstance().updateRepo(message.data); - }); + case 'syncRepo': { + await vscode.commands.executeCommand('openshift.helm.sync', message.data); break; } case 'deleteRepo': { - const status = await ManageRepository.getInstance().deleteRepo(message.data.name); - if (status) { - OpenShiftExplorer.getInstance().refresh(); - const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); - void ManageRepositoryViewLoader.panel?.webview.postMessage({ - action: 'getRepositoryList', - repositories - }); - } + await vscode.commands.executeCommand('openshift.helm.delete', message.data.repo, true); break; } - case 'renameRepo': { - const renameRepoStatus = await ManageRepository.getInstance().editRepo(message.data.oldRepo, message.data.newName, message.data.newURL); - if (renameRepoStatus) { - OpenShiftExplorer.getInstance().refresh(); - const repositories = (await ManageRepository.getInstance().list()).sort(ascRepoName); - void ManageRepositoryViewLoader.panel?.webview.postMessage({ - action: 'getRepositoryList', - repositories - }); - } + case 'editRepo': { + await vscode.commands.executeCommand('openshift.helm.edit', message.data.oldRepo, message.data.newName, message.data.newURL, false, true); break; } default: diff --git a/test/integration/helm.test.ts b/test/integration/helm.test.ts index a8134a84a..b9c2b05c4 100644 --- a/test/integration/helm.test.ts +++ b/test/integration/helm.test.ts @@ -4,8 +4,6 @@ *-----------------------------------------------------------------------------------------------*/ import { expect } from 'chai'; -import { CommandText } from '../../src/base/command'; -import { CliChannel } from '../../src/cli'; import * as Helm from '../../src/helm/helm'; import { Oc } from '../../src/oc/ocWrapper'; import { Odo } from '../../src/odo/odoWrapper'; @@ -51,9 +49,7 @@ suite('helm integration', function () { test('installs OpenShift repo', async function () { await Helm.addHelmRepo('openshift','https://charts.openshift.io/'); - const repoListOutput = ( - await CliChannel.getInstance().executeTool(new CommandText('helm', 'repo list')) - ).stdout; + const repoListOutput = (await Helm.getHelmRepos()).stdout; expect(repoListOutput).to.contain('openshift'); expect(repoListOutput).to.contain('https://charts.openshift.io/'); }); From 834b6cb6cf84d54ea7be258595f1f12a519d0076 Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Fri, 24 Nov 2023 20:11:25 +0530 Subject: [PATCH 4/4] rebase Signed-off-by: msivasubramaniaan --- src/webview/helm-chart/helmChartLoader.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webview/helm-chart/helmChartLoader.ts b/src/webview/helm-chart/helmChartLoader.ts index 1fe14d13f..e38fe22b8 100644 --- a/src/webview/helm-chart/helmChartLoader.ts +++ b/src/webview/helm-chart/helmChartLoader.ts @@ -169,6 +169,7 @@ export default class HelmChartLoader { } public static async getHelmCharts(): Promise { + helmCharts.length = 0; const cliData = await Helm.getHelmRepos(); if (!cliData.error && !cliData.stderr) { const helmRepos = JSON.parse(cliData.stdout) as HelmRepo[];