From 0f82cdb4d8bd28e023a0eb5fab61f9fb842b87b6 Mon Sep 17 00:00:00 2001 From: Andre Dietisheim Date: Tue, 9 Apr 2024 10:10:27 +0200 Subject: [PATCH] feat: add/remove/edit helm repo (#673) Signed-off-by: Andre Dietisheim --- build.gradle | 4 +- .../openshift/test/ui/annotations/UITest.java | 8 +- ...mportProjectFromVersionControlFixture.java | 2 + .../test/ui/common/ProjectTreeFixture.java | 2 + .../test/ui/common/WelcomeDialogFixture.java | 4 +- .../test/ui/views/GettingStartedView.java | 6 +- .../intellij/openshift/utils/helm/Charts.java | 2 +- .../openshift/utils/helm/HelmCliRepoTest.java | 2 +- .../actions/helm/AddHelmRepoAction.java | 78 +++++ .../actions/helm/OpenHelmChartsAction.java | 7 +- .../helm/RemoveRepositoriesAction.java | 121 ++++++++ .../intellij/openshift/ui/SwingUtils.java | 41 +-- .../openshift/ui/helm/AddHelmRepoDialog.java | 268 ++++++++++++++++++ .../openshift/ui/helm/ChartIcons.java | 2 +- .../openshift/ui/helm/ChartPanels.java | 4 +- .../openshift/ui/helm/ChartsDialog.java | 74 ++--- .../openshift/ui/helm/DetailsPanel.java | 3 +- .../intellij/openshift/utils/helm/Helm.java | 4 +- .../openshift/utils/helm/HelmCli.java | 40 ++- src/main/resources/META-INF/plugin.xml | 2 + 20 files changed, 563 insertions(+), 111 deletions(-) create mode 100644 src/main/java/org/jboss/tools/intellij/openshift/actions/helm/AddHelmRepoAction.java create mode 100644 src/main/java/org/jboss/tools/intellij/openshift/actions/helm/RemoveRepositoriesAction.java create mode 100644 src/main/java/org/jboss/tools/intellij/openshift/ui/helm/AddHelmRepoDialog.java diff --git a/build.gradle b/build.gradle index b92e7f57d..be9822d21 100644 --- a/build.gradle +++ b/build.gradle @@ -140,7 +140,7 @@ dependencies { 'io.fabric8:openshift-client:6.12.0', 'org.apache.commons:commons-compress:1.26.1', 'org.apache.commons:commons-exec:1.4.0', - 'com.redhat.devtools.intellij:intellij-common:1.9.5', + 'com.redhat.devtools.intellij:intellij-common:1.9.6-SNAPSHOT', 'io.jsonwebtoken:jjwt-impl:0.12.5', 'io.jsonwebtoken:jjwt-jackson:0.12.5', 'org.keycloak:keycloak-installed-adapter:24.0.2', @@ -150,7 +150,7 @@ dependencies { 'org.junit.platform:junit-platform-launcher:1.10.2', 'org.mockito:mockito-core:5.11.0', 'org.easytesting:fest-assert:1.4', - 'com.redhat.devtools.intellij:intellij-common:1.9.5:test', + 'com.redhat.devtools.intellij:intellij-common:1.9.6-SNAPSHOT:test', 'org.awaitility:awaitility:4.2.1', 'org.mock-server:mockserver-client-java:5.15.0', 'org.mock-server:mockserver-netty:5.15.0', diff --git a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/annotations/UITest.java b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/annotations/UITest.java index c73130532..a637a52d7 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/annotations/UITest.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/annotations/UITest.java @@ -10,12 +10,12 @@ ******************************************************************************/ package org.jboss.tools.intellij.openshift.test.ui.annotations; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; - -import org.junit.jupiter.api.Tag; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * @author Ondrej Dockal diff --git a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ImportProjectFromVersionControlFixture.java b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ImportProjectFromVersionControlFixture.java index 4c8f9c5b4..d97babb2b 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ImportProjectFromVersionControlFixture.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ImportProjectFromVersionControlFixture.java @@ -18,7 +18,9 @@ import com.intellij.remoterobot.fixtures.FixtureName; import com.intellij.remoterobot.fixtures.JTextFieldFixture; import org.jetbrains.annotations.NotNull; + import java.time.Duration; + import static com.intellij.remoterobot.search.locators.Locators.byXpath; /** diff --git a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ProjectTreeFixture.java b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ProjectTreeFixture.java index 8cc9c8ccc..c1b5cd6a6 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ProjectTreeFixture.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/ProjectTreeFixture.java @@ -17,7 +17,9 @@ import com.intellij.remoterobot.fixtures.FixtureName; import com.intellij.remoterobot.fixtures.JTreeFixture; import org.jetbrains.annotations.NotNull; + import java.time.Duration; + import static com.intellij.remoterobot.search.locators.Locators.byXpath; import static com.intellij.remoterobot.utils.RepeatUtilsKt.waitFor; diff --git a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/WelcomeDialogFixture.java b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/WelcomeDialogFixture.java index 5f905d3c9..93cff2b4e 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/WelcomeDialogFixture.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/common/WelcomeDialogFixture.java @@ -14,10 +14,12 @@ import com.intellij.remoterobot.data.RemoteComponent; import com.intellij.remoterobot.fixtures.ComponentFixture; import com.intellij.remoterobot.fixtures.ContainerFixture; -import org.jetbrains.annotations.NotNull; import com.intellij.remoterobot.fixtures.DefaultXpath; import com.intellij.remoterobot.fixtures.FixtureName; +import org.jetbrains.annotations.NotNull; + import java.time.Duration; + import static com.intellij.remoterobot.search.locators.Locators.byXpath; /** diff --git a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/views/GettingStartedView.java b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/views/GettingStartedView.java index c60ca3f58..fee9798f9 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/test/ui/views/GettingStartedView.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/test/ui/views/GettingStartedView.java @@ -12,7 +12,11 @@ import com.intellij.remoterobot.RemoteRobot; import com.intellij.remoterobot.data.RemoteComponent; -import com.intellij.remoterobot.fixtures.*; +import com.intellij.remoterobot.fixtures.ComponentFixture; +import com.intellij.remoterobot.fixtures.ContainerFixture; +import com.intellij.remoterobot.fixtures.DefaultXpath; +import com.intellij.remoterobot.fixtures.FixtureName; +import com.intellij.remoterobot.fixtures.JTreeFixture; import com.intellij.remoterobot.search.locators.Locator; import com.intellij.remoterobot.utils.Keyboard; import com.intellij.remoterobot.utils.WaitForConditionTimeoutException; diff --git a/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/Charts.java b/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/Charts.java index 5f6aa0297..9c030774c 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/Charts.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/Charts.java @@ -31,7 +31,7 @@ public static Chart get(String name, Helm helm) throws Exception { } public static void addRepository(Pair pair, Helm helm) throws IOException { - helm.addRepo(pair.first, pair.second); + helm.addRepo(pair.first, pair.second, null); } } diff --git a/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCliRepoTest.java b/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCliRepoTest.java index 7934c6d96..c622467e2 100644 --- a/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCliRepoTest.java +++ b/src/it/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCliRepoTest.java @@ -20,7 +20,7 @@ public void testListRepos_should_list_repo_that_was_added() throws IOException { // given openshift repo was added to helm repos String name = "openshift"; String url = "https://charts.openshift.io/"; - helm.addRepo(name, url); + helm.addRepo(name, url, null); // when List repositories = helm.listRepos(); // then diff --git a/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/AddHelmRepoAction.java b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/AddHelmRepoAction.java new file mode 100644 index 000000000..fe70f8bd2 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/AddHelmRepoAction.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package org.jboss.tools.intellij.openshift.actions.helm; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.redhat.devtools.intellij.common.utils.ApplicationUtils; +import com.redhat.devtools.intellij.common.utils.SwingUtils; +import java.awt.Point; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import org.jboss.tools.intellij.openshift.actions.HelmAction; +import org.jboss.tools.intellij.openshift.telemetry.TelemetryService; +import org.jboss.tools.intellij.openshift.tree.application.HelmRepositoriesNode; +import org.jboss.tools.intellij.openshift.ui.helm.AddHelmRepoDialog; +import org.jboss.tools.intellij.openshift.utils.helm.Helm; +import org.jboss.tools.intellij.openshift.utils.helm.HelmRepository; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class AddHelmRepoAction extends HelmAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(AddHelmRepoAction.class); + + @Override + public void actionPerformedOnSelectedObject(AnActionEvent anActionEvent, Object selected, @NotNull Helm helm) { + Project project = getEventProject(anActionEvent); + if (!(selected instanceof HelmRepositoriesNode repositoriesNode)) { + return; + } + openAddHelmRepoDialog(repositoriesNode, helm, project, SwingUtils.getMouseLocation(anActionEvent)); + } + + private void openAddHelmRepoDialog(HelmRepositoriesNode repositoriesNode, Helm helm, Project project, Point location) { + CompletableFuture.supplyAsync( + () -> listRepositories(helm), + ApplicationUtils.PLATFORM_EXECUTOR + ).thenAcceptAsync( + repositories -> { + AddHelmRepoDialog dialog = new AddHelmRepoDialog(repositories, repositoriesNode, helm, project, location); + sendTelemetryResults(TelemetryService.TelemetryResult.SUCCESS); + dialog.show(); + }, + ApplicationUtils.UI_EXECUTOR + ); + } + + private Collection listRepositories(Helm helm) { + try { + return helm.listRepos(); + } catch (IOException e) { + LOGGER.warn("Could not list helm repositories", e); + return Collections.emptyList(); + } + } + + @Override + public String getTelemetryActionName() { + return "helm-add repo"; + } + + @Override + public boolean isVisible(Object selected) { + return selected instanceof HelmRepositoriesNode; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/OpenHelmChartsAction.java b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/OpenHelmChartsAction.java index 97cb2f471..345eb4e16 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/OpenHelmChartsAction.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/OpenHelmChartsAction.java @@ -13,10 +13,10 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import org.jboss.tools.intellij.openshift.actions.HelmAction; +import org.jboss.tools.intellij.openshift.actions.NodeUtils; import org.jboss.tools.intellij.openshift.telemetry.TelemetryService; import org.jboss.tools.intellij.openshift.tree.application.ApplicationsRootNode; import org.jboss.tools.intellij.openshift.tree.application.NamespaceNode; -import org.jboss.tools.intellij.openshift.tree.application.ParentableNode; import org.jboss.tools.intellij.openshift.ui.helm.ChartsDialog; import org.jboss.tools.intellij.openshift.utils.helm.Helm; import org.jboss.tools.intellij.openshift.utils.odo.Odo; @@ -27,11 +27,10 @@ public class OpenHelmChartsAction extends HelmAction { @Override public void actionPerformedOnSelectedObject(AnActionEvent anActionEvent, Object selected, @NotNull Helm helm) { Project project = getEventProject(anActionEvent); - ParentableNode parentableNode = ((ParentableNode) selected); - if (parentableNode == null) { + ApplicationsRootNode rootNode = NodeUtils.getRoot(selected); + if (rootNode == null) { return; } - ApplicationsRootNode rootNode = parentableNode.getRoot(); Odo odo = rootNode.getOdo().getNow(null); if (odo == null) { return; diff --git a/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/RemoveRepositoriesAction.java b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/RemoveRepositoriesAction.java new file mode 100644 index 000000000..161dcd732 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/openshift/actions/helm/RemoveRepositoriesAction.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package org.jboss.tools.intellij.openshift.actions.helm; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.redhat.devtools.intellij.common.actions.StructureTreeAction; +import com.redhat.devtools.intellij.common.utils.UIHelper; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.swing.tree.TreePath; +import org.jboss.tools.intellij.openshift.actions.HelmAction; +import org.jboss.tools.intellij.openshift.telemetry.TelemetryService; +import org.jboss.tools.intellij.openshift.tree.application.ApplicationsRootNode; +import org.jboss.tools.intellij.openshift.tree.application.HelmRepositoryNode; +import org.jboss.tools.intellij.openshift.tree.application.ProcessingNode; +import org.jboss.tools.intellij.openshift.utils.helm.Helm; +import org.jetbrains.annotations.Nullable; + +import static org.jboss.tools.intellij.openshift.actions.ActionUtils.runWithProgress; +import static org.jboss.tools.intellij.openshift.actions.NodeUtils.clearProcessing; +import static org.jboss.tools.intellij.openshift.actions.NodeUtils.setProcessing; + +public class RemoveRepositoriesAction extends HelmAction { + + @Override + public void actionPerformed(AnActionEvent anActionEvent, TreePath[] path, Object[] selected) { + Helm helm = getHelm(anActionEvent); + if (helm == null) { + return; + } + Project project = getEventProject(anActionEvent); + List repositories = toHelmRepositoryNodes(selected); + if (repositories == null + || repositories.isEmpty()) { + return; + } + + if (cancelRemoval(repositories)) { + sendTelemetryResults(TelemetryService.TelemetryResult.ABORTED); + return; + } + + runWithProgress((ProgressIndicator progress) -> { + removeRepositories(repositories, helm); + }, + "Remove Helm Repositories...", + project); + } + + private void removeRepositories(List repositories, Helm helm) { + ProcessingNode[] processingNodes = repositories.toArray(new ProcessingNode[0]); + ApplicationsRootNode rootNode = repositories.get(0).getRoot(); + try { + setProcessing("removing...", rootNode, processingNodes); + helm.removeRepos( + repositories.stream() + .map(HelmRepositoryNode::getName) + .toArray(String[]::new)); + clearProcessing(rootNode, processingNodes); + sendTelemetryResults(TelemetryService.TelemetryResult.SUCCESS); + } catch (Exception e) { + clearProcessing(rootNode, processingNodes); + UIHelper.executeInUI(() -> Messages.showErrorDialog("Error: " + e.getLocalizedMessage(), "Remove Helm Repositories")); + sendTelemetryResults(TelemetryService.TelemetryResult.ERROR); + } + } + + @Nullable + private static List toHelmRepositoryNodes(Object[] selected) { + if (selected == null + || selected.length == 0) { + return null; + } + + return Arrays.stream(selected) + .map(StructureTreeAction::getElement) + .filter(HelmRepositoryNode.class::isInstance) + .map(HelmRepositoryNode.class::cast) + .collect(Collectors.toList()); + } + + private boolean cancelRemoval(List releases) { + String repositoriesNames = releases.stream() + .map(HelmRepositoryNode::getName) + .collect(Collectors.joining(", ")); + return Messages.NO == Messages.showYesNoDialog( + "Remove Repositories " + + repositoriesNames + + ".\n\nAre you sure?", + "Remove Repositories", + Messages.getQuestionIcon()); + } + + @Override + public String getTelemetryActionName() { + return "helm-remove repositories"; + } + + @Override + public boolean isVisible(Object[] selected) { + return Arrays.stream(selected).anyMatch(item -> { + Object node = getElement(item); + if (!(node instanceof HelmRepositoryNode)) { + return false; + } + return !((HelmRepositoryNode) node).isProcessing(); + }); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/SwingUtils.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/SwingUtils.java index 33cf1019b..c10c1b4ef 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/ui/SwingUtils.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/SwingUtils.java @@ -10,33 +10,15 @@ ******************************************************************************/ package org.jboss.tools.intellij.openshift.ui; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.wm.impl.IdeGlassPaneEx; import com.intellij.ui.JBColor; import com.intellij.ui.SizedIcon; -import com.intellij.ui.WindowMoveListener; -import com.intellij.ui.WindowResizeListener; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.scale.JBUIScale; import com.intellij.ui.table.JBTable; import com.intellij.util.containers.JBIterable; import com.intellij.util.ui.JBFont; -import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.AbstractButton; -import javax.swing.DefaultCellEditor; -import javax.swing.Icon; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JRootPane; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellEditor; -import javax.swing.text.JTextComponent; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; @@ -47,6 +29,16 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import javax.swing.AbstractButton; +import javax.swing.DefaultCellEditor; +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.text.JTextComponent; +import org.jetbrains.annotations.NotNull; public class SwingUtils { @@ -136,19 +128,6 @@ private static JBIterable focusableComponents(@NotNull Component comp .filter(c -> c instanceof JComboBox || c instanceof AbstractButton || c instanceof JTextComponent); } - public static void setGlassPaneResizable(JRootPane rootPane, Disposable disposable) { - WindowResizeListener resizeListener = new WindowResizeListener(rootPane, JBUI.insets(10), null); - IdeGlassPaneEx glassPane = (IdeGlassPaneEx) rootPane.getGlassPane(); - glassPane.addMousePreprocessor(resizeListener, disposable); - glassPane.addMouseMotionPreprocessor(resizeListener, disposable); - } - - public static void setMovable(JRootPane rootPane, JComponent... movableComponents) { - WindowMoveListener windowMoveListener = new WindowMoveListener(rootPane); - Stream.of(movableComponents).forEach( - component -> component.addMouseListener(windowMoveListener)); - } - public static Point locationOrMouseLocation(Point location) { if (location == null) { location = MouseInfo.getPointerInfo().getLocation(); diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/AddHelmRepoDialog.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/AddHelmRepoDialog.java new file mode 100644 index 000000000..1dc7f41cf --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/AddHelmRepoDialog.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package org.jboss.tools.intellij.openshift.ui.helm; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogPanel; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.ui.validation.DialogValidation; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBTextField; +import com.redhat.devtools.intellij.common.ui.UndecoratedDialog; +import java.awt.Point; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.JComponent; +import net.miginfocom.swing.MigLayout; +import org.jboss.tools.intellij.openshift.actions.NodeUtils; +import org.jboss.tools.intellij.openshift.actions.NotificationUtils; +import org.jboss.tools.intellij.openshift.tree.application.HelmRepositoriesNode; +import org.jboss.tools.intellij.openshift.utils.helm.Helm; +import org.jboss.tools.intellij.openshift.utils.helm.HelmRepository; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jboss.tools.intellij.openshift.ui.SwingUtils.setBold; + +public class AddHelmRepoDialog extends UndecoratedDialog { + + private static final Logger LOGGER = LoggerFactory.getLogger(AddHelmRepoDialog.class); + + private final Collection existingRepositories; + private final HelmRepositoriesNode repositoriesNode; + private final Helm helm; + private final Project project; + private final Point location; + private JBLabel title; + + private boolean nameManuallyModified = false; + private JBTextField nameText; + private JBTextField urlText; + private JBTextField flagsText; + + public AddHelmRepoDialog(Collection repositories, HelmRepositoriesNode repositoriesNode, Helm helm, Project project, Point location) { + super(project, null, false, IdeModalityType.MODELESS, true); + this.existingRepositories = repositories; + this.repositoriesNode = repositoriesNode; + this.helm = helm; + this.project = project; + this.location = location; + init(); + } + + @Override + protected void init() { + super.init(); + + setOKButtonText("Add"); + setGlassPaneResizable(); + setMovableUsing(title); + if (location != null) { + setLocation(location); + } + registerEscapeShortcut(e -> closeImmediately()); + IdeFocusManager.getInstance(project).requestFocus(urlText, true); + } + + @Override + protected @Nullable JComponent createCenterPanel() { + DialogPanel panel = new DialogPanel(new MigLayout( + "flowx, ins 4, gap 4, fillx, filly, hidemode 3", + "[left][300:pref][right]")); + Map> validators = new HashMap<>(); + panel.setValidationsOnInput(validators); + panel.setValidationsOnApply(validators); + + this.title = new JBLabel("Add repository"); + setBold(title); + panel.add(title, "spanx 2, gap 0 0 0 14"); + + JBLabel closeIcon = new JBLabel(); + closeIcon.setIcon(AllIcons.Windows.CloseSmall); + closeIcon.addMouseListener(onClose()); + panel.add(closeIcon, "aligny top, wrap"); + + JBLabel nameLabel = new JBLabel("Name:"); + panel.add(nameLabel); + this.nameText = new JBTextField(); + panel.add(nameText, "growx, spanx 2, wrap"); + validators.put(nameText, Collections.singletonList(validateName(nameText))); + nameText.addKeyListener(onKeyInName()); + + JBLabel urlLabel = new JBLabel("URL:"); + panel.add(urlLabel); + this.urlText = new JBTextField(); + panel.add(urlText, "growx, spanx 2, wrap"); + validators.put(urlText, Collections.singletonList(validateURL(urlText))); + urlText.addKeyListener(onKeyInUrl(nameText, urlText)); + + JBLabel flagsLabel = new JBLabel("Flags:"); + panel.add(flagsLabel); + this.flagsText = new JBTextField(); + panel.add(flagsText, "growx, spanx 2, wrap"); + flagsText.addKeyListener(onKeyInFlags()); + + initValidation(); + panel.validateAll(); + return panel; + } + + private KeyListener onKeyInUrl(JBTextField nameText, JBTextField urlTextField) { + return new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + String host = urlTextField.getText(); + if (!StringUtil.isEmptyOrSpaces(host) + && !nameManuallyModified) { + nameText.setText(getName(host)); + nameManuallyModified = false; + } + doOkOnEnterKey(e); + super.keyReleased(e); + } + }; + } + + private KeyListener onKeyInName() { + return new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + nameManuallyModified = true; + doOkOnEnterKey(e); + } + }; + } + + private KeyListener onKeyInFlags() { + return new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + doOkOnEnterKey(e); + } + }; + } + + private void doOkOnEnterKey(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + doOKAction(); + } + } + + @NotNull + private DialogValidation validateName(JBTextField textField) { + return () -> { + String name = textField.getText(); + if (StringUtil.isEmptyOrSpaces(name)) { + return new ValidationInfo("Name required", textField); + } else if (name.contains("/")) { + return new ValidationInfo("Name must not contain '/'", textField); + } else if (existingRepositories.stream().anyMatch( + repository -> repository.getName().equals(name))) { + return new ValidationInfo("Repository with this name already exists", textField); + } + return null; + }; + } + + @NotNull + private DialogValidation validateURL(JBTextField textField) { + return () -> { + String url = textField.getText(); + if (StringUtil.isEmptyOrSpaces(url)) { + return new ValidationInfo("Url required", textField); + } else { + String host = com.redhat.devtools.intellij.common.utils.UrlUtils.getHost(url); + if (host == null) { + return new ValidationInfo("Invalid URL", textField); + } + } + return null; + }; + } + + private String getName(String url) { + try { + URI uri = URI.create(url); + String host = uri.getHost(); + if (StringUtil.isEmptyOrSpaces(host)) { + return null; + } + String path = uri.getPath(); + if (StringUtil.isEmptyOrSpaces(path)) { + return host; + } else { + // "https://docs.wildfly.org/charts/wildfly-charts/ -> docs.wildfly.org-charts-wildfly-charts + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + String appendix = path + .replaceAll("/", "-"); + return host + appendix; + } + } catch (IllegalArgumentException e) { + return null; + } + + } + + private MouseAdapter onClose() { + return new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + close(0); + } + }; + } + + @Override + protected void doOKAction() { + addRepo(nameText.getText(), urlText.getText(), flagsText.getText(), helm); + super.doOKAction(); + } + + private void addRepo(String name, String url, String flags, Helm helm) { + if (StringUtil.isEmptyOrSpaces(url) + || StringUtil.isEmptyOrSpaces(name)) { + return; + } + + ProgressManager.getInstance().run(new Task.Backgroundable(project, "Adding helm repo " + name, true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + helm.addRepo(name, url, StringUtil.isEmptyOrSpaces(flags)? null : flags); + NodeUtils.fireModified(repositoriesNode); + } catch (IOException e) { + NotificationUtils.notifyError("Could not add helm repo " + name, e.getMessage()); + } + } + }); + } + +} diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartIcons.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartIcons.java index 43afa2bbb..0a7e38062 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartIcons.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartIcons.java @@ -13,7 +13,7 @@ import com.intellij.ui.IconManager; import org.jboss.tools.intellij.openshift.utils.helm.ChartRelease; -import javax.swing.Icon; +import javax.swing.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartPanels.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartPanels.java index e3a510eeb..a297cb568 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartPanels.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartPanels.java @@ -17,8 +17,8 @@ import org.jboss.tools.intellij.openshift.utils.helm.Helm; import org.jboss.tools.intellij.openshift.utils.odo.Odo; -import javax.swing.JComponent; -import java.awt.Component; +import javax.swing.*; +import java.awt.*; import java.util.stream.Stream; public class ChartPanels extends MultiPanel { diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartsDialog.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartsDialog.java index 69fa8ac33..7c28880b5 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartsDialog.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/ChartsDialog.java @@ -12,16 +12,10 @@ import com.intellij.find.SearchTextArea; import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.CommonShortcuts; -import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.OnePixelDivider; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.OnePixelSplitter; -import com.intellij.ui.PopupBorder; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBPanel; import com.intellij.ui.components.JBScrollPane; @@ -29,6 +23,22 @@ import com.intellij.ui.render.RenderingUtil; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.JBUI; +import com.redhat.devtools.intellij.common.ui.UndecoratedDialog; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableModel; import net.miginfocom.swing.MigLayout; import org.jboss.tools.intellij.openshift.tree.application.ApplicationsRootNode; import org.jboss.tools.intellij.openshift.ui.StatusIcon; @@ -41,31 +51,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.ImageIcon; -import javax.swing.JComponent; -import javax.swing.JRootPane; -import javax.swing.JTable; -import javax.swing.JTextArea; -import javax.swing.RootPaneContainer; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.DefaultTableModel; -import java.awt.Window; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - import static org.jboss.tools.intellij.openshift.ui.SwingUtils.EXECUTOR_BACKGROUND; import static org.jboss.tools.intellij.openshift.ui.SwingUtils.EXECUTOR_UI; import static org.jboss.tools.intellij.openshift.ui.SwingUtils.setBold; import static org.jboss.tools.intellij.openshift.ui.helm.ChartVersions.toChartVersions; -public class ChartsDialog extends DialogWrapper { +public class ChartsDialog extends UndecoratedDialog { private static final Logger LOGGER = LoggerFactory.getLogger(ChartsDialog.class); @@ -93,32 +84,15 @@ public ChartsDialog(ApplicationsRootNode rootNode, Helm helm, Odo odo, Project p protected void init() { super.init(); setUndecorated(true); - Window dialogWindow = getPeer().getWindow(); - JRootPane rootPane = ((RootPaneContainer) dialogWindow).getRootPane(); - registerShortcuts(rootPane); - setBorders(rootPane); - SwingUtils.setGlassPaneResizable(getPeer().getRootPane(), getDisposable()); - SwingUtils.setMovable(getRootPane(), title, statusIcon.get()); + registerEscapeShortcut(e -> closeImmediately()); + setGlassPaneResizable(); + setMovableUsing(title, statusIcon.get()); setupTable(chartsTable, chartsTableModel, statusIcon) .thenCompose((Void) -> addDefaultRepo(helm)) .thenCompose((Void) -> load(chartsTable, chartsTableModel, statusIcon, helm)); } - private static void setBorders(JRootPane rootPane) { - rootPane.setBorder(PopupBorder.Factory.create(true, true)); - rootPane.setWindowDecorationStyle(JRootPane.NONE); - } - - private void registerShortcuts(JRootPane rootPane) { - AnAction escape = ActionManager.getInstance().getAction("EditorEscape"); - DumbAwareAction.create(e -> closeImmediately()) - .registerCustomShortcutSet( - escape == null ? CommonShortcuts.ESCAPE : escape.getShortcutSet(), - rootPane, - myDisposable); - } - @Override protected @Nullable JComponent createCenterPanel() { JBPanel panel = new JBPanel<>(new MigLayout( @@ -213,7 +187,7 @@ private CompletableFuture addDefaultRepo(Helm helm) { return CompletableFuture .runAsync(() -> { try { - helm.addRepo(OPENSHIFT_REPO_NAME, OPENSHIFT_REPO_URL); + helm.addRepo(OPENSHIFT_REPO_NAME, OPENSHIFT_REPO_URL, null); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } @@ -242,12 +216,6 @@ private CompletableFuture load( }, EXECUTOR_UI); } - private void closeImmediately() { - if (isVisible()) { - doCancelAction(); - } - } - private static Border createSearchTextBorders() { return JBUI.Borders.compound( JBUI.Borders.customLine(JBUI.CurrentTheme.BigPopup.searchFieldBorderColor(), 1, 0, 1, 0), diff --git a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/DetailsPanel.java b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/DetailsPanel.java index be04e8f46..e5950a199 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/DetailsPanel.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/ui/helm/DetailsPanel.java @@ -18,8 +18,7 @@ import net.miginfocom.swing.MigLayout; import org.jboss.tools.intellij.openshift.ui.SwingUtils; -import javax.swing.JButton; -import javax.swing.JLabel; +import javax.swing.*; import java.awt.event.ActionEvent; class DetailsPanel extends JBPanel implements ChartPanel, Disposable { diff --git a/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/Helm.java b/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/Helm.java index d6950dc8a..89ad462c8 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/Helm.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/Helm.java @@ -15,7 +15,9 @@ public interface Helm { - String addRepo(String name, String url) throws IOException; + String addRepo(String name, String url, String flags) throws IOException; + + void removeRepos(String... name) throws IOException; List listRepos() throws IOException; diff --git a/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCli.java b/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCli.java index 04c6b0a5b..3dabb6017 100644 --- a/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCli.java +++ b/src/main/java/org/jboss/tools/intellij/openshift/utils/helm/HelmCli.java @@ -13,11 +13,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.intellij.openapi.util.text.StringUtil; import com.redhat.devtools.intellij.common.utils.ExecHelper; -import org.jboss.tools.intellij.openshift.telemetry.TelemetryService; -import org.jboss.tools.intellij.openshift.utils.Serialization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -25,8 +20,13 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jboss.tools.intellij.openshift.telemetry.TelemetryService; +import org.jboss.tools.intellij.openshift.utils.Serialization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage; import static org.jboss.tools.intellij.openshift.Constants.HOME_FOLDER; @@ -43,12 +43,12 @@ public HelmCli(String command) { } @Override - public String addRepo(String name, String url) throws IOException { + public String addRepo(String name, String url, String flags) throws IOException { ActionMessage telemetry = TelemetryService.instance().getBuilder().action( TelemetryService.NAME_PREFIX_MISC + "helm-add repo"); try { LOGGER.info("Adding repo {} at {}.", name, url); - String result = execute(command, Collections.emptyMap(), "repo", "add", name, url); + String result = execute(command, Collections.emptyMap(), "repo", "add", name, url, flags); asyncSend(telemetry.success()); return result; } catch (IOException e) { @@ -58,6 +58,32 @@ public String addRepo(String name, String url) throws IOException { } @Override + public void removeRepos(String... names) throws IOException { + LOGGER.info("Removing repositories {}.", String.join(", ", names)); + List notRemoved = Arrays.stream(names) + .map(this::removeRepo) + .filter(Objects::nonNull) + .toList(); + if (!notRemoved.isEmpty()) { + throw new IOException("Could not remove repositories " + String.join(", ", notRemoved)); + } + } + + private String removeRepo(String name) { + ActionMessage telemetry = TelemetryService.instance().getBuilder().action( + TelemetryService.NAME_PREFIX_MISC + "helm-remove repository"); + try { + execute(command, Collections.emptyMap(), "repo", "remove", name); + asyncSend(telemetry.success()); + return null; + } catch (IOException e) { + LOGGER.info("Could not remove repository " + name, e); + asyncSend(telemetry.error(e.getMessage())); + return name; + } + } + + @Override public List listRepos() throws IOException { ActionMessage telemetry = TelemetryService.instance().getBuilder().action( TelemetryService.NAME_PREFIX_MISC + "helm-list repo"); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index bd9b4561a..0c1d3af16 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -334,6 +334,8 @@ + +