diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/META-INF/MANIFEST.MF b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/META-INF/MANIFEST.MF index eaf6d387a..897595a44 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/META-INF/MANIFEST.MF +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/META-INF/MANIFEST.MF @@ -26,7 +26,6 @@ Require-Bundle: org.junit, org.reactivestreams.reactive-streams;bundle-version="1.0.0", io.projectreactor.reactor-core;bundle-version="[3.3.1,3.3.2)", org.apache.commons.lang, - org.hamcrest;bundle-version="[1.1.0,1.2.0)", org.eclipse.lsp4e, org.eclipse.lsp4j, org.springframework.ide.eclipse.boot.dash;bundle-version="3.9.12", @@ -47,7 +46,8 @@ Require-Bundle: org.junit, org.springframework.ide.eclipse.buildship30, org.eclipse.ui.console, org.springframework.ide.eclipse.docker.client;bundle-version="3.9.14", - org.springsource.ide.eclipse.commons.boot.ls;bundle-version="4.8.1" + org.springsource.ide.eclipse.commons.boot.ls;bundle-version="4.8.1", + org.hamcrest.library;bundle-version="1.3.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.core.runtime, diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java index ebc6bcb07..2cc4398ce 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java @@ -26,6 +26,8 @@ import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.assert import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.assertNotContains; import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.createFile; +import static org.mockito.ArgumentMatchers.*; + import java.net.URI; import java.time.Duration; import java.util.ArrayList; @@ -51,6 +53,7 @@ import org.eclipse.swt.graphics.Color; import org.junit.After; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.mockito.Matchers; import org.mockito.Mockito; import org.springframework.ide.eclipse.beans.ui.live.model.LiveBeansModel; @@ -618,7 +621,6 @@ public class BootDashDockerTests { String containerId = con.getName(); assertEquals(1, listContainersWithId(containerId).size()); -// when(ui().confirmOperation(eq("Deleting Elements"), any())).thenAnswer(answer); DeleteElementsAction delete = actions().getDeleteAppsAction(); harness.selection.setElements(con); assertTrue(delete.isEnabled()); @@ -1121,8 +1123,15 @@ public class BootDashDockerTests { //if (ui().confirmOperation("Deleting Elements", modifiable.getDeletionConfirmationMessage(workitem.getValue()))) { -// confirm popup was disabled. -// when(ui().confirmOperation("Deleting Elements", "Delete webby ?")).thenReturn(true); + RunTargetType rtt = model.getRunTarget().getType(); + Mockito.when(ui().confirmWithToggle(eq(DeleteElementsAction.PREF_SKIP_CONFIRM_DELETE(rtt)), + // String title, + eq("Deleting Elements"), + // String message, + anyString(), + // String toggleMessage + anyString() + )).thenReturn(true); deleteAction.run(); ACondition.waitFor("Everything is deleted", 5_000, () -> { @@ -1136,6 +1145,7 @@ public class BootDashDockerTests { // client().listImages(ListImagesParam.allImages()).stream(). }); +// verifyNoMoreInteractions(ui()); } @Test @@ -1162,6 +1172,14 @@ public class BootDashDockerTests { assertTrue(delete.isEnabled()); assertTrue(delete.isVisible()); + Mockito.when(ui().confirmWithToggle(eq(DeleteElementsAction.PREF_SKIP_CONFIRM_DELETE(model.getRunTarget().getType())), + // String title, + eq("Deleting Elements"), + // String message, + anyString(), + // String toggleMessage + anyString() + )).thenReturn(true); delete.run(); ACondition.waitFor("Image and container deletion", 10_000, () -> { @@ -1244,7 +1262,7 @@ public class BootDashDockerTests { Model model = (Model) invocation.getArguments()[0]; //model.performOk(); //no clicking ok, so that's like when dialog is 'canceled'. return null; - }).when(ui()).selectDockerDaemonDialog(Matchers.any()); + }).when(ui()).selectDockerDaemonDialog(any()); createTarget.run(); createTarget.waitFor(Duration.ofMillis(2000)); diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/plugin.xml b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/plugin.xml index af594d2c0..920c98636 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/plugin.xml +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/plugin.xml @@ -240,6 +240,13 @@ id="org.springframework.ide.eclipse.boot.dash.ngrok.NGROKInstallPreferencePage" name="ngrok"> + + + extends Nameable { default MissingLiveInfoMessages getMissingLiveInfoMessages() { return MissingLiveInfoMessages.DEFAULT; } + + /** + * Must return true if the models of this type are {@link DeletionCapabableModel}s. + */ + default boolean supportsDeletion() { + return true; // true for most models, so we make this the default implementation + } } diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteBootDashModel.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteBootDashModel.java index bfb651036..f50c248ef 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteBootDashModel.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteBootDashModel.java @@ -13,6 +13,7 @@ package org.springframework.ide.eclipse.boot.dash.model.remote; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -23,6 +24,7 @@ import org.eclipse.jdt.core.IJavaProject; import org.springframework.ide.eclipse.boot.dash.api.App; import org.springframework.ide.eclipse.boot.dash.api.Deletable; import org.springframework.ide.eclipse.boot.dash.api.ProjectDeploymentTarget; +import org.springframework.ide.eclipse.boot.dash.api.Styleable; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.RemoteBootDashModel; import org.springframework.ide.eclipse.boot.dash.livexp.DisposingFactory; import org.springframework.ide.eclipse.boot.dash.model.BootDashElement; @@ -144,10 +146,41 @@ public class GenericRemoteBootDashModel extends RemoteBootDashMo } @Override - public String getDeletionConfirmationMessage(Collection value) { + public String getDeletionConfirmationMessage(Collection elements) { + + Set withChild = new LinkedHashSet<>(); + for (BootDashElement toDelete : elements) { + ImmutableSet children = toDelete.getChildren().getValues(); + for (BootDashElement child : children) { + if (!elements.contains(child)) { + //child is implicitly deleted. So we should warn/confirm + // See: https://www.pivotaltracker.com/story/show/173538290 + withChild.add(toDelete); + } + } + } + if (!withChild.isEmpty()) { + StringBuilder confirmMessage = new StringBuilder("These elements have children, which will be deleted implicitly:\n"); + boolean first = true; + for (BootDashElement bde : withChild) { + if (!first) { + confirmMessage.append(", "); + } + confirmMessage.append(niceName(bde)); + first = false; + } + return confirmMessage.toString(); + } return null; // no confirmation asked. } + private String niceName(BootDashElement bde) { + if (bde instanceof Styleable) { + return ((Styleable) bde).getStyledName(null).getString(); + } + return bde.getName(); + } + @Override public boolean canBeAdded(List sources) { if (getRunTarget() instanceof ProjectDeploymentTarget) { diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/runtargettypes/LocalRunTargetType.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/runtargettypes/LocalRunTargetType.java index 6028b7654..17e4173f6 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/runtargettypes/LocalRunTargetType.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/runtargettypes/LocalRunTargetType.java @@ -64,4 +64,12 @@ public class LocalRunTargetType extends AbstractRunTargetType { public ImageDescriptor getDisconnectedIcon() { return getIcon(); } + + @Override + public boolean supportsDeletion() { + //Not supported right now. + //It might be possible to support deletion in the future (deleting a local app could amount + // to deleting corresponding project from the workspace, for example). + return false; + } } \ No newline at end of file diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/prefs/BootDashPrefsPage.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/prefs/BootDashPrefsPage.java new file mode 100644 index 000000000..eb3602239 --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/prefs/BootDashPrefsPage.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2020 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.dash.prefs; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.springframework.ide.eclipse.boot.dash.BootDashActivator; +import org.springframework.ide.eclipse.boot.dash.api.RunTargetType; +import org.springframework.ide.eclipse.boot.dash.views.DeleteElementsAction; + +/** + * @author Kris De Volder + */ +public class BootDashPrefsPage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + public BootDashPrefsPage() { + super(GRID); + } + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(BootDashActivator.getDefault().getPreferenceStore()); + } + + @Override + protected void createFieldEditors() { + Composite parent = getFieldEditorParent(); + for (RunTargetType rtt : BootDashActivator.getDefault().getModel().getRunTargetTypes()) { + if (rtt.supportsDeletion()) { + addField(new BooleanFieldEditor(DeleteElementsAction.PREF_SKIP_CONFIRM_DELETE(rtt), "Skip Delete Element Confirmation ("+rtt.getName()+")", parent)); + } + } + } + +// @Override +// protected void adjustGridLayout() { +// // Do nothing. Page offers one column grid layout. Group controls layout fields appropriately. +// } + +// private void setTooltip(Composite parent, StringFieldEditor fe, String tooltip) { +// fe.getLabelControl(parent).setToolTipText(tooltip); +// fe.getTextControl(parent).setToolTipText(tooltip); +// } +// +// private void setTooltip(Composite parent, BooleanFieldEditor2 fe, String tooltip) { +// fe.getChangeControl(parent).setToolTipText(tooltip); +// } + +} diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashActions.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashActions.java index cbf71b5ce..2111ebe7e 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashActions.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashActions.java @@ -31,6 +31,7 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IAction; import org.springframework.ide.eclipse.boot.core.BootActivator; +import org.springframework.ide.eclipse.boot.core.BootPreferences; import org.springframework.ide.eclipse.boot.dash.BootDashActivator; import org.springframework.ide.eclipse.boot.dash.api.RunTargetType; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.deployment.DeployToRemoteTargetAction; @@ -50,6 +51,7 @@ import org.springframework.ide.eclipse.boot.dash.model.runtargettypes.LocalRunTa import org.springframework.ide.eclipse.boot.dash.model.runtargettypes.RemoteRunTarget; import org.springframework.ide.eclipse.boot.dash.model.runtargettypes.RemoteRunTargetType; import org.springframework.ide.eclipse.boot.dash.ngrok.NGROKInstallManager; +import org.springframework.ide.eclipse.boot.dash.prefs.BootDashPrefsPage; import org.springframework.ide.eclipse.boot.dash.views.AbstractBootDashAction.Location; import org.springframework.ide.eclipse.boot.dash.views.AbstractBootDashElementsAction.Params; import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression; @@ -84,7 +86,8 @@ public class BootDashActions { private ExposeAppAction exposeRunAppAction; private ExposeAppAction exposeDebugAppAction; - private OpenFilterPreferencesAction openFilterPreferencesAction; + private OpenPreferencesAction openFilterPreferencesAction; + private OpenPreferencesAction openBootDashPreferencesAction; private DuplicateConfigAction duplicateConfigAction; @@ -111,6 +114,7 @@ public class BootDashActions { private RestartDevtoolsClientAction restartDevtoolsClientAction; + public interface Factory { Collection create(BootDashActions actions, BootDashViewModel model, MultiSelection selection, LiveExpression section, SimpleDIContext context, LiveProcessCommandsExecutor liveProcessCmds); } @@ -314,7 +318,14 @@ public class BootDashActions { debugOnTargetActions = createDeployOnTargetActions(RunState.DEBUGGING); runOnTargetActions = createDeployOnTargetActions(RunState.RUNNING); - openFilterPreferencesAction = new OpenFilterPreferencesAction(context); + openFilterPreferencesAction = new OpenPreferencesAction(context, BootPreferences.BOOT_PREFERENCE_PAGE_ID, + "Boot Projects Filters Preferences...", + "Open Preferences for Spring Boot projects filters" + ); + openBootDashPreferencesAction = new OpenPreferencesAction(context, BootDashPrefsPage.class.getName(), + "Boot Dash UI Preferences...", + "Open Preferences for Boot Dash" + ); liveDataConnectionManagement = new LiveDataConnectionManagementActions(defaultActionParams()); enableRemoteDevtoolsAction = new EnableRemoteDevtoolsAction(defaultActionParams()); @@ -583,10 +594,14 @@ public class BootDashActions { return getDeployAndStartOnTargetActions(runOnTargetActions); } - public OpenFilterPreferencesAction getOpenFilterPreferencesAction() { + public OpenPreferencesAction getOpenFilterPreferencesAction() { return openFilterPreferencesAction; } + public IAction getOpenBootDashPreferencesAction() { + return this.openBootDashPreferencesAction; + } + private ImmutableList getDeployAndStartOnTargetActions( DisposingFactory actionFactory) { ArrayList targets = new ArrayList<>(model.getRunTargets().getValues()); diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashTreeView.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashTreeView.java index 2a87574a0..fa196c61b 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashTreeView.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/BootDashTreeView.java @@ -206,6 +206,7 @@ public class BootDashTreeView extends ViewPartWithSections implements ITabbedPro manager.add(new Separator()); manager.add(actions.getOpenFilterPreferencesAction()); + manager.add(actions.getOpenBootDashPreferencesAction()); // manager.add(refreshAction); // manager.add(new Separator()); diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/DeleteElementsAction.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/DeleteElementsAction.java index f427f581b..fd6e18cb1 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/DeleteElementsAction.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/DeleteElementsAction.java @@ -31,6 +31,10 @@ import com.google.common.collect.Multimap; public class DeleteElementsAction extends AbstractBootDashElementsAction { + public static String PREF_SKIP_CONFIRM_DELETE(RunTargetType rtt) { + return "boot.dash."+rtt.getName()+".delete.confirm.skip"; + } + private Class targetTypeClass; public DeleteElementsAction(BootDashActions actions, Class targetType, MultiSelection selection, SimpleDIContext context) { @@ -62,7 +66,8 @@ public class DeleteElementsAction extends AbstractBootD BootDashModel model = workitem.getKey(); final DeletionCapabableModel modifiable = (DeletionCapabableModel)model; //cast is safe. Only DeleteCapabableModel are added to sortingBins String confirmationMsg = modifiable.getDeletionConfirmationMessage(workitem.getValue()); - if (confirmationMsg==null || ui().confirmOperation("Deleting Elements", confirmationMsg)) { + RunTargetType runTargetType = model.getRunTarget().getType(); + if (confirmationMsg==null || ui().confirmWithToggle(PREF_SKIP_CONFIRM_DELETE(runTargetType), "Deleting Elements", confirmationMsg, "Skip this dialog next time")) { Job job = new Job("Deleting Elements from " + model.getRunTarget().getName()) { @Override protected IStatus run(IProgressMonitor monitor) { diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenFilterPreferencesAction.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenPreferencesAction.java similarity index 58% rename from eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenFilterPreferencesAction.java rename to eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenPreferencesAction.java index c0215c507..18bb483d6 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenFilterPreferencesAction.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/views/OpenPreferencesAction.java @@ -12,28 +12,27 @@ package org.springframework.ide.eclipse.boot.dash.views; import org.eclipse.jface.action.IAction; import org.eclipse.ui.dialogs.PreferencesUtil; -import org.springframework.ide.eclipse.boot.core.BootPreferences; import org.springframework.ide.eclipse.boot.dash.di.SimpleDIContext; -import org.springframework.ide.eclipse.boot.dash.model.UserInteractions; /** - * Action opens Preferences dialog at Spring -> Boot page to allow Spring Boot - * projects filtering + * Action opens Preferences dialog on some specific page. * * @author Alex Boyko - * */ -public class OpenFilterPreferencesAction extends AbstractBootDashAction { +public class OpenPreferencesAction extends AbstractBootDashAction { - protected OpenFilterPreferencesAction(SimpleDIContext context) { + private final String PREFERENCE_PAGE_ID; + + protected OpenPreferencesAction(SimpleDIContext context, String pageId, String text, String tooltip) { super(context, IAction.AS_PUSH_BUTTON); - setText("Boot Projects Filters Preferences..."); - setToolTipText("Open Preferences for Spring Boot projects filters"); + this.PREFERENCE_PAGE_ID = pageId; + setText(text); + setToolTipText(tooltip); } @Override public void run() { - PreferencesUtil.createPreferenceDialogOn(null, BootPreferences.BOOT_PREFERENCE_PAGE_ID, null, null).open(); + PreferencesUtil.createPreferenceDialogOn(null, PREFERENCE_PAGE_ID, null, null).open(); } } diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.test/META-INF/MANIFEST.MF b/eclipse-extensions/org.springframework.ide.eclipse.boot.test/META-INF/MANIFEST.MF index 4ed7e460e..5bc758b79 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.test/META-INF/MANIFEST.MF +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.test/META-INF/MANIFEST.MF @@ -28,9 +28,9 @@ Require-Bundle: org.eclipse.ui, org.glassfish.jersey.core.jersey-common;bundle-version="2.22.1", org.springframework.ide.eclipse.boot.validation, org.springframework.ide.eclipse.boot.launch, - org.hamcrest;bundle-version="[1.1.0,1.2.0)", org.apache.commons.lang3, - com.google.guava + com.google.guava, + org.hamcrest.library;bundle-version="1.3.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Bundle-Vendor: Pivotal Inc.