Deletion confirmation for CF / Docker elements

Both now work in similar way and can be 'skipped' by setting
a oreference or a 'do not show again' checkbox in the dialog.
This commit is contained in:
Kris De Volder
2020-10-13 15:39:44 -07:00
parent 430da643a9
commit e0bba737d3
12 changed files with 177 additions and 23 deletions

View File

@@ -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,

View File

@@ -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));

View File

@@ -240,6 +240,13 @@
id="org.springframework.ide.eclipse.boot.dash.ngrok.NGROKInstallPreferencePage"
name="ngrok">
</page>
<page
category="org.springframework.ide.eclipse.boot.ui.preferences.BootPreferencePage"
class="org.springframework.ide.eclipse.boot.dash.prefs.BootDashPrefsPage"
id="org.springframework.ide.eclipse.boot.dash.prefs.BootDashPrefsPage"
name="Boot Dash">
</page>
</extension>
<extension

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.eclipse.boot.dash.api;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jface.resource.ImageDescriptor;
import org.springframework.ide.eclipse.boot.dash.model.DeletionCapabableModel;
import org.springframework.ide.eclipse.boot.dash.model.MissingLiveInfoMessages;
import org.springframework.ide.eclipse.boot.dash.model.Nameable;
import org.springframework.ide.eclipse.boot.dash.model.RunTarget;
@@ -96,4 +97,11 @@ public interface RunTargetType<Params> 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
}
}

View File

@@ -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<Client, Params> extends RemoteBootDashMo
}
@Override
public String getDeletionConfirmationMessage(Collection<BootDashElement> value) {
public String getDeletionConfirmationMessage(Collection<BootDashElement> elements) {
Set<BootDashElement> withChild = new LinkedHashSet<>();
for (BootDashElement toDelete : elements) {
ImmutableSet<BootDashElement> 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<Object> sources) {
if (getRunTarget() instanceof ProjectDeploymentTarget) {

View File

@@ -64,4 +64,12 @@ public class LocalRunTargetType extends AbstractRunTargetType<Void> {
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;
}
}

View File

@@ -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);
// }
}

View File

@@ -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<AbstractBootDashAction> create(BootDashActions actions, BootDashViewModel model, MultiSelection<BootDashElement> selection, LiveExpression<BootDashModel> 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<IAction> getDeployAndStartOnTargetActions(
DisposingFactory<RunTarget, AbstractBootDashAction> actionFactory) {
ArrayList<RunTarget> targets = new ArrayList<>(model.getRunTargets().getValues());

View File

@@ -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());

View File

@@ -31,6 +31,10 @@ import com.google.common.collect.Multimap;
public class DeleteElementsAction<T extends RunTargetType> extends AbstractBootDashElementsAction {
public static String PREF_SKIP_CONFIRM_DELETE(RunTargetType rtt) {
return "boot.dash."+rtt.getName()+".delete.confirm.skip";
}
private Class<T> targetTypeClass;
public DeleteElementsAction(BootDashActions actions, Class<T> targetType, MultiSelection<BootDashElement> selection, SimpleDIContext context) {
@@ -62,7 +66,8 @@ public class DeleteElementsAction<T extends RunTargetType> 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) {

View File

@@ -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();
}
}

View File

@@ -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.