diff --git a/docs/dtd/plugin/2.0.0-01.dtd b/docs/dtd/plugin/2.0.0-01.dtd index eec149f3e8..954a2c2219 100644 --- a/docs/dtd/plugin/2.0.0-01.dtd +++ b/docs/dtd/plugin/2.0.0-01.dtd @@ -29,46 +29,28 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -121,4 +103,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index ba06282720..3166c6ec75 100644 --- a/pom.xml +++ b/pom.xml @@ -437,7 +437,7 @@ sonia.scm.maven smp-maven-plugin - 1.0.0-alpha-4 + 1.0.0-alpha-6 diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index d23bbaf07d..95deebfd60 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -73,7 +73,7 @@ public class ScmConfiguration implements Configuration { * Default plugin url */ public static final String DEFAULT_PLUGINURL = - "http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false"; + "http://download.scm-manager.org/api/v2/plugins.json?os={os}&arch={arch}&snapshot=false&version={version}"; /** * Default url for login information (plugin and feature tips on the login page). diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java index 6de52c3cca..22911041d4 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java @@ -1,19 +1,19 @@ /** * Copyright (c) 2010, Sebastian Sdorra * All rights reserved. - * + *

* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + *

* 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -24,558 +24,88 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- import com.github.sdorra.ssp.PermissionObject; import com.github.sdorra.ssp.StaticPermissions; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; +import lombok.Data; import sonia.scm.Validateable; import sonia.scm.util.Util; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; //~--- JDK imports ------------------------------------------------------------ /** - * * @author Sebastian Sdorra */ +@Data @StaticPermissions( - value = "plugin", - generatedClass = "PluginPermissions", + value = "plugin", + generatedClass = "PluginPermissions", permissions = {}, - globalPermissions = { "read", "manage" }, + globalPermissions = {"read", "manage"}, custom = true, customGlobal = true ) @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "plugin-information") -public class PluginInformation - implements PermissionObject, Validateable, Cloneable, Serializable -{ +public class PluginInformation implements PermissionObject, Validateable, Cloneable, Serializable { - /** Field description */ private static final long serialVersionUID = 461382048865977206L; - //~--- methods -------------------------------------------------------------- + private String name; + private String version; + private String displayName; + private String description; + private String author; + private String category; + private String avatarUrl; + private PluginCondition condition; + private PluginState state; - /** - * Method description - * - * - * @return - * - * @since 1.11 - */ @Override - public PluginInformation clone() - { + public PluginInformation clone() { PluginInformation clone = new PluginInformation(); - - clone.setArtifactId(artifactId); + clone.setName(name); + clone.setVersion(version); + clone.setDisplayName(displayName); + clone.setDescription(description); clone.setAuthor(author); clone.setCategory(category); - clone.setTags(tags); - - if (condition != null) - { + clone.setAvatarUrl(avatarUrl); + clone.setState(state); + if (condition != null) { clone.setCondition(condition.clone()); } - - clone.setDescription(description); - clone.setGroupId(groupId); - clone.setName(name); - - if (Util.isNotEmpty(screenshots)) - { - clone.setScreenshots(new ArrayList(screenshots)); - } - - clone.setState(state); - clone.setUrl(url); - clone.setVersion(version); - clone.setWiki(wiki); - return clone; } - /** - * Method description - * - * - * @param obj - * - * @return - */ @Override - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - - if (getClass() != obj.getClass()) - { - return false; - } - - final PluginInformation other = (PluginInformation) obj; - - //J- - return Objects.equal(artifactId, other.artifactId) - && Objects.equal(author, other.author) - && Objects.equal(category, other.category) - && Objects.equal(tags, other.tags) - && Objects.equal(condition, other.condition) - && Objects.equal(description, other.description) - && Objects.equal(groupId, other.groupId) - && Objects.equal(name, other.name) - && Objects.equal(screenshots, other.screenshots) - && Objects.equal(state, other.state) - && Objects.equal(url, other.url) - && Objects.equal(version, other.version) - && Objects.equal(wiki, other.wiki); - //J+ + public String getId() { + return getName(true); } - /** - * Method description - * - * - * @return - */ - @Override - public int hashCode() - { - return Objects.hashCode(artifactId, author, category, tags, condition, - description, groupId, name, screenshots, state, url, version, wiki); - } + public String getName(boolean withVersion) { + StringBuilder id = new StringBuilder(name); - /** - * Method description - * - * - * @return - */ - @Override - public String toString() - { - //J- - return MoreObjects.toStringHelper(this) - .add("artifactId", artifactId) - .add("author", author) - .add("category", category) - .add("tags", tags) - .add("condition", condition) - .add("description", description) - .add("groupId", groupId) - .add("name", name) - .add("screenshots", screenshots) - .add("state", state) - .add("url", url) - .add("version", version) - .add("wiki", wiki) - .toString(); - //J+ - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public String getArtifactId() - { - return artifactId; - } - - /** - * Method description - * - * - * @return - */ - public String getAuthor() - { - return author; - } - - /** - * Method description - * - * - * @return - */ - public String getCategory() - { - return category; - } - - /** - * Method description - * - * - * @return - */ - public PluginCondition getCondition() - { - return condition; - } - - /** - * Method description - * - * - * @return - */ - public String getDescription() - { - return description; - } - - /** - * Method description - * - * - * @return - */ - public String getGroupId() - { - return groupId; - } - - /** - * Method description - * - * - * @return - */ - @Override - public String getId() - { - return getId(true); - } - - /** - * Method description - * - * - * @param withVersion - * - * @return - * @since 1.21 - */ - public String getId(boolean withVersion) - { - StringBuilder id = new StringBuilder(groupId); - - id.append(":").append(artifactId); - - if (withVersion) - { + if (withVersion) { id.append(":").append(version); } - return id.toString(); } - /** - * Method description - * - * - * @return - */ - public String getName() - { - return name; - } - - /** - * Method description - * - * - * @return - */ - public List getScreenshots() - { - return screenshots; - } - - /** - * Method description - * - * - * @return - */ - public PluginState getState() - { - return state; - } - - /** - * Method description - * - * - * @return - */ - public List getTags() - { - return tags; - } - - /** - * Method description - * - * - * @return - */ - public String getUrl() - { - return url; - } - - /** - * Method description - * - * - * @return - */ - public String getVersion() - { - return version; - } - - /** - * Method description - * - * - * @return - */ - public String getWiki() - { - return wiki; - } - - /** - * Method description - * - * - * @return - */ @Override - public boolean isValid() - { - return Util.isNotEmpty(groupId) && Util.isNotEmpty(artifactId) - && Util.isNotEmpty(name) && Util.isNotEmpty(version); + public boolean isValid() { + return Util.isNotEmpty(name) && Util.isNotEmpty(version); } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param artifactId - */ - public void setArtifactId(String artifactId) - { - this.artifactId = artifactId; - } - - /** - * Method description - * - * - * @param author - */ - public void setAuthor(String author) - { - this.author = author; - } - - /** - * Method description - * - * - * @param category - */ - public void setCategory(String category) - { - this.category = category; - } - - /** - * Method description - * - * - * @param condition - */ - public void setCondition(PluginCondition condition) - { - this.condition = condition; - } - - /** - * Method description - * - * - * @param description - */ - public void setDescription(String description) - { - this.description = description; - } - - /** - * Method description - * - * - * @param groupId - */ - public void setGroupId(String groupId) - { - this.groupId = groupId; - } - - /** - * Method description - * - * - * @param name - */ - public void setName(String name) - { - this.name = name; - } - - /** - * Method description - * - * - * @param screenshots - */ - public void setScreenshots(List screenshots) - { - this.screenshots = screenshots; - } - - /** - * Method description - * - * - * @param state - */ - public void setState(PluginState state) - { - this.state = state; - } - - /** - * Method description - * - * - * @param tags - */ - public void setTags(List tags) - { - this.tags = tags; - } - - /** - * Method description - * - * - * @param url - */ - public void setUrl(String url) - { - this.url = url; - } - - /** - * Method description - * - * - * @param version - */ - public void setVersion(String version) - { - this.version = version; - } - - /** - * Method description - * - * - * @param wiki - */ - public void setWiki(String wiki) - { - this.wiki = wiki; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String artifactId; - - /** Field description */ - private String author; - - /** Field description */ - private String category; - - /** Field description */ - private PluginCondition condition; - - /** Field description */ - private String description; - - /** Field description */ - private String groupId; - - /** Field description */ - private String name; - - /** Field description */ - @XmlElement(name = "screenshot") - @XmlElementWrapper(name = "screenshots") - private List screenshots; - - /** Field description */ - private PluginState state; - - /** Field description */ - @XmlElement(name = "tag") - @XmlElementWrapper(name = "tags") - private List tags; - - /** Field description */ - private String url; - - /** Field description */ - private String version; - - /** Field description */ - private String wiki; } diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java index f44de35e8a..5443b2328d 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java @@ -75,29 +75,24 @@ public class PluginInformationComparator { int result = 0; - result = Util.compare(plugin.getGroupId(), other.getGroupId()); + result = Util.compare(plugin.getName(), other.getName()); if (result == 0) { - result = Util.compare(plugin.getArtifactId(), other.getArtifactId()); + PluginState state = plugin.getState(); + PluginState otherState = other.getState(); - if (result == 0) + if ((state != null) && (otherState != null)) { - PluginState state = plugin.getState(); - PluginState otherState = other.getState(); - - if ((state != null) && (otherState != null)) - { - result = state.getCompareValue() - otherState.getCompareValue(); - } - else if ((state == null) && (otherState != null)) - { - result = 1; - } - else if ((state != null) && (otherState == null)) - { - result = -1; - } + result = state.getCompareValue() - otherState.getCompareValue(); + } + else if ((state == null) && (otherState != null)) + { + result = 1; + } + else if ((state != null) && (otherState == null)) + { + result = -1; } } diff --git a/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java b/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java index 63d5e8fb8f..f674bdd2ba 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java +++ b/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java @@ -219,16 +219,10 @@ public final class SmpArchive throw new PluginException("could not find information section"); } - if (Strings.isNullOrEmpty(info.getGroupId())) + if (Strings.isNullOrEmpty(info.getName())) { throw new PluginException( - "could not find groupId in plugin descriptor"); - } - - if (Strings.isNullOrEmpty(info.getArtifactId())) - { - throw new PluginException( - "could not find artifactId in plugin descriptor"); + "could not find name in plugin descriptor"); } if (Strings.isNullOrEmpty(info.getVersion())) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index c06edcd918..5807ffa998 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -31,7 +31,6 @@ package sonia.scm.repository.api; -import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.cache.CacheManager; diff --git a/scm-core/src/main/java/sonia/scm/util/IOUtil.java b/scm-core/src/main/java/sonia/scm/util/IOUtil.java index 16f84d1031..4058c089f4 100644 --- a/scm-core/src/main/java/sonia/scm/util/IOUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/IOUtil.java @@ -37,14 +37,11 @@ package sonia.scm.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.io.Command; import sonia.scm.io.CommandResult; import sonia.scm.io.SimpleCommand; import sonia.scm.io.ZipUnArchiver; -//~--- JDK imports ------------------------------------------------------------ - import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -55,12 +52,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra diff --git a/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java b/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java index 95addf388f..d7f4ecf515 100644 --- a/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java +++ b/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java @@ -85,7 +85,7 @@ public class SmpArchiveTest public void testExtract() throws IOException, ParserConfigurationException, SAXException { - File archive = createArchive("sonia.sample", "sample", "1.0"); + File archive = createArchive("sonia.sample", "1.0"); File target = tempFolder.newFolder(); IOUtil.mkdirs(target); @@ -112,7 +112,7 @@ public class SmpArchiveTest @Test public void testGetPlugin() throws IOException { - File archive = createArchive("sonia.sample", "sample", "1.0"); + File archive = createArchive("sonia.sample", "1.0"); Plugin plugin = SmpArchive.create(archive).getPlugin(); assertNotNull(plugin); @@ -121,8 +121,7 @@ public class SmpArchiveTest assertNotNull(info); - assertEquals("sonia.sample", info.getGroupId()); - assertEquals("sample", info.getArtifactId()); + assertEquals("sonia.sample", info.getName()); assertEquals("1.0", info.getVersion()); } @@ -132,22 +131,9 @@ public class SmpArchiveTest * @throws IOException */ @Test(expected = PluginException.class) - public void testWithMissingArtifactId() throws IOException + public void testWithMissingName() throws IOException { - File archive = createArchive("sonia.sample", null, "1.0"); - - SmpArchive.create(archive).getPlugin(); - } - - /** - * Method description - * - * @throws IOException - */ - @Test(expected = PluginException.class) - public void testWithMissingGroupId() throws IOException - { - File archive = createArchive(null, "sample", "1.0"); + File archive = createArchive( null, "1.0"); SmpArchive.create(archive).getPlugin(); } @@ -160,7 +146,7 @@ public class SmpArchiveTest @Test(expected = PluginException.class) public void testWithMissingVersion() throws IOException { - File archive = createArchive("sonia.sample", "sample", null); + File archive = createArchive("sonia.sample", null); SmpArchive.create(archive).getPlugin(); } @@ -169,13 +155,12 @@ public class SmpArchiveTest * Method description * * - * @param groupId - * @param artifactId + * @param name * @param version * * @return */ - private File createArchive(String groupId, String artifactId, String version) + private File createArchive(String name, String version) { File archiveFile; @@ -183,7 +168,7 @@ public class SmpArchiveTest { File descriptor = tempFolder.newFile(); - writeDescriptor(descriptor, groupId, artifactId, version); + writeDescriptor(descriptor, name, version); archiveFile = tempFolder.newFile(); try (ZipOutputStream zos = @@ -229,14 +214,13 @@ public class SmpArchiveTest * * * @param descriptor - * @param groupId - * @param artifactId + * @param name * @param version * * @throws IOException */ - private void writeDescriptor(File descriptor, String groupId, - String artifactId, String version) + private void writeDescriptor(File descriptor, String name, + String version) throws IOException { try @@ -252,8 +236,7 @@ public class SmpArchiveTest writer.writeStartDocument(); writer.writeStartElement("plugin"); writer.writeStartElement("information"); - writeElement(writer, "groupId", groupId); - writeElement(writer, "artifactId", artifactId); + writeElement(writer, "name", name); writeElement(writer, "version", version); writer.writeEndElement(); diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index a838e2f146..ac1f007394 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -10,7 +10,6 @@ scm-git-plugin - scm-git-plugin smp https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java index a3f63b8f5a..e7f26d6885 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java @@ -1,6 +1,5 @@ package sonia.scm.repository.spi; -import com.google.common.base.Throwables; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import sonia.scm.repository.GitUtil; diff --git a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml index ff699441a8..ba1d625fb4 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml +++ b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -46,14 +46,10 @@ 2 - Sebastian Sdorra - Git - - git - scm - vcs - dvcs - + Git + Cloudogu GmbH + Source Code Management + /images/git-logo.png diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 025f79add3..e5decb0567 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -10,7 +10,6 @@ scm-hg-plugin - scm-hg-plugin smp https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java index b8883a0d92..1d227fb54e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java @@ -40,11 +40,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.web.HgUtil; -//~--- JDK imports ------------------------------------------------------------ - +import javax.servlet.http.HttpServletRequest; import java.util.Map; -import javax.servlet.http.HttpServletRequest; +//~--- JDK imports ------------------------------------------------------------ /** * diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml index 1d0b05c4a8..352192121f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml +++ b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -46,15 +46,10 @@ jo 2 - Sebastian Sdorra - Mercurial - - mercurial - hg - scm - vcs - dvcs - + Mercurial + Cloudogu GmbH + Source Code Management + /images/hg-logo.png diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml index 6cfa74ea61..1a12234014 100644 --- a/scm-plugins/scm-legacy-plugin/pom.xml +++ b/scm-plugins/scm-legacy-plugin/pom.xml @@ -6,8 +6,9 @@ scm-plugins 2.0.0-SNAPSHOT - sonia.scm.plugins + scm-legacy-plugin + Support migrated repository urls and v1 passwords 2.0.0-SNAPSHOT smp @@ -21,6 +22,7 @@ ${servlet.version} provided + javax.ws.rs jsr311-api diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js index 396558f852..6728a93c78 100644 --- a/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js +++ b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import { withRouter } from "react-router-dom"; +import {withRouter} from "react-router-dom"; class DummyComponent extends React.Component { render() { diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/index.js b/scm-plugins/scm-legacy-plugin/src/main/js/index.js index 97c3eb7e32..7be0386359 100644 --- a/scm-plugins/scm-legacy-plugin/src/main/js/index.js +++ b/scm-plugins/scm-legacy-plugin/src/main/js/index.js @@ -1,14 +1,9 @@ // @flow import React from "react"; -import { withRouter } from "react-router-dom"; -import { binder } from "@scm-manager/ui-extensions"; -import { - ProtectedRoute, - apiClient, - ErrorNotification, - ErrorBoundary -} from "@scm-manager/ui-components"; +import {withRouter} from "react-router-dom"; +import {binder} from "@scm-manager/ui-extensions"; +import {apiClient, ErrorBoundary, ErrorNotification, ProtectedRoute} from "@scm-manager/ui-components"; import DummyComponent from "./DummyComponent"; import type {Links} from "@scm-manager/ui-types"; diff --git a/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml index f8a3c8c7b4..2a6b553cdf 100644 --- a/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml +++ b/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -46,7 +46,9 @@ 2 - Sebastian Sdorra + Legacy + Cloudogu GmbH + Legacy Support diff --git a/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java index 169c80eae2..a28f87dbf8 100644 --- a/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java +++ b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java @@ -10,7 +10,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 4386efde5b..83da627eb9 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -10,7 +10,6 @@ scm-svn-plugin - scm-svn-plugin smp https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml index 302abd2b10..5e941e98e1 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml +++ b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -46,14 +46,10 @@ 2 - Sebastian Sdorra - Subversion - - subversion - scm - vcs - svn - + Subversion + Cloudogu GmbH + Source Code Management + /images/svn-logo.gif diff --git a/scm-ui-components/packages/ui-components/src/Autocomplete.js b/scm-ui-components/packages/ui-components/src/Autocomplete.js index 22107e75b6..116df6562a 100644 --- a/scm-ui-components/packages/ui-components/src/Autocomplete.js +++ b/scm-ui-components/packages/ui-components/src/Autocomplete.js @@ -1,7 +1,7 @@ // @flow import React from "react"; -import { AsyncCreatable, Async } from "react-select"; -import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types"; +import {Async, AsyncCreatable} from "react-select"; +import type {AutocompleteObject, SelectValue} from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; type Props = { diff --git a/scm-ui-components/packages/ui-components/src/CardColumn.js b/scm-ui-components/packages/ui-components/src/CardColumn.js index e1eb65255a..65710c7be2 100644 --- a/scm-ui-components/packages/ui-components/src/CardColumn.js +++ b/scm-ui-components/packages/ui-components/src/CardColumn.js @@ -25,12 +25,17 @@ const styles = { }, content: { display: "flex", - flexGrow: 1 + flexGrow: 1, + alignItems: "center", + justifyContent: "space-between" }, footer: { display: "flex", marginTop: "auto", paddingBottom: "1.5rem" + }, + noBottomMargin: { + marginBottom: "0 !important" } }; @@ -38,9 +43,11 @@ type Props = { title: string, description: string, avatar: React.Node, + contentRight?: React.Node, footerLeft: React.Node, footerRight: React.Node, link: string, + // context props classes: any }; @@ -55,7 +62,15 @@ class CardColumn extends React.Component { }; render() { - const { avatar, title, description, footerLeft, footerRight, classes } = this.props; + const { + avatar, + title, + description, + contentRight, + footerLeft, + footerRight, + classes + } = this.props; const link = this.createLink(); return ( <> @@ -64,16 +79,29 @@ class CardColumn extends React.Component {

{avatar}
-
+
-
+

{title}

{description}

+ {contentRight && contentRight}
-
+
{footerLeft}
{footerRight}
diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js index 72173679dd..0c6367a474 100644 --- a/scm-ui-components/packages/ui-components/src/ErrorNotification.js +++ b/scm-ui-components/packages/ui-components/src/ErrorNotification.js @@ -1,7 +1,7 @@ //@flow import React from "react"; -import { translate } from "react-i18next"; -import { BackendError, ForbiddenError, UnauthorizedError } from "./errors"; +import {translate} from "react-i18next"; +import {BackendError, ForbiddenError, UnauthorizedError} from "./errors"; import Notification from "./Notification"; import BackendErrorNotification from "./BackendErrorNotification"; diff --git a/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js index e39130f1d7..8aeeedc3e4 100644 --- a/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js +++ b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { translate } from "react-i18next"; +import {translate} from "react-i18next"; import type AutocompleteProps from "./UserGroupAutocomplete"; import UserGroupAutocomplete from "./UserGroupAutocomplete"; diff --git a/scm-ui-components/packages/ui-components/src/UserAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js index 9ef7aaa7a7..308835d8db 100644 --- a/scm-ui-components/packages/ui-components/src/UserAutocomplete.js +++ b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { translate } from "react-i18next"; +import {translate} from "react-i18next"; import type AutocompleteProps from "./UserGroupAutocomplete"; import UserGroupAutocomplete from "./UserGroupAutocomplete"; diff --git a/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js index 0d6e3ec46e..d038e21221 100644 --- a/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js +++ b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import type { SelectValue } from "@scm-manager/ui-types"; +import type {SelectValue} from "@scm-manager/ui-types"; import Autocomplete from "./Autocomplete"; export type AutocompleteProps = { diff --git a/scm-ui-components/packages/ui-components/src/repos/Diff.js b/scm-ui-components/packages/ui-components/src/repos/Diff.js index 97692210c2..7a5bcbf4a2 100644 --- a/scm-ui-components/packages/ui-components/src/repos/Diff.js +++ b/scm-ui-components/packages/ui-components/src/repos/Diff.js @@ -1,7 +1,7 @@ //@flow import React from "react"; import DiffFile from "./DiffFile"; -import type { DiffObjectProps, File } from "./DiffTypes"; +import type {DiffObjectProps, File} from "./DiffTypes"; type Props = DiffObjectProps & { diff: File[] diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js index 3f849dff6b..ab020f723d 100644 --- a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js +++ b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js @@ -1,17 +1,10 @@ //@flow import React from "react"; -import { - Hunk, - Diff as DiffComponent, - getChangeKey, - Change, - DiffObjectProps, - File -} from "react-diff-view"; +import {Change, Diff as DiffComponent, DiffObjectProps, File, getChangeKey, Hunk} from "react-diff-view"; import injectSheets from "react-jss"; import classNames from "classnames"; -import { translate } from "react-i18next"; -import { ButtonGroup, Button } from "../buttons"; +import {translate} from "react-i18next"; +import {Button, ButtonGroup} from "../buttons"; const styles = { panel: { diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js index c8a5250756..876b757f44 100644 --- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import { apiClient } from "../apiclient"; +import {apiClient} from "../apiclient"; import ErrorNotification from "../ErrorNotification"; import parser from "gitdiff-parser"; diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js index 8d46a9f0b4..74a4eb0050 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js @@ -1,9 +1,9 @@ //@flow import React from "react"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; -import { ButtonAddons, Button } from "../../buttons"; -import { createChangesetLink, createSourcesLink } from "./changesets"; -import { translate } from "react-i18next"; +import type {Changeset, Repository} from "@scm-manager/ui-types"; +import {Button, ButtonAddons} from "../../buttons"; +import {createChangesetLink, createSourcesLink} from "./changesets"; +import {translate} from "react-i18next"; type Props = { repository: Repository, diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js index 0150fcb9c3..6de47f0c87 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js @@ -1,16 +1,16 @@ //@flow import React from "react"; -import type { Changeset, Repository, Tag } from "@scm-manager/ui-types"; +import type {Changeset, Repository} from "@scm-manager/ui-types"; import classNames from "classnames"; -import { Interpolate, translate } from "react-i18next"; +import {Interpolate, translate} from "react-i18next"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; -import { DateFromNow } from "../.."; +import {DateFromNow} from "../.."; import ChangesetAuthor from "./ChangesetAuthor"; -import { parseDescription } from "./changesets"; -import { AvatarWrapper, AvatarImage } from "../../avatar"; -import { ExtensionPoint} from "@scm-manager/ui-extensions"; +import {parseDescription} from "./changesets"; +import {AvatarImage, AvatarWrapper} from "../../avatar"; +import {ExtensionPoint} from "@scm-manager/ui-extensions"; import ChangesetTags from "./ChangesetTags"; import ChangesetButtonGroup from "./ChangesetButtonGroup"; diff --git a/scm-ui-components/packages/ui-components/src/repos/index.js b/scm-ui-components/packages/ui-components/src/repos/index.js index fd1448fbdd..19f9a160b3 100644 --- a/scm-ui-components/packages/ui-components/src/repos/index.js +++ b/scm-ui-components/packages/ui-components/src/repos/index.js @@ -1,5 +1,6 @@ // @flow import * as diffs from "./diffs"; + export { diffs }; export * from "./changesets"; diff --git a/scm-ui-components/packages/ui-types/src/Plugin.js b/scm-ui-components/packages/ui-types/src/Plugin.js index bb9c5e7d88..3f4f9858c1 100644 --- a/scm-ui-components/packages/ui-types/src/Plugin.js +++ b/scm-ui-components/packages/ui-types/src/Plugin.js @@ -1,12 +1,15 @@ //@flow -import type { Collection, Links } from "./hal"; +import type {Collection, Links} from "./hal"; + export type Plugin = { name: string, - type: string, version: string, - author: string, + displayName: string, description?: string, + author: string, + category: string, + avatarUrl: string, _links: Links }; diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index a94965fa68..0bc220515a 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -41,7 +41,7 @@ "date-format": "Datumsformat", "anonymous-access-enabled": "Anonyme Zugriffe erlauben", "skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen", - "plugin-url": "Plugin URL", + "plugin-url": "Plugin Center URL", "enabled-xsrf-protection": "XSRF Protection aktivieren", "namespace-strategy": "Namespace Strategie", "login-info-url": "Login Info URL" @@ -55,7 +55,7 @@ "help": { "realmDescriptionHelpText": "Beschreibung des Authentication Realm.", "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", - "pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", + "pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", "enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.", "enableRepositoryArchiveHelpText": "Repository Archive aktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", "disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index 894d0563ba..ce0f7252df 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -41,7 +41,7 @@ "date-format": "Date Format", "anonymous-access-enabled": "Anonymous Access Enabled", "skip-failed-authenticators": "Skip Failed Authenticators", - "plugin-url": "Plugin URL", + "plugin-url": "Plugin Center URL", "enabled-xsrf-protection": "Enabled XSRF Protection", "namespace-strategy": "Namespace Strategy", "login-info-url": "Login Info URL" @@ -55,7 +55,7 @@ "help": { "realmDescriptionHelpText": "Enter authentication realm description.", "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.", - "pluginRepositoryHelpText": "The url of the plugin repository. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture", + "pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture", "enableForwardingHelpText": "Enable mod_proxy port forwarding.", "enableRepositoryArchiveHelpText": "Enable repository archives. A complete page reload is required after a change of this value.", "disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.", diff --git a/scm-ui/src/admin/components/form/GeneralSettings.js b/scm-ui/src/admin/components/form/GeneralSettings.js index a2fb1127af..842d0ed7f4 100644 --- a/scm-ui/src/admin/components/form/GeneralSettings.js +++ b/scm-ui/src/admin/components/form/GeneralSettings.js @@ -29,6 +29,7 @@ class GeneralSettings extends React.Component { t, realmDescription, loginInfoUrl, + pluginUrl, enabledXsrfProtection, namespaceStrategy, hasUpdatePermission, @@ -78,6 +79,17 @@ class GeneralSettings extends React.Component { />
+
+
+ +
+
); } @@ -85,7 +97,6 @@ class GeneralSettings extends React.Component { handleLoginInfoUrlChange = (value: string) => { this.props.onChange(true, value, "loginInfoUrl"); }; - handleRealmDescriptionChange = (value: string) => { this.props.onChange(true, value, "realmDescription"); }; @@ -95,6 +106,9 @@ class GeneralSettings extends React.Component { handleNamespaceStrategyChange = (value: string) => { this.props.onChange(true, value, "namespaceStrategy"); }; + handlePluginCenterUrlChange = (value: string) => { + this.props.onChange(true, value, "pluginUrl"); + }; } export default translate("config")(GeneralSettings); diff --git a/scm-ui/src/admin/containers/Admin.js b/scm-ui/src/admin/containers/Admin.js index 58e37a0a7c..22c6184548 100644 --- a/scm-ui/src/admin/containers/Admin.js +++ b/scm-ui/src/admin/containers/Admin.js @@ -1,14 +1,24 @@ // @flow import React from "react"; import { translate } from "react-i18next"; -import {Redirect, Route, Switch} from "react-router-dom"; +import { Redirect, Route, Switch } from "react-router-dom"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import type { History } from "history"; import { connect } from "react-redux"; import { compose } from "redux"; import type { Links } from "@scm-manager/ui-types"; -import { Page, Navigation, NavLink, Section, SubNavigation } from "@scm-manager/ui-components"; -import { getLinks } from "../../modules/indexResource"; +import { + Page, + Navigation, + NavLink, + Section, + SubNavigation +} from "@scm-manager/ui-components"; +import { + getLinks, + getAvailablePluginsLink, + getInstalledPluginsLink +} from "../../modules/indexResource"; import AdminDetails from "./AdminDetails"; import PluginsOverview from "../plugins/containers/PluginsOverview"; import GlobalConfig from "./GlobalConfig"; @@ -18,6 +28,8 @@ import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; type Props = { links: Links, + availablePluginsLink: string, + installedPluginsLink: string, // context objects t: string => string, @@ -28,7 +40,7 @@ type Props = { class Admin extends React.Component { stripEndingSlash = (url: string) => { if (url.endsWith("/")) { - if(url.includes("role")) { + if (url.includes("role")) { return url.substring(0, url.length - 2); } return url.substring(0, url.length - 1); @@ -47,7 +59,7 @@ class Admin extends React.Component { }; render() { - const { links, t } = this.props; + const { links, availablePluginsLink, installedPluginsLink, t } = this.props; const url = this.matchedUrl(); const extensionProps = { @@ -62,34 +74,54 @@ class Admin extends React.Component { - - + + ( - + )} /> ( - + )} /> ( - + )} /> ( - + )} /> { ( - + )} /> ( - - )} + render={() => } /> { icon="fas fa-info-circle" label={t("admin.menu.informationNavLink")} /> - { - links.plugins && - - - {/* Activate this again after available plugins page is created */} - {/**/} - - } + {(availablePluginsLink || installedPluginsLink) && ( + + {installedPluginsLink && ( + + )} + {availablePluginsLink && ( + + )} + + )} { const mapStateToProps = (state: any) => { const links = getLinks(state); + const availablePluginsLink = getAvailablePluginsLink(state); + const installedPluginsLink = getInstalledPluginsLink(state); return { - links + links, + availablePluginsLink, + installedPluginsLink }; }; diff --git a/scm-ui/src/admin/plugins/components/PluginAvatar.js b/scm-ui/src/admin/plugins/components/PluginAvatar.js index 10408f14bd..42a1fd732b 100644 --- a/scm-ui/src/admin/plugins/components/PluginAvatar.js +++ b/scm-ui/src/admin/plugins/components/PluginAvatar.js @@ -1,8 +1,8 @@ //@flow import React from "react"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Plugin } from "@scm-manager/ui-types"; -import { Image } from "@scm-manager/ui-components"; +import {ExtensionPoint} from "@scm-manager/ui-extensions"; +import type {Plugin} from "@scm-manager/ui-types"; +import {Image} from "@scm-manager/ui-components"; type Props = { plugin: Plugin @@ -14,7 +14,7 @@ export default class PluginAvatar extends React.Component { return (

- Logo + Logo

); diff --git a/scm-ui/src/admin/plugins/components/PluginEntry.js b/scm-ui/src/admin/plugins/components/PluginEntry.js index a8cdaad915..7aaeb3f67f 100644 --- a/scm-ui/src/admin/plugins/components/PluginEntry.js +++ b/scm-ui/src/admin/plugins/components/PluginEntry.js @@ -1,11 +1,21 @@ //@flow import React from "react"; -import type { Plugin } from "@scm-manager/ui-types"; -import { CardColumn } from "@scm-manager/ui-components"; +import injectSheet from "react-jss"; +import type {Plugin} from "@scm-manager/ui-types"; +import {CardColumn} from "@scm-manager/ui-components"; import PluginAvatar from "./PluginAvatar"; type Props = { - plugin: Plugin + plugin: Plugin, + + // context props + classes: any +}; + +const styles = { + link: { + pointerEvents: "cursor" + } }; class PluginEntry extends React.Component { @@ -13,6 +23,17 @@ class PluginEntry extends React.Component { return ; }; + createContentRight = (plugin: Plugin) => { + const { classes } = this.props; + if (plugin._links && plugin._links.install && plugin._links.install.href) { + return ( +
console.log(plugin._links.install.href) /*TODO trigger plugin installation*/}> + +
+ ); + } + }; + createFooterLeft = (plugin: Plugin) => { return {plugin.author}; }; @@ -24,6 +45,7 @@ class PluginEntry extends React.Component { render() { const { plugin } = this.props; const avatar = this.createAvatar(plugin); + const contentRight = this.createContentRight(plugin); const footerLeft = this.createFooterLeft(plugin); const footerRight = this.createFooterRight(plugin); @@ -32,8 +54,9 @@ class PluginEntry extends React.Component { @@ -41,4 +64,4 @@ class PluginEntry extends React.Component { } } -export default PluginEntry; +export default injectSheet(styles)(PluginEntry); diff --git a/scm-ui/src/admin/plugins/components/groupByCategory.js b/scm-ui/src/admin/plugins/components/groupByCategory.js index 1c542d45e3..49b6590d9a 100644 --- a/scm-ui/src/admin/plugins/components/groupByCategory.js +++ b/scm-ui/src/admin/plugins/components/groupByCategory.js @@ -6,7 +6,7 @@ export default function groupByCategory( ): PluginGroup[] { let groups = {}; for (let plugin of plugins) { - const groupName = plugin.type; + const groupName = plugin.category; let group = groups[groupName]; if (!group) { diff --git a/scm-ui/src/admin/plugins/containers/PluginsOverview.js b/scm-ui/src/admin/plugins/containers/PluginsOverview.js index 7a3fc7ec4a..5a8a46f884 100644 --- a/scm-ui/src/admin/plugins/containers/PluginsOverview.js +++ b/scm-ui/src/admin/plugins/containers/PluginsOverview.js @@ -18,7 +18,10 @@ import { isFetchPluginsPending } from "../modules/plugins"; import PluginsList from "../components/PluginsList"; -import { getPluginsLink } from "../../../modules/indexResource"; +import { + getAvailablePluginsLink, + getInstalledPluginsLink +} from "../../../modules/indexResource"; type Props = { loading: boolean, @@ -26,7 +29,8 @@ type Props = { collection: PluginCollection, baseUrl: string, installed: boolean, - pluginsLink: string, + availablePluginsLink: string, + installedPluginsLink: string, // context objects t: string => string, @@ -37,8 +41,27 @@ type Props = { class PluginsOverview extends React.Component { componentDidMount() { - const { fetchPluginsByLink, pluginsLink } = this.props; - fetchPluginsByLink(pluginsLink); + const { + installed, + fetchPluginsByLink, + availablePluginsLink, + installedPluginsLink + } = this.props; + fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink); + } + + componentDidUpdate(prevProps) { + const { + installed, + fetchPluginsByLink, + availablePluginsLink, + installedPluginsLink + } = this.props; + if (prevProps.installed !== installed) { + fetchPluginsByLink( + installed ? installedPluginsLink : availablePluginsLink + ); + } } render() { @@ -81,13 +104,15 @@ const mapStateToProps = state => { const collection = getPluginCollection(state); const loading = isFetchPluginsPending(state); const error = getFetchPluginsFailure(state); - const pluginsLink = getPluginsLink(state); + const availablePluginsLink = getAvailablePluginsLink(state); + const installedPluginsLink = getInstalledPluginsLink(state); return { collection, loading, error, - pluginsLink + availablePluginsLink, + installedPluginsLink }; }; diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js index 1d358ecc87..8c05578b6b 100644 --- a/scm-ui/src/containers/Main.js +++ b/scm-ui/src/containers/Main.js @@ -1,16 +1,16 @@ //@flow import React from "react"; -import { Redirect, Route, Switch, withRouter } from "react-router-dom"; -import type { Links } from "@scm-manager/ui-types"; +import {Redirect, Route, Switch, withRouter} from "react-router-dom"; +import type {Links} from "@scm-manager/ui-types"; import Overview from "../repos/containers/Overview"; import Users from "../users/containers/Users"; import Login from "../containers/Login"; import Logout from "../containers/Logout"; -import { ProtectedRoute } from "@scm-manager/ui-components"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; +import {ProtectedRoute} from "@scm-manager/ui-components"; +import {binder, ExtensionPoint} from "@scm-manager/ui-extensions"; import CreateUser from "../users/containers/CreateUser"; import SingleUser from "../users/containers/SingleUser"; diff --git a/scm-ui/src/modules/indexResource.js b/scm-ui/src/modules/indexResource.js index 9676faffba..d62e6b8b5d 100644 --- a/scm-ui/src/modules/indexResource.js +++ b/scm-ui/src/modules/indexResource.js @@ -116,8 +116,12 @@ export function getUiPluginsLink(state: Object) { return getLink(state, "uiPlugins"); } -export function getPluginsLink(state: Object) { - return getLink(state, "plugins"); +export function getAvailablePluginsLink(state: Object) { + return getLink(state, "availablePlugins"); +} + +export function getInstalledPluginsLink(state: Object) { + return getLink(state, "installedPlugins"); } export function getMeLink(state: Object) { diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 5cb2078e5a..164ddcbc64 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,33 +1,20 @@ //@flow import React from "react"; -import { - fetchRepoByName, - getFetchRepoFailure, - getRepository, - isFetchRepoPending -} from "../modules/repos"; +import {fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; -import { connect } from "react-redux"; -import { Redirect, Route, Switch } from "react-router-dom"; -import type { Repository } from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {Redirect, Route, Switch} from "react-router-dom"; +import type {Repository} from "@scm-manager/ui-types"; -import { - Loading, - Navigation, - SubNavigation, - NavLink, - Page, - Section, - ErrorPage -} from "@scm-manager/ui-components"; -import { translate } from "react-i18next"; +import {ErrorPage, Loading, Navigation, NavLink, Page, Section, SubNavigation} from "@scm-manager/ui-components"; +import {translate} from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; import BranchesOverview from "../branches/containers/BranchesOverview"; import CreateBranch from "../branches/containers/CreateBranch"; import Permissions from "../permissions/containers/Permissions"; -import type { History } from "history"; +import type {History} from "history"; import EditRepoNavLink from "../components/EditRepoNavLink"; import BranchRoot from "../branches/containers/BranchRoot"; import ChangesetsRoot from "./ChangesetsRoot"; @@ -35,8 +22,8 @@ import ChangesetView from "./ChangesetView"; import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; import RepositoryNavLink from "../components/RepositoryNavLink"; -import { getLinks, getRepositoriesLink } from "../../modules/indexResource"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; +import {getLinks, getRepositoriesLink} from "../../modules/indexResource"; +import {binder, ExtensionPoint} from "@scm-manager/ui-extensions"; type Props = { namespace: string, diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index efd92914d3..33211c76f4 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -1,25 +1,20 @@ // @flow import React from "react"; -import { translate } from "react-i18next"; -import type { - RepositoryRole, - PermissionCollection, - PermissionCreateEntry, - SelectValue -} from "@scm-manager/ui-types"; +import {translate} from "react-i18next"; +import type {PermissionCollection, PermissionCreateEntry, RepositoryRole, SelectValue} from "@scm-manager/ui-types"; import { - Subtitle, - SubmitButton, Button, + GroupAutocomplete, LabelWithHelpIcon, Radio, - GroupAutocomplete, + SubmitButton, + Subtitle, UserAutocomplete } from "@scm-manager/ui-components"; import * as validator from "../components/permissionValidation"; import RoleSelector from "../components/RoleSelector"; import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog"; -import { findVerbsForRole } from "../modules/permissions"; +import {findVerbsForRole} from "../modules/permissions"; type Props = { availableRoles: RepositoryRole[], diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index ea467a56da..41cc366ea6 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -1,44 +1,34 @@ //@flow import React from "react"; -import { connect } from "react-redux"; -import { translate } from "react-i18next"; +import {connect} from "react-redux"; +import {translate} from "react-i18next"; import { + createPermission, + createPermissionReset, + deletePermissionReset, fetchAvailablePermissionsIfNeeded, fetchPermissions, - getFetchAvailablePermissionsFailure, getAvailablePermissions, + getAvailableRepositoryRoles, + getAvailableRepositoryVerbs, + getCreatePermissionFailure, + getDeletePermissionsFailure, + getFetchAvailablePermissionsFailure, getFetchPermissionsFailure, - isFetchAvailablePermissionsPending, - isFetchPermissionsPending, + getModifyPermissionsFailure, getPermissionsOfRepo, hasCreatePermission, - createPermission, isCreatePermissionPending, - getCreatePermissionFailure, - createPermissionReset, - getDeletePermissionsFailure, - getModifyPermissionsFailure, - modifyPermissionReset, - deletePermissionReset, - getAvailableRepositoryRoles, - getAvailableRepositoryVerbs + isFetchAvailablePermissionsPending, + isFetchPermissionsPending, + modifyPermissionReset } from "../modules/permissions"; -import { - Loading, - ErrorPage, - Subtitle, - LabelWithHelpIcon -} from "@scm-manager/ui-components"; -import type { - Permission, - PermissionCollection, - PermissionCreateEntry, - RepositoryRole -} from "@scm-manager/ui-types"; +import {ErrorPage, LabelWithHelpIcon, Loading, Subtitle} from "@scm-manager/ui-components"; +import type {Permission, PermissionCollection, PermissionCreateEntry, RepositoryRole} from "@scm-manager/ui-types"; import SinglePermission from "./SinglePermission"; import CreatePermissionForm from "./CreatePermissionForm"; -import type { History } from "history"; -import { getPermissionsLink } from "../../modules/repos"; +import type {History} from "history"; +import {getPermissionsLink} from "../../modules/repos"; import { getGroupAutoCompleteLink, getRepositoryRolesLink, diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 549c970aaf..11533a0526 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,25 +1,20 @@ // @flow import React from "react"; -import { connect } from "react-redux"; -import { withRouter } from "react-router-dom"; -import type { Branch, Repository } from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {withRouter} from "react-router-dom"; +import type {Branch, Repository} from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; -import { - ErrorNotification, - Loading, - BranchSelector, - Breadcrumb -} from "@scm-manager/ui-components"; -import { translate } from "react-i18next"; +import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components"; +import {translate} from "react-i18next"; import { fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending } from "../../branches/modules/branches"; -import { compose } from "redux"; +import {compose} from "redux"; import Content from "./Content"; -import { fetchSources, isDirectory } from "../modules/sources"; +import {fetchSources, isDirectory} from "../modules/sources"; type Props = { repository: Repository, diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index a74d8dc429..73431b780e 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -464,32 +464,28 @@ sonia.scm.maven smp-maven-plugin - - + + sonia.scm.plugins scm-hg-plugin ${project.version} - smp - - + + sonia.scm.plugins scm-svn-plugin ${project.version} - smp - - + + sonia.scm.plugins scm-git-plugin ${project.version} - smp - - + + sonia.scm.plugins scm-legacy-plugin ${project.version} - smp - - + + diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java new file mode 100644 index 0000000000..6d5711133f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java @@ -0,0 +1,108 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.plugin.Plugin; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginPermissions; +import sonia.scm.plugin.PluginState; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + +public class AvailablePluginResource { + + private final PluginDtoCollectionMapper collectionMapper; + private final PluginManager pluginManager; + private final PluginDtoMapper mapper; + + @Inject + public AvailablePluginResource(PluginDtoCollectionMapper collectionMapper, PluginManager pluginManager, PluginDtoMapper mapper) { + this.collectionMapper = collectionMapper; + this.pluginManager = pluginManager; + this.mapper = mapper; + } + + /** + * Returns a collection of available plugins. + * + * @return collection of available plugins. + */ + @GET + @Path("") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(CollectionDto.class) + @Produces(VndMediaType.PLUGIN_COLLECTION) + public Response getAvailablePlugins() { + PluginPermissions.read().check(); + Collection plugins = pluginManager.getAvailable() + .stream() + .filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE)) + .collect(Collectors.toList()); + return Response.ok(collectionMapper.map(plugins)).build(); + } + + /** + * Returns available plugin. + * + * @return available plugin. + */ + @GET + @Path("/{name}/{version}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 404, condition = "not found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(PluginDto.class) + @Produces(VndMediaType.PLUGIN) + public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) { + PluginPermissions.read().check(); + Optional plugin = pluginManager.getAvailable() + .stream() + .filter(p -> p.getId().equals(name + ":" + version)) + .findFirst(); + if (plugin.isPresent()) { + return Response.ok(mapper.map(plugin.get())).build(); + } else { + throw notFound(entity(Plugin.class, name)); + } + } + + /** + * Triggers plugin installation. + * @param name plugin artefact name + * @param version plugin version + * @return HTTP Status. + */ + @POST + @Path("/{name}/{version}/install") + @Consumes(VndMediaType.PLUGIN) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) { + PluginPermissions.manage().check(); + pluginManager.install(name + ":" + version); + return Response.ok().build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index cace57577c..d05596abd5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -51,7 +51,8 @@ public class IndexDtoGenerator extends HalAppenderMapper { link("logout", resourceLinks.authentication().logout()) ); if (PluginPermissions.read().isPermitted()) { - builder.single(link("plugins", resourceLinks.pluginCollection().self())); + builder.single(link("installedPlugins", resourceLinks.installedPluginCollection().self())); + builder.single(link("availablePlugins", resourceLinks.availablePluginCollection().self())); } if (UserPermissions.list().isPermitted()) { builder.single(link("users", resourceLinks.userCollection().self())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java similarity index 79% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java index c3b6ea6020..f10912e5ac 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java @@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.plugin.Plugin; import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginPermissions; import sonia.scm.plugin.PluginWrapper; import sonia.scm.web.VndMediaType; @@ -22,17 +23,19 @@ import java.util.Optional; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; -public class PluginResource { +public class InstalledPluginResource { private final PluginLoader pluginLoader; private final PluginDtoCollectionMapper collectionMapper; private final PluginDtoMapper mapper; + private final PluginManager pluginManager; @Inject - public PluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) { + public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) { this.pluginLoader = pluginLoader; this.collectionMapper = collectionMapper; this.mapper = mapper; + this.pluginManager = pluginManager; } /** @@ -57,12 +60,12 @@ public class PluginResource { /** * Returns the installed plugin with the given id. * - * @param id id of plugin + * @param name name of plugin * * @return installed plugin with specified id */ @GET - @Path("{id}") + @Path("/{name}") @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 404, condition = "not found"), @@ -70,18 +73,17 @@ public class PluginResource { }) @TypeHint(PluginDto.class) @Produces(VndMediaType.PLUGIN) - public Response getInstalledPlugin(@PathParam("id") String id) { + public Response getInstalledPlugin(@PathParam("name") String name) { PluginPermissions.read().check(); Optional pluginDto = pluginLoader.getInstalledPlugins() .stream() - .filter(plugin -> id.equals(plugin.getPlugin().getInformation().getId(false))) + .filter(plugin -> name.equals(plugin.getPlugin().getInformation().getName())) .map(mapper::map) .findFirst(); if (pluginDto.isPresent()) { return Response.ok(pluginDto.get()).build(); } else { - throw notFound(entity(Plugin.class, id)); + throw notFound(entity(Plugin.class, name)); } } - } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index cf09eeb128..0b419cf542 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -54,5 +54,7 @@ public class MapperModule extends AbstractModule { bind(UIPluginDtoCollectionMapper.class); bind(ScmPathInfoStore.class).in(ServletScopes.REQUEST); + + bind(PluginDtoMapper.class).to(Mappers.getMapper(PluginDtoMapper.class).getClass()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java index d119eca711..b096266537 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; - import lombok.NoArgsConstructor; import lombok.Setter; @@ -13,10 +12,12 @@ import lombok.Setter; public class PluginDto extends HalRepresentation { private String name; - private String type; private String version; - private String author; + private String displayName; private String description; + private String author; + private String category; + private String avatarUrl; public PluginDto(Links links) { add(links); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java index 72178e94f3..5d8746c211 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java @@ -4,6 +4,7 @@ import com.google.inject.Inject; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; +import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginWrapper; import java.util.Collection; @@ -24,13 +25,26 @@ public class PluginDtoCollectionMapper { this.mapper = mapper; } - public HalRepresentation map(Collection plugins) { + public HalRepresentation map(List plugins) { List dtos = plugins.stream().map(mapper::map).collect(toList()); - return new HalRepresentation(createLinks(), embedDtos(dtos)); + return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos)); } - private Links createLinks() { - String baseUrl = resourceLinks.pluginCollection().self(); + public HalRepresentation map(Collection plugins) { + List dtos = plugins.stream().map(mapper::map).collect(toList()); + return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos)); + } + + private Links createInstalledPluginsLinks() { + String baseUrl = resourceLinks.installedPluginCollection().self(); + + Links.Builder linksBuilder = linkingTo() + .with(Links.linkingTo().self(baseUrl).build()); + return linksBuilder.build(); + } + + private Links createAvailablePluginsLinks() { + String baseUrl = resourceLinks.availablePluginCollection().self(); Links.Builder linksBuilder = linkingTo() .with(Links.linkingTo().self(baseUrl).build()); 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 d17ecdae70..ca81edd7ff 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 @@ -1,32 +1,54 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginState; import sonia.scm.plugin.PluginWrapper; + import javax.inject.Inject; +import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; -public class PluginDtoMapper { - - private final ResourceLinks resourceLinks; +@Mapper +public abstract class PluginDtoMapper { @Inject - public PluginDtoMapper(ResourceLinks resourceLinks) { - this.resourceLinks = resourceLinks; - } + private ResourceLinks resourceLinks; public PluginDto map(PluginWrapper plugin) { - Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.plugin() - .self(plugin.getPlugin().getInformation().getId(false))); + return map(plugin.getPlugin().getInformation()); + } - PluginDto pluginDto = new PluginDto(linksBuilder.build()); - pluginDto.setName(plugin.getPlugin().getInformation().getName()); - pluginDto.setType(plugin.getPlugin().getInformation().getCategory() != null ? plugin.getPlugin().getInformation().getCategory() : "Miscellaneous"); - pluginDto.setVersion(plugin.getPlugin().getInformation().getVersion()); - pluginDto.setAuthor(plugin.getPlugin().getInformation().getAuthor()); - pluginDto.setDescription(plugin.getPlugin().getInformation().getDescription()); + public abstract PluginDto map(PluginInformation plugin); - return pluginDto; + @AfterMapping + protected void appendCategory(@MappingTarget PluginDto dto) { + if (dto.getCategory() == null) { + dto.setCategory("Miscellaneous"); + } + } + + @ObjectFactory + public PluginDto createDto(PluginInformation pluginInformation) { + Links.Builder linksBuilder; + if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) { + linksBuilder = linkingTo() + .self(resourceLinks.availablePlugin() + .self(pluginInformation.getName(), pluginInformation.getVersion())); + + linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion()))); + } + else { + linksBuilder = linkingTo() + .self(resourceLinks.installedPlugin() + .self(pluginInformation.getName())); + } + + return new PluginDto(linksBuilder.build()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java index e9b0f0a997..79c46369a3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java @@ -4,18 +4,23 @@ import javax.inject.Inject; import javax.inject.Provider; import javax.ws.rs.Path; -@Path("v2/") +@Path("v2/plugins") public class PluginRootResource { - private Provider pluginResourceProvider; + private Provider installedPluginResourceProvider; + private Provider availablePluginResourceProvider; @Inject - public PluginRootResource(Provider pluginResourceProvider) { - this.pluginResourceProvider = pluginResourceProvider; + public PluginRootResource(Provider installedPluginResourceProvider, Provider availablePluginResourceProvider) { + this.installedPluginResourceProvider = installedPluginResourceProvider; + this.availablePluginResourceProvider = availablePluginResourceProvider; } - @Path("plugins") - public PluginResource plugins() { - return pluginResourceProvider.get(); + @Path("/installed") + public InstalledPluginResource installedPlugins() { + return installedPluginResourceProvider.get(); } + + @Path("/available") + public AvailablePluginResource availablePlugins() { return availablePluginResourceProvider.get(); } } 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 1d06659649..268f5f8619 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 @@ -651,35 +651,71 @@ class ResourceLinks { } } - public PluginLinks plugin() { - return new PluginLinks(scmPathInfoStore.get()); + public InstalledPluginLinks installedPlugin() { + return new InstalledPluginLinks(scmPathInfoStore.get()); } - static class PluginLinks { - private final LinkBuilder pluginLinkBuilder; + static class InstalledPluginLinks { + private final LinkBuilder installedPluginLinkBuilder; - PluginLinks(ScmPathInfo pathInfo) { - pluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class); + InstalledPluginLinks(ScmPathInfo pathInfo) { + installedPluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class); } String self(String id) { - return pluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href(); + return installedPluginLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugin").parameters(id).href(); } } - public PluginCollectionLinks pluginCollection() { - return new PluginCollectionLinks(scmPathInfoStore.get()); + public InstalledPluginCollectionLinks installedPluginCollection() { + return new InstalledPluginCollectionLinks(scmPathInfoStore.get()); } - static class PluginCollectionLinks { - private final LinkBuilder pluginCollectionLinkBuilder; + static class InstalledPluginCollectionLinks { + private final LinkBuilder installedPluginCollectionLinkBuilder; - PluginCollectionLinks(ScmPathInfo pathInfo) { - pluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class); + InstalledPluginCollectionLinks(ScmPathInfo pathInfo) { + installedPluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class); } String self() { - return pluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href(); + return installedPluginCollectionLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugins").parameters().href(); + } + } + + public AvailablePluginLinks availablePlugin() { + return new AvailablePluginLinks(scmPathInfoStore.get()); + } + + static class AvailablePluginLinks { + private final LinkBuilder availablePluginLinkBuilder; + + AvailablePluginLinks(ScmPathInfo pathInfo) { + availablePluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class); + } + + String self(String name, String version) { + return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name, version).href(); + } + + String install(String name, String version) { + return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href(); + } + } + + public AvailablePluginCollectionLinks availablePluginCollection() { + return new AvailablePluginCollectionLinks(scmPathInfoStore.get()); + } + + static class AvailablePluginCollectionLinks { + private final LinkBuilder availablePluginCollectionLinkBuilder; + + AvailablePluginCollectionLinks(ScmPathInfo pathInfo) { + availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class); + } + + String self() { + return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href(); } } 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 ed1f691988..b718a43a81 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -78,6 +78,8 @@ import javax.xml.bind.JAXB; import sonia.scm.net.ahc.AdvancedHttpClient; +import static sonia.scm.plugin.PluginCenterDtoMapper.*; + /** * TODO replace aether stuff. * TODO check AdvancedPluginConfiguration from 1.x @@ -99,7 +101,7 @@ public class DefaultPluginManager implements PluginManager LoggerFactory.getLogger(DefaultPluginManager.class); /** enable or disable remote plugins */ - private static final boolean REMOTE_PLUGINS_ENABLED = false; + private static final boolean REMOTE_PLUGINS_ENABLED = true; /** Field description */ public static final Predicate FILTER_UPDATES = @@ -181,8 +183,6 @@ public class DefaultPluginManager implements PluginManager PluginCenter center = getPluginCenter(); - // pluginHandler.install(id); - for (PluginInformation plugin : center.getPlugins()) { String pluginId = plugin.getId(); @@ -309,14 +309,12 @@ public class DefaultPluginManager implements PluginManager PluginPermissions.manage().check(); String[] idParts = id.split(":"); - String groupId = idParts[0]; - String artefactId = idParts[1]; + String name = idParts[0]; PluginInformation installed = null; for (PluginInformation info : getInstalled()) { - if (groupId.equals(info.getGroupId()) - && artefactId.equals(info.getArtifactId())) + if (name.equals(info.getName())) { installed = info; @@ -326,9 +324,9 @@ public class DefaultPluginManager implements PluginManager if (installed == null) { - StringBuilder msg = new StringBuilder(groupId); + StringBuilder msg = new StringBuilder(name); - msg.append(":").append(groupId).append(" is not install"); + msg.append(" is not install"); throw new PluginNotInstalledException(msg.toString()); } @@ -423,7 +421,7 @@ public class DefaultPluginManager implements PluginManager for (PluginInformation info : centerPlugins) { - if (!installedPlugins.containsKey(info.getId())) + if (!installedPlugins.containsKey(info.getName())) { availablePlugins.add(info); } @@ -596,48 +594,28 @@ public class DefaultPluginManager implements PluginManager { synchronized (DefaultPluginManager.class) { - String pluginUrl = configuration.getPluginUrl(); + String pluginUrl = buildPluginUrl(configuration.getPluginUrl()); + logger.info("fetch plugin information from {}", pluginUrl); - pluginUrl = buildPluginUrl(pluginUrl); - - if (logger.isInfoEnabled()) - { - logger.info("fetch plugin informations from {}", pluginUrl); - } - - /** - * remote plugins are disabled for early 2.0.0-SNAPSHOTS - * TODO enable remote plugins later - */ if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl)) { try { - center = httpClient.get(pluginUrl).request().contentFromXml(PluginCenter.class); + center = new PluginCenter(); + PluginCenterDto pluginCenterDto = httpClient.get(pluginUrl).request().contentFromJson(PluginCenterDto.class); + Set pluginInformationSet = map(pluginCenterDto.getEmbedded().getPlugins()); + center.setPlugins(pluginInformationSet); preparePlugins(center); cache.put(PluginCenter.class.getName(), center); - - /* - * if (pluginHandler == null) - * { - * pluginHandler = new AetherPluginHandler(this, - * SCMContext.getContext(), configuration, - * advancedPluginConfiguration); - * } - * - * pluginHandler.setPluginRepositories(center.getRepositories()); - */ } catch (IOException ex) { logger.error("could not load plugins from plugin center", ex); } } - - if (center == null) - { - center = new PluginCenter(); - } + } + if(center == null) { + center = new PluginCenter(); } } @@ -719,8 +697,7 @@ public class DefaultPluginManager implements PluginManager */ private boolean isSamePlugin(PluginInformation p1, PluginInformation p2) { - return p1.getGroupId().equals(p2.getGroupId()) - && p1.getArtifactId().equals(p2.getArtifactId()); + return p1.getName().equals(p2.getName()); } //~--- fields --------------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java index d1fe214f50..372470df14 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java @@ -115,8 +115,8 @@ public final class ExplodedSmp implements Comparable } else { - String id = plugin.getInformation().getId(false); - String oid = o.plugin.getInformation().getId(false); + String id = plugin.getInformation().getName(false); + String oid = o.plugin.getInformation().getName(false); if (depends.contains(oid) && odepends.contains(id)) { diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java new file mode 100644 index 0000000000..8bb48c8ceb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java @@ -0,0 +1,90 @@ +package sonia.scm.plugin; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public final class PluginCenterDto implements Serializable { + + @XmlElement(name = "_embedded") + private Embedded embedded; + + public Embedded getEmbedded() { + return embedded; + } + + @XmlRootElement(name = "_embedded") + @XmlAccessorType(XmlAccessType.FIELD) + public static class Embedded { + + @XmlElement(name = "plugins") + private List plugins; + + public List getPlugins() { + if (plugins == null) { + plugins = ImmutableList.of(); + } + return plugins; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "plugins") + @Getter + @AllArgsConstructor + public static class Plugin { + + private String name; + private String version; + private String displayName; + private String description; + private String category; + private String author; + private String avatarUrl; + private String sha256; + + @XmlElement(name = "conditions") + private Condition conditions; + + @XmlElement(name = "dependecies") + private Dependency dependencies; + + @XmlElement(name = "_links") + private Map links; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "conditions") + @Getter + @AllArgsConstructor + public static class Condition { + + private List os; + private String arch; + private String minVersion; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "dependencies") + @Getter + @AllArgsConstructor + static class Dependency { + private String name; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @Getter + static class Link { + private String href; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java new file mode 100644 index 0000000000..ea445b3ede --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java @@ -0,0 +1,27 @@ +package sonia.scm.plugin; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Mapper +public interface PluginCenterDtoMapper { + + @Mapping(source = "conditions", target = "condition") + PluginInformation map(PluginCenterDto.Plugin plugin); + + PluginCondition map(PluginCenterDto.Condition condition); + + static Set map(List dtos) { + PluginCenterDtoMapper mapper = Mappers.getMapper(PluginCenterDtoMapper.class); + Set plugins = new HashSet<>(); + for (PluginCenterDto.Plugin plugin : dtos) { + plugins.add(mapper.map(plugin)); + } + return plugins; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java index e28ccff2ff..8bc4f47658 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java @@ -126,7 +126,7 @@ public final class PluginNode */ public String getId() { - return plugin.getPlugin().getInformation().getId(false); + return plugin.getPlugin().getInformation().getName(false); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java index c7d669ee63..b91ee9b1ee 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -318,10 +318,7 @@ public final class PluginProcessor { for (Path parent : parentStream) { - try (DirectoryStream direcotries = stream(parent, filter)) - { - paths.addAll(direcotries); - } + paths.add(parent); } } @@ -333,7 +330,6 @@ public final class PluginProcessor * * * @param parentClassLoader - * @param directory * @param smp * * @return @@ -377,7 +373,7 @@ public final class PluginProcessor URL[] urlArray = urls.toArray(new URL[urls.size()]); Plugin plugin = smp.getPlugin(); - String id = plugin.getInformation().getId(false); + String id = plugin.getInformation().getName(false); if (smp.getPlugin().isChildFirstClassLoader()) { @@ -472,7 +468,6 @@ public final class PluginProcessor * * * @param classLoader - * @param directory * @param smp * * @return @@ -511,7 +506,6 @@ public final class PluginProcessor * * * @param classLoader - * @param smps * @param rootNodes * * @return diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java index 52d192da32..0354ded11a 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java @@ -109,7 +109,7 @@ public final class PluginsInternal { PluginInformation info = plugin.getInformation(); - return new File(new File(parent, info.getGroupId()), info.getArtifactId()); + return new File(parent, info.getName()); } /** @@ -131,14 +131,14 @@ public final class PluginsInternal if (directory.exists()) { logger.debug("delete directory {} for plugin extraction", - archive.getPlugin().getInformation().getId(false)); + archive.getPlugin().getInformation().getName(false)); IOUtil.delete(directory); } IOUtil.mkdirs(directory); logger.debug("extract plugin {}", - archive.getPlugin().getInformation().getId(false)); + archive.getPlugin().getInformation().getName(false)); archive.extract(directory); Files.write(checksum, checksumFile, Charsets.UTF_8); diff --git a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java index e913cead25..173dcb0638 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java @@ -7,10 +7,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.lifecycle.RestartEvent; import sonia.scm.event.ScmEventBus; -import sonia.scm.update.repository.MigrationStrategy; +import sonia.scm.lifecycle.RestartEvent; import sonia.scm.update.repository.DefaultMigrationStrategyDAO; +import sonia.scm.update.repository.MigrationStrategy; import sonia.scm.update.repository.V1Repository; import sonia.scm.update.repository.XmlRepositoryV1UpdateStep; import sonia.scm.util.ValidationUtil; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java new file mode 100644 index 0000000000..57564999ef --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java @@ -0,0 +1,182 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +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.jboss.resteasy.spi.UnhandledException; +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; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginState; +import sonia.scm.web.VndMediaType; + +import javax.inject.Provider; +import javax.servlet.http.HttpServletResponse; + +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AvailablePluginResourceTest { + + private Dispatcher dispatcher; + + @Mock + Provider installedPluginResourceProvider; + + @Mock + Provider availablePluginResourceProvider; + + @Mock + private PluginDtoCollectionMapper collectionMapper; + + @Mock + private PluginManager pluginManager; + + @Mock + private PluginDtoMapper mapper; + + @InjectMocks + AvailablePluginResource availablePluginResource; + + PluginRootResource pluginRootResource; + + private final Subject subject = mock(Subject.class); + + + @BeforeEach + void prepareEnvironment() { + dispatcher = MockDispatcherFactory.createDispatcher(); + pluginRootResource = new PluginRootResource(installedPluginResourceProvider, availablePluginResourceProvider); + when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource); + dispatcher.getRegistry().addSingletonResource(pluginRootResource); + } + + @Nested + class withAuthorization { + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @AfterEach + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void getAvailablePlugins() throws URISyntaxException, UnsupportedEncodingException { + PluginInformation pluginInformation = new PluginInformation(); + pluginInformation.setState(PluginState.AVAILABLE); + when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(pluginInformation)); + when(collectionMapper.map(Collections.singletonList(pluginInformation))).thenReturn(new MockedResultDto()); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available"); + request.accept(VndMediaType.PLUGIN_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); + assertThat(response.getContentAsString()).contains("\"marker\":\"x\""); + } + + @Test + void getAvailablePlugin() throws UnsupportedEncodingException, URISyntaxException { + PluginInformation pluginInformation = new PluginInformation(); + pluginInformation.setState(PluginState.AVAILABLE); + pluginInformation.setName("pluginName"); + pluginInformation.setVersion("2.0.0"); + when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(pluginInformation)); + + PluginDto pluginDto = new PluginDto(); + pluginDto.setName("pluginName"); + when(mapper.map(pluginInformation)).thenReturn(pluginDto); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); + assertThat(response.getContentAsString()).contains("\"name\":\"pluginName\""); + } + + @Test + void installPlugin() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + verify(pluginManager).install("pluginName:2.0.0"); + assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); + } + } + + @Nested + class WithoutAuthorization { + + @BeforeEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldNotGetAvailablePluginsIfMissingPermission() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available"); + request.accept(VndMediaType.PLUGIN_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + + assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + } + + @Test + void shouldNotGetAvailablePluginIfMissingPermission() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + } + + @Test + void shouldNotInstallPluginIfMissingPermission() throws URISyntaxException { + ThreadContext.unbindSubject(); + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + } + } + + public class MockedResultDto extends HalRepresentation { + public String getMarker() { + return "x"; + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java new file mode 100644 index 0000000000..a81eadadb8 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java @@ -0,0 +1,160 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +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.jboss.resteasy.spi.UnhandledException; +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; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.plugin.Plugin; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.PluginState; +import sonia.scm.plugin.PluginWrapper; +import sonia.scm.web.VndMediaType; + +import javax.inject.Provider; +import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class InstalledPluginResourceTest { + + private Dispatcher dispatcher; + + @Mock + Provider installedPluginResourceProvider; + + @Mock + Provider availablePluginResourceProvider; + + @Mock + private PluginLoader pluginLoader; + + @Mock + private PluginDtoCollectionMapper collectionMapper; + + @Mock + private PluginDtoMapper mapper; + + @InjectMocks + InstalledPluginResource installedPluginResource; + + PluginRootResource pluginRootResource; + + private final Subject subject = mock(Subject.class); + + @BeforeEach + void prepareEnvironment() { + dispatcher = MockDispatcherFactory.createDispatcher(); + pluginRootResource = new PluginRootResource(installedPluginResourceProvider, availablePluginResourceProvider); + when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource); + dispatcher.getRegistry().addSingletonResource(pluginRootResource); + } + + @Nested + class withAuthorization { + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @AfterEach + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void getInstalledPlugins() throws URISyntaxException, UnsupportedEncodingException { + PluginWrapper pluginWrapper = new PluginWrapper(null, null, null, null); + when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(pluginWrapper)); + when(collectionMapper.map(Collections.singletonList(pluginWrapper))).thenReturn(new MockedResultDto()); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed"); + request.accept(VndMediaType.PLUGIN_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); + assertThat(response.getContentAsString()).contains("\"marker\":\"x\""); + } + + @Test + void getInstalledPlugin() throws UnsupportedEncodingException, URISyntaxException { + PluginInformation pluginInformation = new PluginInformation(); + pluginInformation.setVersion("2.0.0"); + pluginInformation.setName("pluginName"); + pluginInformation.setState(PluginState.INSTALLED); + Plugin plugin = new Plugin(2, pluginInformation, null, null, false, null); + PluginWrapper pluginWrapper = new PluginWrapper(plugin, null, null, null); + when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(pluginWrapper)); + + PluginDto pluginDto = new PluginDto(); + pluginDto.setName("pluginName"); + when(mapper.map(pluginWrapper)).thenReturn(pluginDto); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed/pluginName"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); + assertThat(response.getContentAsString()).contains("\"name\":\"pluginName\""); + } + } + + @Nested + class WithoutAuthorization { + + @BeforeEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldNotGetInstalledPluginsIfMissingPermission() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed"); + request.accept(VndMediaType.PLUGIN_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + + assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + } + + @Test + void shouldNotGetInstalledPluginIfMissingPermission() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed/pluginName"); + request.accept(VndMediaType.PLUGIN); + MockHttpResponse response = new MockHttpResponse(); + + assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + } + } + + public class MockedResultDto extends HalRepresentation { + public String getMarker() { + return "x"; + } + } +} 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 new file mode 100644 index 0000000000..97b46603d3 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java @@ -0,0 +1,88 @@ +package sonia.scm.api.v2.resources; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginState; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class PluginDtoMapperTest { + + @SuppressWarnings("unused") // Is injected + private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("https://hitchhiker.com/")); + + @InjectMocks + private PluginDtoMapperImpl mapper; + + @Test + void shouldMapInformation() { + PluginInformation information = createPluginInformation(); + + PluginDto dto = mapper.map(information); + + assertThat(dto.getName()).isEqualTo("scm-cas-plugin"); + assertThat(dto.getVersion()).isEqualTo("1.0.0"); + assertThat(dto.getDisplayName()).isEqualTo("CAS"); + assertThat(dto.getAuthor()).isEqualTo("Sebastian Sdorra"); + assertThat(dto.getCategory()).isEqualTo("Authentication"); + assertThat(dto.getAvatarUrl()).isEqualTo("https://avatar.scm-manager.org/plugins/cas.png"); + } + + private PluginInformation createPluginInformation() { + PluginInformation information = new PluginInformation(); + information.setName("scm-cas-plugin"); + information.setVersion("1.0.0"); + information.setDisplayName("CAS"); + information.setAuthor("Sebastian Sdorra"); + information.setCategory("Authentication"); + information.setAvatarUrl("https://avatar.scm-manager.org/plugins/cas.png"); + return information; + } + + @Test + void shouldAppendInstalledSelfLink() { + PluginInformation information = createPluginInformation(); + information.setState(PluginState.INSTALLED); + + PluginDto dto = mapper.map(information); + assertThat(dto.getLinks().getLinkBy("self").get().getHref()) + .isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin"); + } + + @Test + void shouldAppendAvailableSelfLink() { + PluginInformation information = createPluginInformation(); + information.setState(PluginState.AVAILABLE); + + PluginDto dto = mapper.map(information); + assertThat(dto.getLinks().getLinkBy("self").get().getHref()) + .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0"); + } + + @Test + void shouldAppendInstallLink() { + PluginInformation information = createPluginInformation(); + information.setState(PluginState.AVAILABLE); + + PluginDto dto = mapper.map(information); + assertThat(dto.getLinks().getLinkBy("install").get().getHref()) + .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0/install"); + } + + @Test + void shouldReturnMiscellaneousIfCategoryIsNull() { + PluginInformation information = createPluginInformation(); + information.setCategory(null); + + PluginDto dto = mapper.map(information); + assertThat(dto.getCategory()).isEqualTo("Miscellaneous"); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 83a0f073dd..1aef4e57cb 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -36,6 +36,10 @@ public class ResourceLinksMock { when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo)); when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo)); when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); + when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(uriInfo)); + when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(uriInfo)); + when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(uriInfo)); + when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(uriInfo)); when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo)); when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo)); when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo)); @@ -46,7 +50,6 @@ public class ResourceLinksMock { when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo)); when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(uriInfo)); when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo)); - when(resourceLinks.pluginCollection()).thenReturn(new ResourceLinks.PluginCollectionLinks(uriInfo)); return resourceLinks; } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java index b7bde65677..601725d938 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java @@ -60,12 +60,12 @@ public class ExplodedSmpTest @Test public void testCompareTo() { - ExplodedSmp e1 = create("a", "c", "1", "a:a"); + ExplodedSmp e1 = create("a", "c", "1", "a"); ExplodedSmp e3 = create("a", "a", "1"); ExplodedSmp e2 = create("a", "b", "1"); List es = list(e1, e2, e3); - is(es, 2, "c"); + is(es, 2, "a"); } /** @@ -75,9 +75,9 @@ public class ExplodedSmpTest @Test(expected = PluginCircularDependencyException.class) public void testCompareToCyclicDependency() { - ExplodedSmp e1 = create("a", "a", "1", "a:c"); - ExplodedSmp e2 = create("a", "b", "1"); - ExplodedSmp e3 = create("a", "c", "1", "a:a"); + ExplodedSmp e1 = create("a", "1", "c"); + ExplodedSmp e2 = create("b", "1"); + ExplodedSmp e3 = create("c", "1", "a"); list(e1, e2, e3); } @@ -89,9 +89,9 @@ public class ExplodedSmpTest @Test public void testCompareToTransitiveDependencies() { - ExplodedSmp e1 = create("a", "a", "1", "a:b"); - ExplodedSmp e2 = create("a", "b", "1"); - ExplodedSmp e3 = create("a", "c", "1", "a:a"); + ExplodedSmp e1 = create("a", "1", "b"); + ExplodedSmp e2 = create("b", "1"); + ExplodedSmp e3 = create("c", "1", "a"); List es = list(e1, e2, e3); @@ -107,9 +107,9 @@ public class ExplodedSmpTest @Test public void testMultipleDependencies() { - ExplodedSmp e1 = create("a", "a", "1", "a:b", "a:c"); - ExplodedSmp e2 = create("a", "b", "1", "a:c"); - ExplodedSmp e3 = create("a", "c", "1"); + ExplodedSmp e1 = create("a", "1", "b", "c"); + ExplodedSmp e2 = create("b", "1", "c"); + ExplodedSmp e3 = create("c", "1"); List es = list(e1, e2, e3); is(es, 2, "a"); @@ -119,20 +119,18 @@ public class ExplodedSmpTest * Method description * * - * @param groupId - * @param artifactId + * @param name * @param version * @param dependencies * * @return */ - private ExplodedSmp create(String groupId, String artifactId, String version, + private ExplodedSmp create(String name, String version, String... dependencies) { PluginInformation info = new PluginInformation(); - info.setGroupId(groupId); - info.setArtifactId(artifactId); + info.setName(name); info.setVersion(version); Plugin plugin = new Plugin(2, info, null, null, false, @@ -170,6 +168,6 @@ public class ExplodedSmpTest */ private void is(List es, int p, String a) { - assertEquals(a, es.get(p).getPlugin().getInformation().getArtifactId()); + assertEquals(a, es.get(p).getPlugin().getInformation().getName()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java new file mode 100644 index 0000000000..66a90255b3 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java @@ -0,0 +1,86 @@ +package sonia.scm.plugin; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static sonia.scm.plugin.PluginCenterDto.Plugin; +import static sonia.scm.plugin.PluginCenterDto.*; + +class PluginCenterDtoMapperTest { + + @Test + void shouldMapSinglePlugin() { + Plugin plugin = new Plugin( + "scm-hitchhiker-plugin", + "SCM Hitchhiker Plugin", + "plugin for hitchhikers", + "Travel", + "2.0.0", + "trillian", + "http://avatar.url", + "555000444", + new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), + new Dependency("scm-review-plugin"), + new HashMap<>()); + + PluginInformation result = PluginCenterDtoMapper.map(Collections.singletonList(plugin)).iterator().next(); + + assertThat(result.getAuthor()).isEqualTo(plugin.getAuthor()); + assertThat(result.getCategory()).isEqualTo(plugin.getCategory()); + assertThat(result.getVersion()).isEqualTo(plugin.getVersion()); + assertThat(result.getCondition().getArch()).isEqualTo(plugin.getConditions().getArch()); + assertThat(result.getCondition().getMinVersion()).isEqualTo(plugin.getConditions().getMinVersion()); + assertThat(result.getCondition().getOs().iterator().next()).isEqualTo(plugin.getConditions().getOs().iterator().next()); + assertThat(result.getDescription()).isEqualTo(plugin.getDescription()); + assertThat(result.getName()).isEqualTo(plugin.getName()); + } + + @Test + void shouldMapMultiplePlugins() { + Plugin plugin1 = new Plugin( + "scm-review-plugin", + "SCM Hitchhiker Plugin", + "plugin for hitchhikers", + "Travel", + "2.1.0", + "trillian", + "https://avatar.url", + "12345678aa", + new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), + new Dependency("scm-review-plugin"), + new HashMap<>()); + + Plugin plugin2 = new Plugin( + "scm-hitchhiker-plugin", + "SCM Hitchhiker Plugin", + "plugin for hitchhikers", + "Travel", + "2.0.0", + "dent", + "http://avatar.url", + "555000444", + new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), + new Dependency("scm-review-plugin"), + new HashMap<>()); + + Set resultSet = PluginCenterDtoMapper.map(Arrays.asList(plugin1, plugin2)); + + List pluginsList = new ArrayList<>(resultSet); + + PluginInformation pluginInformation1 = pluginsList.get(1); + PluginInformation pluginInformation2 = pluginsList.get(0); + + assertThat(pluginInformation1.getAuthor()).isEqualTo(plugin1.getAuthor()); + assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion()); + assertThat(pluginInformation2.getAuthor()).isEqualTo(plugin2.getAuthor()); + assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion()); + assertThat(resultSet.size()).isEqualTo(2); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java index 8b352b8e68..87e9cbf7b7 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java @@ -71,37 +71,37 @@ public class PluginProcessorTest /** Field description */ private static final PluginResource PLUGIN_A = new PluginResource("sonia/scm/plugin/scm-a-plugin.smp", "scm-a-plugin.smp", - "sonia.scm.plugins:scm-a-plugin:1.0.0-SNAPSHOT"); + "scm-a-plugin:1.0.0-SNAPSHOT"); /** Field description */ private static final PluginResource PLUGIN_B = new PluginResource("sonia/scm/plugin/scm-b-plugin.smp", "scm-b-plugin.smp", - "sonia.scm.plugins:scm-b-plugin:1.0.0-SNAPSHOT"); + "scm-b-plugin:1.0.0-SNAPSHOT"); /** Field description */ private static final PluginResource PLUGIN_C = new PluginResource("sonia/scm/plugin/scm-c-plugin.smp", "scm-c-plugin.smp", - "sonia.scm.plugins:scm-c-plugin:1.0.0-SNAPSHOT"); + "scm-c-plugin:1.0.0-SNAPSHOT"); /** Field description */ private static final PluginResource PLUGIN_D = new PluginResource("sonia/scm/plugin/scm-d-plugin.smp", "scm-d-plugin.smp", - "sonia.scm.plugins:scm-d-plugin:1.0.0-SNAPSHOT"); + "scm-d-plugin:1.0.0-SNAPSHOT"); /** Field description */ private static final PluginResource PLUGIN_E = new PluginResource("sonia/scm/plugin/scm-e-plugin.smp", "scm-e-plugin.smp", - "sonia.scm.plugins:scm-e-plugin:1.0.0-SNAPSHOT"); + "scm-e-plugin:1.0.0-SNAPSHOT"); /** Field description */ private static final PluginResource PLUGIN_F_1_0_0 = new PluginResource("sonia/scm/plugin/scm-f-plugin-1.0.0.smp", - "scm-f-plugin.smp", "sonia.scm.plugins:scm-f-plugin:1.0.0"); + "scm-f-plugin.smp", "scm-f-plugin:1.0.0"); /** Field description */ private static final PluginResource PLUGIN_F_1_0_1 = new PluginResource("sonia/scm/plugin/scm-f-plugin-1.0.1.smp", - "scm-f-plugin.smp", "sonia.scm.plugins:scm-f-plugin:1.0.1"); + "scm-f-plugin.smp", "scm-f-plugin:1.0.1"); //~--- methods -------------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java index 06d6c1732c..0115f4510e 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java @@ -71,7 +71,7 @@ public class PluginTreeTest { PluginCondition condition = new PluginCondition("999", new ArrayList(), "hit"); - Plugin plugin = new Plugin(2, createInfo("a", "b", "1"), null, condition, + Plugin plugin = new Plugin(2, createInfo("a", "1"), null, condition, false, null); ExplodedSmp smp = createSmp(plugin); @@ -102,7 +102,7 @@ public class PluginTreeTest List smps = createSmps("a", "b", "c"); List nodes = unwrapIds(new PluginTree(smps).getRootNodes()); - assertThat(nodes, containsInAnyOrder("a:a", "b:b", "c:c")); + assertThat(nodes, containsInAnyOrder("a", "b", "c")); } /** @@ -114,7 +114,7 @@ public class PluginTreeTest @Test(expected = PluginException.class) public void testScmVersion() throws IOException { - Plugin plugin = new Plugin(1, createInfo("a", "b", "1"), null, null, false, + Plugin plugin = new Plugin(1, createInfo("a", "1"), null, null, false, null); ExplodedSmp smp = createSmp(plugin); @@ -141,34 +141,32 @@ public class PluginTreeTest PluginTree tree = new PluginTree(smps); List rootNodes = tree.getRootNodes(); - assertThat(unwrapIds(rootNodes), containsInAnyOrder("a:a")); + assertThat(unwrapIds(rootNodes), containsInAnyOrder("a")); PluginNode a = rootNodes.get(0); - assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b:b", "c:c")); + assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b", "c")); - PluginNode b = a.getChild("b:b"); + PluginNode b = a.getChild("b"); - assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c:c")); + assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c")); } /** * Method description * * - * @param groupId - * @param artifactId + * @param name * @param version * * @return */ - private PluginInformation createInfo(String groupId, String artifactId, + private PluginInformation createInfo(String name, String version) { PluginInformation info = new PluginInformation(); - info.setGroupId(groupId); - info.setArtifactId(artifactId); + info.setName(name); info.setVersion(version); return info; @@ -201,7 +199,7 @@ public class PluginTreeTest */ private ExplodedSmp createSmp(String name) throws IOException { - return createSmp(new Plugin(2, createInfo(name, name, "1.0.0"), null, null, + return createSmp(new Plugin(2, createInfo(name, "1.0.0"), null, null, false, null)); } @@ -224,10 +222,10 @@ public class PluginTreeTest for (String d : dependencies) { - dependencySet.add(d.concat(":").concat(d)); + dependencySet.add(d); } - Plugin plugin = new Plugin(2, createInfo(name, name, "1"), null, null, + Plugin plugin = new Plugin(2, createInfo(name, "1"), null, null, false, dependencySet); return createSmp(plugin); diff --git a/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java b/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java index 9dbe00059e..683c446af7 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.update.repository.MigrationStrategy; import sonia.scm.update.repository.DefaultMigrationStrategyDAO; +import sonia.scm.update.repository.MigrationStrategy; import sonia.scm.update.repository.V1Repository; import sonia.scm.update.repository.XmlRepositoryV1UpdateStep; diff --git a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java index 73c7fe6aca..6ab32f3397 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java @@ -11,10 +11,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.SCMContextProvider; import sonia.scm.security.AssignedPermission; import sonia.scm.store.ConfigurationEntryStore; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.store.InMemoryConfigurationEntryStore; import sonia.scm.store.InMemoryConfigurationEntryStoreFactory; -import sonia.scm.update.security.XmlSecurityV1UpdateStep; import javax.xml.bind.JAXBException; import java.io.IOException; diff --git a/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java b/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java index 5ac8f37c01..5481e2e2a5 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java @@ -6,14 +6,12 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryType; -import javax.inject.Inject; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp index 4106b97945..a70205e3eb 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp differ diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp index aee452fac4..b80169b9b5 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp differ diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp index ec5c816c12..68509a2ee8 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp differ diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp index 68b1facafe..702b5c344f 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp differ diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp index cfcaae8427..8f2758f962 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp differ diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp index 7132f78277..b5cac4f1e3 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp differ