From ac4eca7520a4f7db90149159513cd9d022ffeaf1 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 26 Sep 2019 16:51:26 +0200 Subject: [PATCH 01/25] Fetch exception when uninstall file could not be written --- .../sonia/scm/plugin/DefaultPluginManager.java | 9 +++++++-- .../scm/plugin/PluginDependencyTracker.java | 6 +++++- .../scm/plugin/DefaultPluginManagerTest.java | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index d54645f10d..1c636ffb17 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -193,9 +193,14 @@ public class DefaultPluginManager implements PluginManager { doThrow().violation("plugin is a core plugin and cannot be uninstalled").when(installed.isCore()); dependencyTracker.removeInstalled(installed.getDescriptor()); - installed.setMarkedForUninstall(true); - createMarkerFile(installed, InstalledPlugin.UNINSTALL_MARKER_FILENAME); + try { + createMarkerFile(installed, InstalledPlugin.UNINSTALL_MARKER_FILENAME); + installed.setMarkedForUninstall(true); + } catch (RuntimeException e) { + dependencyTracker.addInstalled(installed.getDescriptor()); + throw e; + } if (restartAfterInstallation) { restart("plugin installation"); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginDependencyTracker.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginDependencyTracker.java index a68b391b97..2609522555 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginDependencyTracker.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginDependencyTracker.java @@ -33,6 +33,10 @@ class PluginDependencyTracker { } private void removeDependency(String from, String to) { - plugins.get(to).remove(from); + Collection dependencies = plugins.get(to); + if (dependencies == null) { + throw new NullPointerException("inverse dependencies not found for " + to); + } + dependencies.remove(from); } } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index 057a05eb79..ad196b8ca3 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -358,6 +358,24 @@ class DefaultPluginManagerTest { verify(mailPlugin).setMarkedForUninstall(true); } + @Test + void shouldNotChangeStateWhenUninstallFileCouldNotBeCreated() { + InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin"); + InstalledPlugin reviewPlugin = createInstalled("scm-review-plugin"); + when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin")); + + when(reviewPlugin.getDirectory()).thenThrow(new PluginException("when the file could not be written an exception like this is thrown")); + + when(loader.getInstalledPlugins()).thenReturn(asList(mailPlugin, reviewPlugin)); + + manager.computeInstallationDependencies(); + + assertThrows(PluginException.class, () -> manager.uninstall("scm-review-plugin", false)); + + verify(mailPlugin, never()).setMarkedForUninstall(true); + assertThrows(ScmConstraintViolationException.class, () -> manager.uninstall("scm-mail-plugin", false)); + } + @Test void shouldThrowExceptionWhenUninstallingCorePlugin(@TempDirectory.TempDir Path temp) { InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin"); From 3145b751c690ffadce2242c99ea5d3714502dec4 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 26 Sep 2019 17:50:54 +0200 Subject: [PATCH 02/25] Add cancel method to remove install and uninstall files --- .../java/sonia/scm/plugin/PluginManager.java | 2 + .../scm/plugin/DefaultPluginManager.java | 38 ++++++++++--------- .../plugin/PendingPluginUninstallation.java | 31 +++++++++++++++ .../scm/plugin/DefaultPluginManagerTest.java | 24 ++++++++++++ 4 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 3d0ea94536..1e787d24c3 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -93,4 +93,6 @@ public interface PluginManager { * Install all pending plugins and restart the scm context. */ void executePendingAndRestart(); + + void cancelInstallations(); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 1c636ffb17..c03cfdab84 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -44,8 +44,8 @@ import sonia.scm.lifecycle.RestartEvent; import sonia.scm.version.Version; import javax.inject.Inject; -import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -72,7 +72,8 @@ public class DefaultPluginManager implements PluginManager { private final PluginLoader loader; private final PluginCenter center; private final PluginInstaller installer; - private final Collection pendingQueue = new ArrayList<>(); + private final Collection pendingInstallQueue = new ArrayList<>(); + private final Collection pendingUninstallQueue = new ArrayList<>(); private final PluginDependencyTracker dependencyTracker = new PluginDependencyTracker(); @Inject @@ -106,7 +107,7 @@ public class DefaultPluginManager implements PluginManager { } private Optional getPending(String name) { - return pendingQueue + return pendingInstallQueue .stream() .map(PendingPluginInstallation::getPlugin) .filter(filterByName(name)) @@ -179,7 +180,7 @@ public class DefaultPluginManager implements PluginManager { if (restartAfterInstallation) { restart("plugin installation"); } else { - pendingQueue.addAll(pendingInstallations); + pendingInstallQueue.addAll(pendingInstallations); updateMayUninstallFlag(); } } @@ -192,15 +193,8 @@ public class DefaultPluginManager implements PluginManager { .orElseThrow(() -> NotFoundException.notFound(entity(InstalledPlugin.class, name))); doThrow().violation("plugin is a core plugin and cannot be uninstalled").when(installed.isCore()); - dependencyTracker.removeInstalled(installed.getDescriptor()); - try { - createMarkerFile(installed, InstalledPlugin.UNINSTALL_MARKER_FILENAME); - installed.setMarkedForUninstall(true); - } catch (RuntimeException e) { - dependencyTracker.addInstalled(installed.getDescriptor()); - throw e; - } + markForUninstall(installed); if (restartAfterInstallation) { restart("plugin installation"); @@ -220,18 +214,22 @@ public class DefaultPluginManager implements PluginManager { && dependencyTracker.mayUninstall(p.getDescriptor().getInformation().getName()); } - private void createMarkerFile(InstalledPlugin plugin, String markerFile) { + private void markForUninstall(InstalledPlugin plugin) { + dependencyTracker.removeInstalled(plugin.getDescriptor()); try { - Files.createFile(plugin.getDirectory().resolve(markerFile)); - } catch (IOException e) { - throw new PluginException("could not mark plugin " + plugin.getId() + " in path " + plugin.getDirectory() + "as " + markerFile, e); + Path file = Files.createFile(plugin.getDirectory().resolve(InstalledPlugin.UNINSTALL_MARKER_FILENAME)); + pendingUninstallQueue.add(new PendingPluginUninstallation(plugin, file)); + plugin.setMarkedForUninstall(true); + } catch (Exception e) { + dependencyTracker.addInstalled(plugin.getDescriptor()); + throw new PluginException("could not mark plugin " + plugin.getId() + " in path " + plugin.getDirectory() + "as " + InstalledPlugin.UNINSTALL_MARKER_FILENAME, e); } } @Override public void executePendingAndRestart() { PluginPermissions.manage().check(); - if (!pendingQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) { + if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) { restart("execute pending plugin changes"); } } @@ -274,4 +272,10 @@ public class DefaultPluginManager implements PluginManager { private boolean isUpdatable(String name) { return getAvailable(name).isPresent() && !getPending(name).isPresent(); } + + @Override + public void cancelInstallations() { + pendingUninstallQueue.forEach(PendingPluginUninstallation::cancel); + pendingInstallQueue.forEach(PendingPluginInstallation::cancel); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java new file mode 100644 index 0000000000..e5481fb292 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java @@ -0,0 +1,31 @@ +package sonia.scm.plugin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +class PendingPluginUninstallation { + + private static final Logger LOG = LoggerFactory.getLogger(PendingPluginUninstallation.class); + + private final InstalledPlugin plugin; + private final Path uninstallFile; + + PendingPluginUninstallation(InstalledPlugin plugin, Path uninstallFile) { + this.plugin = plugin; + this.uninstallFile = uninstallFile; + } + + void cancel() { + String name = plugin.getDescriptor().getInformation().getName(); + LOG.info("cancel uninstallation of plugin {}", name); + try { + Files.delete(uninstallFile); + } catch (IOException ex) { + throw new PluginFailedToCancelInstallationException("failed to cancel uninstallation of plugin " + name, ex); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index ad196b8ca3..c761034ac1 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -20,6 +20,8 @@ import sonia.scm.ScmConstraintViolationException; import sonia.scm.event.ScmEventBus; import sonia.scm.lifecycle.RestartEvent; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -445,6 +447,28 @@ class DefaultPluginManagerTest { verify(eventBus).post(any(RestartEvent.class)); } + + @Test + void shouldUndoPendingInstallations(@TempDirectory.TempDir Path temp) throws IOException { + InstalledPlugin mailPlugin = createInstalled("scm-ssh-plugin"); + Path mailPluginPath = temp.resolve("scm-mail-plugin"); + Files.createDirectories(mailPluginPath); + when(mailPlugin.getDirectory()).thenReturn(mailPluginPath); + when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin)); + + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); + PendingPluginInstallation gitPendingPluginInformation = mock(PendingPluginInstallation.class); + when(installer.install(git)).thenReturn(gitPendingPluginInformation); + + manager.install("scm-git-plugin", false); + manager.uninstall("scm-ssh-plugin", false); + + manager.cancelInstallations(); + + assertThat(mailPluginPath.resolve("uninstall")).doesNotExist(); + verify(gitPendingPluginInformation).cancel(); + } } @Nested From 2519c415bf7b2ad7aa2ba273990e7500379d2156 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 27 Sep 2019 11:40:06 +0200 Subject: [PATCH 03/25] Set uninstalled marker on cancel --- .../java/sonia/scm/plugin/PendingPluginUninstallation.java | 1 + .../test/java/sonia/scm/plugin/DefaultPluginManagerTest.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java index e5481fb292..8d5a44d60a 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginUninstallation.java @@ -24,6 +24,7 @@ class PendingPluginUninstallation { LOG.info("cancel uninstallation of plugin {}", name); try { Files.delete(uninstallFile); + plugin.setMarkedForUninstall(false); } catch (IOException ex) { throw new PluginFailedToCancelInstallationException("failed to cancel uninstallation of plugin " + name, ex); } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index c761034ac1..520fcbbbef 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -32,6 +32,7 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; @@ -455,6 +456,8 @@ class DefaultPluginManagerTest { Files.createDirectories(mailPluginPath); when(mailPlugin.getDirectory()).thenReturn(mailPluginPath); when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin)); + ArgumentCaptor uninstallCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing().when(mailPlugin).setMarkedForUninstall(uninstallCaptor.capture()); AvailablePlugin git = createAvailable("scm-git-plugin"); when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); @@ -468,6 +471,8 @@ class DefaultPluginManagerTest { assertThat(mailPluginPath.resolve("uninstall")).doesNotExist(); verify(gitPendingPluginInformation).cancel(); + Boolean lasUninstallMarkerSet = uninstallCaptor.getAllValues().get(uninstallCaptor.getAllValues().size() - 1); + assertThat(lasUninstallMarkerSet).isFalse(); } } From 3b34cb527840e8354e820ce7cf2e0098b7846344 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 27 Sep 2019 11:46:14 +0200 Subject: [PATCH 04/25] Rename method and check permission --- scm-core/src/main/java/sonia/scm/plugin/PluginManager.java | 2 +- .../main/java/sonia/scm/plugin/DefaultPluginManager.java | 3 ++- .../java/sonia/scm/plugin/DefaultPluginManagerTest.java | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 1e787d24c3..964ebf605b 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -94,5 +94,5 @@ public interface PluginManager { */ void executePendingAndRestart(); - void cancelInstallations(); + void cancelPending(); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index c03cfdab84..ed5e7f78db 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -274,7 +274,8 @@ public class DefaultPluginManager implements PluginManager { } @Override - public void cancelInstallations() { + public void cancelPending() { + PluginPermissions.manage().check(); pendingUninstallQueue.forEach(PendingPluginUninstallation::cancel); pendingInstallQueue.forEach(PendingPluginInstallation::cancel); } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index 520fcbbbef..84bf843ca2 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -467,7 +467,7 @@ class DefaultPluginManagerTest { manager.install("scm-git-plugin", false); manager.uninstall("scm-ssh-plugin", false); - manager.cancelInstallations(); + manager.cancelPending(); assertThat(mailPluginPath.resolve("uninstall")).doesNotExist(); verify(gitPendingPluginInformation).cancel(); @@ -529,5 +529,9 @@ class DefaultPluginManagerTest { assertThrows(AuthorizationException.class, () -> manager.executePendingAndRestart()); } + @Test + void shouldThrowAuthorizationExceptionsForCancelPending() { + assertThrows(AuthorizationException.class, () -> manager.cancelPending()); + } } } From fd4070b1b1ccec0265d82b73bc9a83a8c0e27a27 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 27 Sep 2019 11:49:14 +0200 Subject: [PATCH 05/25] Add rest method --- .../v2/resources/PendingPluginResource.java | 15 +- .../resources/PendingPluginResourceTest.java | 202 ++++++------------ 2 files changed, 81 insertions(+), 136 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java index 3997a5e7c5..17fff25f16 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java @@ -8,7 +8,6 @@ import de.otto.edison.hal.Links; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.PluginManager; -import sonia.scm.plugin.PluginPermissions; import sonia.scm.web.VndMediaType; import javax.inject.Inject; @@ -46,8 +45,6 @@ public class PendingPluginResource { }) @Produces(VndMediaType.PLUGIN_COLLECTION) public Response getPending() { - PluginPermissions.manage().check(); - List pending = pluginManager .getAvailable() .stream() @@ -106,8 +103,18 @@ public class PendingPluginResource { @ResponseCode(code = 500, condition = "internal server error") }) public Response executePending() { - PluginPermissions.manage().check(); pluginManager.executePendingAndRestart(); return Response.ok().build(); } + + @POST + @Path("/cancel") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response cancelPending() { + pluginManager.cancelPending(); + return Response.ok().build(); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java index e5587b78cd..e1b0325cb2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java @@ -1,16 +1,11 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; -import org.apache.shiro.ShiroException; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.util.ThreadContext; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -24,8 +19,6 @@ import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; @@ -34,11 +27,8 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,9 +44,6 @@ class PendingPluginResourceTest { @Mock PluginDtoMapper mapper; - @Mock - Subject subject; - @InjectMocks PendingPluginResource pendingPluginResource; @@ -65,7 +52,6 @@ class PendingPluginResourceTest { @BeforeEach void prepareEnvironment() { dispatcher = MockDispatcherFactory.createDispatcher(); - dispatcher.getProviderFactory().register(new PermissionExceptionMapper()); PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource)); dispatcher.getRegistry().addSingletonResource(pluginRootResource); } @@ -74,141 +60,93 @@ class PendingPluginResourceTest { void mockMapper() { lenient().when(mapper.mapAvailable(any())).thenAnswer(invocation -> { PluginDto dto = new PluginDto(); - dto.setName(((AvailablePlugin)invocation.getArgument(0)).getDescriptor().getInformation().getName()); + dto.setName(((AvailablePlugin) invocation.getArgument(0)).getDescriptor().getInformation().getName()); return dto; }); lenient().when(mapper.mapInstalled(any(), any())).thenAnswer(invocation -> { PluginDto dto = new PluginDto(); - dto.setName(((InstalledPlugin)invocation.getArgument(0)).getDescriptor().getInformation().getName()); + dto.setName(((InstalledPlugin) invocation.getArgument(0)).getDescriptor().getInformation().getName()); return dto; }); } - @Nested - class withAuthorization { + @Test + void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); + when(availablePlugin.isPending()).thenReturn(false); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - @BeforeEach - void bindSubject() { - ThreadContext.bind(subject); - doNothing().when(subject).checkPermission("plugin:manage"); - } + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); - @AfterEach - void unbindSubject() { - ThreadContext.unbindSubject(); - } - - @Test - void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); - when(availablePlugin.isPending()).thenReturn(false); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); - assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); - } - - @Test - void shouldGetPendingAvailablePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - when(pluginManager.getAvailable()).thenReturn(emptyList()); - InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); - when(installedPlugin.isMarkedForUninstall()).thenReturn(true); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldExecutePendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(pluginManager).executePendingAndRestart(); - } + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); + assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); } - @Nested - class WithoutAuthorization { + @Test + void shouldGetPendingAvailablePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - @BeforeEach - void bindSubject() { - ThreadContext.bind(subject); - doThrow(new ShiroException()).when(subject).checkPermission("plugin:manage"); - } + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); - @AfterEach - void unbindSubject() { - ThreadContext.unbindSubject(); - } - - @Test - void shouldNotListPendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - verify(pluginManager, never()).executePendingAndRestart(); - } - - @Test - void shouldNotExecutePendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - verify(pluginManager, never()).executePendingAndRestart(); - } + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); } - static class PermissionExceptionMapper implements ExceptionMapper { + @Test + void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); - @Override - public Response toResponse(ShiroException exception) { - return Response.status(401).entity(exception.getMessage()).build(); - } + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + when(pluginManager.getAvailable()).thenReturn(emptyList()); + InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); + when(installedPlugin.isMarkedForUninstall()).thenReturn(true); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldExecutePendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).executePendingAndRestart(); + } + + @Test + void shouldCancelPendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/cancel"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).cancelPending(); } private AvailablePlugin createAvailablePlugin(String name) { From 52a9429ef5e511453443458af53562888863fdc9 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 27 Sep 2019 12:47:41 +0200 Subject: [PATCH 06/25] Add cancel link --- .../v2/resources/PendingPluginResource.java | 7 +- .../scm/api/v2/resources/ResourceLinks.java | 4 + .../resources/PendingPluginResourceTest.java | 202 ++++++++++++------ 3 files changed, 144 insertions(+), 69 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java index 17fff25f16..7d4663829e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java @@ -8,6 +8,7 @@ import de.otto.edison.hal.Links; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginPermissions; import sonia.scm.web.VndMediaType; import javax.inject.Inject; @@ -68,8 +69,12 @@ public class PendingPluginResource { List updateDtos = updatePlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); List uninstallDtos = uninstallPlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); - if (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty()) { + if ( + PluginPermissions.manage().isPermitted() && + (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty()) + ) { linksBuilder.single(link("execute", resourceLinks.pendingPluginCollection().executePending())); + linksBuilder.single(link("cancel", resourceLinks.pendingPluginCollection().cancelPending())); } Embedded.Builder embedded = Embedded.embeddedBuilder(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index c36cfc09ad..416af2e478 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -739,6 +739,10 @@ class ResourceLinks { return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("executePending").parameters().href(); } + String cancelPending() { + return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("cancelPending").parameters().href(); + } + String self() { return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("getPending").parameters().href(); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java index e1b0325cb2..da6e1b65d7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java @@ -1,11 +1,16 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; +import org.apache.shiro.ShiroException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -19,6 +24,8 @@ import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; @@ -44,6 +51,9 @@ class PendingPluginResourceTest { @Mock PluginDtoMapper mapper; + @Mock + Subject subject; + @InjectMocks PendingPluginResource pendingPluginResource; @@ -52,6 +62,7 @@ class PendingPluginResourceTest { @BeforeEach void prepareEnvironment() { dispatcher = MockDispatcherFactory.createDispatcher(); + dispatcher.getProviderFactory().register(new PermissionExceptionMapper()); PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource)); dispatcher.getRegistry().addSingletonResource(pluginRootResource); } @@ -70,83 +81,138 @@ class PendingPluginResourceTest { }); } - @Test - void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); - when(availablePlugin.isPending()).thenReturn(false); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + @Nested + class withAuthorization { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + lenient().when(subject.isPermitted("plugin:manage")).thenReturn(true); + } + + @AfterEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); + when(availablePlugin.isPending()).thenReturn(false); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); + assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); + } + + @Test + void shouldGetPendingAvailablePluginListWithInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + assertThat(response.getContentAsString()).contains("\"cancel\":{\"href\":\"/v2/plugins/pending/cancel\"}"); + } + + @Test + void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + when(pluginManager.getAvailable()).thenReturn(emptyList()); + InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); + when(installedPlugin.isMarkedForUninstall()).thenReturn(true); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldExecutePendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).executePendingAndRestart(); + } + + @Test + void shouldCancelPendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/cancel"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).cancelPending(); + } - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); - assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); } - @Test - void shouldGetPendingAvailablePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + @Nested + class WithoutAuthorization { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + when(subject.isPermitted("plugin:manage")).thenReturn(false); + } - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + @AfterEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldGetPendingAvailablePluginListWithoutInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); + assertThat(response.getContentAsString()).doesNotContain("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + assertThat(response.getContentAsString()).doesNotContain("\"cancel\":{\"href\":\"/v2/plugins/pending/cancel\"}"); + } } - @Test - void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + static class PermissionExceptionMapper implements ExceptionMapper { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - when(pluginManager.getAvailable()).thenReturn(emptyList()); - InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); - when(installedPlugin.isMarkedForUninstall()).thenReturn(true); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldExecutePendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(pluginManager).executePendingAndRestart(); - } - - @Test - void shouldCancelPendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/cancel"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(pluginManager).cancelPending(); + @Override + public Response toResponse(ShiroException exception) { + return Response.status(401).entity(exception.getMessage()).build(); + } } private AvailablePlugin createAvailablePlugin(String name) { From 32cb67f92e9b81c065c3286c592df42197a3f56b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 27 Sep 2019 14:05:19 +0200 Subject: [PATCH 07/25] implement MultiPluginActions for UpdateAll and CancelPending --- .../packages/ui-types/src/Plugin.js | 1 + .../components/ExecutePendingAction.js | 68 ---------- .../plugins/components/MultiPluginAction.js | 90 +++++++++++++ ...dingModal.js => MultiPluginActionModal.js} | 125 ++++++++++++++++-- .../plugins/containers/PluginsOverview.js | 43 ++++-- 5 files changed, 236 insertions(+), 91 deletions(-) delete mode 100644 scm-ui/src/admin/plugins/components/ExecutePendingAction.js create mode 100644 scm-ui/src/admin/plugins/components/MultiPluginAction.js rename scm-ui/src/admin/plugins/components/{ExecutePendingModal.js => MultiPluginActionModal.js} (57%) diff --git a/scm-ui-components/packages/ui-types/src/Plugin.js b/scm-ui-components/packages/ui-types/src/Plugin.js index c7612a8bf8..f5a621e1ff 100644 --- a/scm-ui-components/packages/ui-types/src/Plugin.js +++ b/scm-ui-components/packages/ui-types/src/Plugin.js @@ -17,6 +17,7 @@ export type Plugin = { }; export type PluginCollection = Collection & { + _links: Links, _embedded: { plugins: Plugin[] | string[] } diff --git a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js b/scm-ui/src/admin/plugins/components/ExecutePendingAction.js deleted file mode 100644 index 6c8d407205..0000000000 --- a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow -import React from "react"; -import { Button } from "@scm-manager/ui-components"; -import type { PendingPlugins } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import ExecutePendingModal from "./ExecutePendingModal"; - -type Props = { - pendingPlugins: PendingPlugins, - - // context props - t: string => string -}; - -type State = { - showModal: boolean -}; - -class ExecutePendingAction extends React.Component { - constructor(props: Props) { - super(props); - this.state = { - showModal: false - }; - } - - openModal = () => { - this.setState({ - showModal: true - }); - }; - - closeModal = () => { - this.setState({ - showModal: false - }); - }; - - renderModal = () => { - const { showModal } = this.state; - const { pendingPlugins } = this.props; - if (showModal) { - return ( - - ); - } - return null; - }; - - render() { - const { t } = this.props; - return ( - <> - {this.renderModal()} -