Merge pull request #1260 from scm-manager/feature/update-optional-dependencies

Feature/update optional dependencies
This commit is contained in:
Sebastian Sdorra
2020-07-22 11:12:17 +02:00
committed by GitHub
17 changed files with 129 additions and 14 deletions

View File

@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add branch link provider to access branch links in plugins ([#1243](https://github.com/scm-manager/scm-manager/pull/1243))
- Add key value input field component ([#1246](https://github.com/scm-manager/scm-manager/pull/1246))
- Add Jexl parser ([#1251](https://github.com/scm-manager/scm-manager/pull/1251))
- Update installed optional plugin dependencies upon plugin upgrade ([#1260](https://github.com/scm-manager/scm-manager/pull/1260))
### Changed
- Adding start delay to liveness and readiness probes in helm chart template

View File

@@ -560,7 +560,7 @@
<plugin>
<groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.1.0</version>
</plugin>
<plugin>

View File

@@ -21,12 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.plugin;
import java.util.Optional;
import java.util.Set;
import static java.util.Collections.emptySet;
/**
* @since 2.0.0
*/
@@ -35,13 +37,23 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
private final PluginInformation information;
private final PluginCondition condition;
private final Set<String> dependencies;
private final Set<String> optionalDependencies;
private final String url;
private final String checksum;
/**
* @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String)} instead
*/
@Deprecated
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
this(information, condition, dependencies, emptySet(), url, checksum);
}
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, Set<String> optionalDependencies, String url, String checksum) {
this.information = information;
this.condition = condition;
this.dependencies = dependencies;
this.optionalDependencies = optionalDependencies;
this.url = url;
this.checksum = checksum;
}
@@ -68,4 +80,9 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
public Set<String> getDependencies() {
return dependencies;
}
@Override
public Set<String> getOptionalDependencies() {
return optionalDependencies;
}
}

View File

@@ -191,6 +191,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin
*
* @since 2.0.0
*/
@Override
public Set<String> getOptionalDependencies() {
if (optionalDependencies == null)
{

View File

@@ -21,11 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.plugin;
import java.util.Set;
import static java.util.Collections.emptySet;
public interface PluginDescriptor {
PluginInformation getInformation();
@@ -34,4 +36,8 @@ public interface PluginDescriptor {
Set<String> getDependencies();
default Set<String> getOptionalDependencies() {
return emptySet();
}
}

View File

@@ -36,6 +36,7 @@ export type Plugin = {
pending: boolean;
markedForUninstall?: boolean;
dependencies: string[];
optionalDependencies: string[];
_links: Links;
};

View File

@@ -62,6 +62,7 @@
"currentVersion": "Installierte Version",
"newVersion": "Neue Version",
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!",
"optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!",
"dependencies": "Abhängigkeiten",
"installedNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
"updatedNotification": "Das Plugin wurde erfolgreich aktualisiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",

View File

@@ -62,6 +62,7 @@
"currentVersion": "Installed version",
"newVersion": "New version",
"dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!",
"optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!",
"dependencies": "Dependencies",
"installedNotification": "Successfully installed plugin. You have to reload the page, to see ui changes:",
"updatedNotification": "Successfully updated plugin. You have to reload the page, to see ui changes:",

View File

@@ -183,6 +183,27 @@ class PluginModal extends React.Component<Props, State> {
return dependencies;
}
renderOptionalDependencies() {
const { plugin, t } = this.props;
let optionalDependencies = null;
if (plugin.optionalDependencies && plugin.optionalDependencies.length > 0) {
optionalDependencies = (
<div className="media">
<Notification type="warning">
<strong>{t("plugins.modal.optionalDependencyNotification")}</strong>
<ul>
{plugin.optionalDependencies.map((optionalDependency, index) => {
return <li key={index}>{optionalDependency}</li>;
})}
</ul>
</Notification>
</div>
);
}
return optionalDependencies;
}
renderNotifications = () => {
const { t, pluginAction } = this.props;
const { restart, error, success } = this.state;
@@ -275,6 +296,7 @@ class PluginModal extends React.Component<Props, State> {
</div>
)}
{this.renderDependencies()}
{this.renderOptionalDependencies()}
</div>
</div>
<div className="media">

View File

@@ -53,6 +53,7 @@ public class PluginDto extends HalRepresentation {
private Boolean core;
private Boolean markedForUninstall;
private Set<String> dependencies;
private Set<String> optionalDependencies;
public PluginDto(Links links) {
add(links);

View File

@@ -68,6 +68,7 @@ public abstract class PluginDtoMapper {
private void map(PluginDto dto, Plugin plugin) {
dto.setDependencies(plugin.getDescriptor().getDependencies());
dto.setOptionalDependencies(plugin.getDescriptor().getOptionalDependencies());
map(plugin.getDescriptor().getInformation(), dto);
if (dto.getCategory() == null) {
dto.setCategory("Miscellaneous");

View File

@@ -253,21 +253,40 @@ public class DefaultPluginManager implements PluginManager {
private void collectPluginsToInstallOrUpdate(List<AvailablePlugin> plugins, String name) {
if (!isInstalledOrPending(name) || isUpdatable(name)) {
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
Set<String> dependencies = plugin.getDescriptor().getDependencies();
if (dependencies != null) {
for (String dependency : dependencies) {
collectPluginsToInstallOrUpdate(plugins, dependency);
}
}
plugins.add(plugin);
collectDependentPlugins(plugins, name);
} else {
LOG.info("plugin {} is already installed or installation is pending, skipping installation", name);
}
}
private void collectOptionalPluginToInstallOrUpdate(List<AvailablePlugin> plugins, String name) {
if (isInstalledOrPending(name) && isUpdatable(name)) {
collectDependentPlugins(plugins, name);
} else {
LOG.info("optional plugin {} is not installed or not updatable", name);
}
}
private void collectDependentPlugins(List<AvailablePlugin> plugins, String name) {
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
Set<String> dependencies = plugin.getDescriptor().getDependencies();
if (dependencies != null) {
for (String dependency : dependencies) {
collectPluginsToInstallOrUpdate(plugins, dependency);
}
}
Set<String> optionalDependencies = plugin.getDescriptor().getOptionalDependencies();
if (dependencies != null) {
for (String optionalDependency : optionalDependencies) {
collectOptionalPluginToInstallOrUpdate(plugins, optionalDependency);
}
}
plugins.add(plugin);
}
private boolean isInstalledOrPending(String name) {
return getInstalled(name).isPresent() || getPending(name).isPresent();
}

View File

@@ -85,6 +85,9 @@ public final class PluginCenterDto implements Serializable {
@XmlElement(name = "dependencies")
private Set<String> dependencies;
@XmlElement(name = "optionalDependencies")
private Set<String> optionalDependencies;
@XmlElement(name = "_links")
private Map<String, Link> links;
}

View File

@@ -43,7 +43,7 @@ public abstract class PluginCenterDtoMapper {
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
String url = plugin.getLinks().get("download").getHref();
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256sum()
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum()
);
plugins.add(new AvailablePlugin(descriptor));
}

View File

@@ -164,6 +164,15 @@ class PluginDtoMapperTest {
assertThat(dto.getDependencies()).containsOnly("one", "two");
}
@Test
void shouldAppendOptionalDependencies() {
AvailablePlugin plugin = createAvailable(createPluginInformation());
when(plugin.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("one", "two"));
PluginDto dto = mapper.mapAvailable(plugin);
assertThat(dto.getOptionalDependencies()).containsOnly("one", "two");
}
@Test
void shouldAppendUninstallLink() {
when(subject.isPermitted("plugin:write")).thenReturn(true);

View File

@@ -260,6 +260,35 @@ class DefaultPluginManagerTest {
verify(installer).install(review);
}
@Test
void shouldUpdateAlreadyInstalledOptionalDependenciesWhenNewerVersionIsAvailable() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
InstalledPlugin installedMail = createInstalled("scm-mail-plugin", "1.0.0");
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail));
manager.install("scm-review-plugin", false);
verify(installer).install(mail);
verify(installer).install(review);
}
@Test
void shouldNotUpdateOptionalDependenciesWhenNewerVersionIsAvailableButItIsNotInstalled() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
manager.install("scm-review-plugin", false);
verify(installer, never()).install(mail);
verify(installer).install(review);
}
@Test
void shouldRollbackOnFailedInstallation() {
AvailablePlugin review = createAvailable("scm-review-plugin");

View File

@@ -67,6 +67,7 @@ class PluginCenterDtoMapperTest {
"555000444",
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
ImmutableSet.of("scm-review-plugin"),
ImmutableSet.of(),
ImmutableMap.of("download", new Link("http://download.hitchhiker.com"))
);
@@ -101,6 +102,7 @@ class PluginCenterDtoMapperTest {
"12345678aa",
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
ImmutableSet.of("scm-review-plugin"),
ImmutableSet.of(),
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/review"))
);
@@ -115,6 +117,7 @@ class PluginCenterDtoMapperTest {
"555000444",
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
ImmutableSet.of("scm-review-plugin"),
ImmutableSet.of(),
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/hitchhiker"))
);