From 2873c44b5264cc8bbf05bbd9a7532ee9e72105da Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 24 Mar 2020 15:01:39 +0100 Subject: [PATCH] show restart checkbox only if restarting is supported --- scm-ui/ui-webapp/public/locales/de/admin.json | 3 +- scm-ui/ui-webapp/public/locales/en/admin.json | 3 +- .../admin/plugins/components/PluginModal.tsx | 26 +++++++++++----- .../scm/api/v2/resources/PluginDtoMapper.java | 23 +++++++++++--- .../PluginDtoCollectionMapperTest.java | 23 ++++++++++++-- .../api/v2/resources/PluginDtoMapperTest.java | 31 ++++++++++++++++++- 6 files changed, 93 insertions(+), 16 deletions(-) diff --git a/scm-ui/ui-webapp/public/locales/de/admin.json b/scm-ui/ui-webapp/public/locales/de/admin.json index e65a10eb85..b2ffbfe282 100644 --- a/scm-ui/ui-webapp/public/locales/de/admin.json +++ b/scm-ui/ui-webapp/public/locales/de/admin.json @@ -69,7 +69,8 @@ "restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.", "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet.", "cancelPending": "Die folgenden Plugin-Änderungen werden abgebrochen und zurückgesetzt.", - "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam." + "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam.", + "manualRestartRequired": "Nach dem die Plugin-Änderung durchgeführt wurde, muss SCM-Manager neu gestartet werden." } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/public/locales/en/admin.json b/scm-ui/ui-webapp/public/locales/en/admin.json index 5e7e44dcd2..a244a495b5 100644 --- a/scm-ui/ui-webapp/public/locales/en/admin.json +++ b/scm-ui/ui-webapp/public/locales/en/admin.json @@ -69,7 +69,8 @@ "restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.", "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted.", "cancelPending": "The following plugin changes will be canceled.", - "updateAllInfo": "The following plugin changes will be executed. You need to restart the scm-manager to make these changes effective." + "updateAllInfo": "The following plugin changes will be executed. You need to restart the scm-manager to make these changes effective.", + "manualRestartRequired": "After the plugin change has been made, scm-manager must be restarted." } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index 7c1d12a6b2..1074588e59 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -214,8 +214,25 @@ class PluginModal extends React.Component { }); }; - render() { + createRestartSectionContent = () => { const { restart } = this.state; + const { plugin, pluginAction, t } = this.props; + + if (plugin._links[pluginAction + "WithRestart"]) { + return ( + + ); + } else { + return {t("plugins.modal.manualRestartRequired")}; + } + }; + + render() { const { plugin, pluginAction, onClose, t } = this.props; const body = ( @@ -262,12 +279,7 @@ class PluginModal extends React.Component {
- + {this.createRestartSectionContent()}
{this.renderNotifications()} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java index 8304487732..ff2522c352 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import sonia.scm.lifecycle.Restarter; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.Plugin; @@ -47,6 +48,9 @@ public abstract class PluginDtoMapper { @Inject private ResourceLinks resourceLinks; + @Inject + private Restarter restarter; + public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto); public PluginDto mapInstalled(InstalledPlugin plugin, List availablePlugins) { @@ -78,12 +82,20 @@ public abstract class PluginDtoMapper { .self(information.getName())); if (!plugin.isPending() && PluginPermissions.manage().isPermitted()) { - links.single(link("install", resourceLinks.availablePlugin().install(information.getName()))); + String href = resourceLinks.availablePlugin().install(information.getName()); + appendLink(links, "install", href); } return new PluginDto(links.build()); } + private void appendLink(Links.Builder links, String name, String href) { + links.single(link(name, href)); + if (restarter.isSupported()) { + links.single(link(name + "WithRestart", href + "?restart=true")); + } + } + private PluginDto createDtoForInstalled(InstalledPlugin plugin, List availablePlugins) { PluginInformation information = plugin.getDescriptor().getInformation(); Optional availablePlugin = checkForUpdates(plugin, availablePlugins); @@ -96,13 +108,16 @@ public abstract class PluginDtoMapper { && !availablePlugin.get().isPending() && PluginPermissions.manage().isPermitted() ) { - links.single(link("update", resourceLinks.availablePlugin().install(information.getName()))); + String href = resourceLinks.availablePlugin().install(information.getName()); + appendLink(links, "update", href); } + if (plugin.isUninstallable() && (!availablePlugin.isPresent() || !availablePlugin.get().isPending()) && PluginPermissions.manage().isPermitted() ) { - links.single(link("uninstall", resourceLinks.installedPlugin().uninstall(information.getName()))); + String href = resourceLinks.installedPlugin().uninstall(information.getName()); + appendLink(links, "uninstall", href); } PluginDto dto = new PluginDto(links.build()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java index 0029296882..a3fa801a22 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapperTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; @@ -36,6 +36,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.lifecycle.Restarter; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.AvailablePluginDescriptor; import sonia.scm.plugin.InstalledPlugin; @@ -59,6 +60,9 @@ class PluginDtoCollectionMapperTest { ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); + @Mock + private Restarter restarter; + @InjectMocks PluginDtoMapperImpl pluginDtoMapper; @@ -142,7 +146,7 @@ class PluginDtoCollectionMapperTest { } @Test - void shouldAddInstallLinkForNewVersionWhenPermitted() { + void shouldAddUpdateLinkForNewVersionWhenPermitted() { when(subject.isPermitted("plugin:manage")).thenReturn(true); PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); @@ -154,6 +158,21 @@ class PluginDtoCollectionMapperTest { assertThat(plugin.getLinks().getLinkBy("update")).isNotEmpty(); } + @Test + void shouldAddUpdateWithRestartLinkForNewVersionWhenPermitted() { + when(restarter.isSupported()).thenReturn(true); + when(subject.isPermitted("plugin:manage")).thenReturn(true); + PluginDtoCollectionMapper mapper = new PluginDtoCollectionMapper(resourceLinks, pluginDtoMapper, manager); + + HalRepresentation result = mapper.mapInstalled( + singletonList(createInstalledPlugin("scm-some-plugin", "1")), + singletonList(createAvailablePlugin("scm-some-plugin", "2"))); + + PluginDto plugin = getPluginDtoFromResult(result); + assertThat(plugin.getLinks().getLinkBy("update")).isNotEmpty(); + assertThat(plugin.getLinks().getLinkBy("updateWithRestart")).isNotEmpty(); + } + @Test void shouldSetInstalledPluginPendingWhenCorrespondingAvailablePluginIsPending() { when(subject.isPermitted("plugin:manage")).thenReturn(true); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java index c4ccbe521d..51a7001140 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import com.google.common.collect.ImmutableSet; @@ -35,6 +35,7 @@ import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.lifecycle.Restarter; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.AvailablePluginDescriptor; import sonia.scm.plugin.InstalledPlugin; @@ -56,6 +57,9 @@ class PluginDtoMapperTest { @SuppressWarnings("unused") // Is injected private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("https://hitchhiker.com/")); + @Mock + private Restarter restarter; + @InjectMocks private PluginDtoMapperImpl mapper; @@ -122,6 +126,7 @@ class PluginDtoMapperTest { PluginDto dto = mapper.mapAvailable(plugin); assertThat(dto.getLinks().getLinkBy("install")).isEmpty(); + assertThat(dto.getLinks().getLinkBy("installWithRestart")).isEmpty(); } @Test @@ -134,6 +139,17 @@ class PluginDtoMapperTest { .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install"); } + @Test + void shouldAppendInstallWithRestartLink() { + when(restarter.isSupported()).thenReturn(true); + when(subject.isPermitted("plugin:manage")).thenReturn(true); + AvailablePlugin plugin = createAvailable(createPluginInformation()); + + PluginDto dto = mapper.mapAvailable(plugin); + assertThat(dto.getLinks().getLinkBy("installWithRestart").get().getHref()) + .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install?restart=true"); + } + @Test void shouldReturnMiscellaneousIfCategoryIsNull() { PluginInformation information = createPluginInformation(); @@ -162,4 +178,17 @@ class PluginDtoMapperTest { assertThat(dto.getLinks().getLinkBy("uninstall").get().getHref()) .isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin/uninstall"); } + + @Test + void shouldAppendUninstallWithRestartLink() { + when(restarter.isSupported()).thenReturn(true); + when(subject.isPermitted("plugin:manage")).thenReturn(true); + + InstalledPlugin plugin = createInstalled(createPluginInformation()); + when(plugin.isUninstallable()).thenReturn(true); + + PluginDto dto = mapper.mapInstalled(plugin, emptyList()); + assertThat(dto.getLinks().getLinkBy("uninstallWithRestart").get().getHref()) + .isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin/uninstall?restart=true"); + } }