mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-17 18:51:10 +01:00
Include cloudogu plugins to plugin center (#1709)
Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/cloudogu_plugins.yaml
Normal file
2
gradle/changelog/cloudogu_plugins.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: Added
|
||||||
|
description: Prepare plugin center to show cloudogu plugins ([#1709](https://github.com/scm-manager/scm-manager/pull/1709))
|
||||||
@@ -40,22 +40,32 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
|
|||||||
private final Set<String> optionalDependencies;
|
private final Set<String> optionalDependencies;
|
||||||
private final String url;
|
private final String url;
|
||||||
private final String checksum;
|
private final String checksum;
|
||||||
|
private final String installLink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String)} instead
|
* @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String, String)} instead
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
|
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
|
||||||
this(information, condition, dependencies, emptySet(), url, checksum);
|
this(information, condition, dependencies, emptySet(), url, checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String, String)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, Set<String> optionalDependencies, String url, String checksum) {
|
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, Set<String> optionalDependencies, String url, String checksum) {
|
||||||
|
this(information, condition, dependencies, optionalDependencies, url, checksum, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, Set<String> optionalDependencies, String url, String checksum, String installLink) {
|
||||||
this.information = information;
|
this.information = information;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
this.optionalDependencies = optionalDependencies;
|
this.optionalDependencies = optionalDependencies;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.checksum = checksum;
|
this.checksum = checksum;
|
||||||
|
this.installLink = installLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
@@ -85,4 +95,8 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
|
|||||||
public Set<String> getOptionalDependencies() {
|
public Set<String> getOptionalDependencies() {
|
||||||
return optionalDependencies;
|
return optionalDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getInstallLink() {
|
||||||
|
return Optional.ofNullable(installLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.plugin;
|
package sonia.scm.plugin;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.github.sdorra.ssp.PermissionObject;
|
import com.github.sdorra.ssp.PermissionObject;
|
||||||
import com.github.sdorra.ssp.StaticPermissions;
|
import com.github.sdorra.ssp.StaticPermissions;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -37,11 +35,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
|||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
@StaticPermissions(
|
@StaticPermissions(
|
||||||
value = "plugin",
|
value = "plugin",
|
||||||
@@ -63,6 +56,7 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
|
|||||||
private String author;
|
private String author;
|
||||||
private String category;
|
private String category;
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
|
private PluginType type = PluginType.SCM;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginInformation clone() {
|
public PluginInformation clone() {
|
||||||
@@ -74,6 +68,7 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
|
|||||||
clone.setAuthor(author);
|
clone.setAuthor(author);
|
||||||
clone.setCategory(category);
|
clone.setCategory(category);
|
||||||
clone.setAvatarUrl(avatarUrl);
|
clone.setAvatarUrl(avatarUrl);
|
||||||
|
clone.setType(type);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,4 +90,9 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
|
|||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return Util.isNotEmpty(name) && Util.isNotEmpty(version);
|
return Util.isNotEmpty(name) && Util.isNotEmpty(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PluginType {
|
||||||
|
SCM,
|
||||||
|
CLOUDOGU
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
useInstallPlugin,
|
useInstallPlugin,
|
||||||
usePendingPlugins,
|
usePendingPlugins,
|
||||||
useUninstallPlugin,
|
useUninstallPlugin,
|
||||||
useUpdatePlugins
|
useUpdatePlugins,
|
||||||
} from "./plugins";
|
} from "./plugins";
|
||||||
import { act } from "react-test-renderer";
|
import { act } from "react-test-renderer";
|
||||||
|
|
||||||
@@ -48,12 +48,13 @@ describe("Test plugin hooks", () => {
|
|||||||
pending: false,
|
pending: false,
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
optionalDependencies: [],
|
optionalDependencies: [],
|
||||||
|
type: "SCM",
|
||||||
_links: {
|
_links: {
|
||||||
install: { href: "/plugins/available/heart-of-gold-plugin/install" },
|
install: { href: "/plugins/available/heart-of-gold-plugin/install" },
|
||||||
installWithRestart: {
|
installWithRestart: {
|
||||||
href: "/plugins/available/heart-of-gold-plugin/install?restart=true"
|
href: "/plugins/available/heart-of-gold-plugin/install?restart=true",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const installedPlugin: Plugin = {
|
const installedPlugin: Plugin = {
|
||||||
@@ -66,23 +67,24 @@ describe("Test plugin hooks", () => {
|
|||||||
markedForUninstall: false,
|
markedForUninstall: false,
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
optionalDependencies: [],
|
optionalDependencies: [],
|
||||||
|
type: "SCM",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "/plugins/installed/heart-of-gold-plugin"
|
href: "/plugins/installed/heart-of-gold-plugin",
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href: "/plugins/available/heart-of-gold-plugin/install"
|
href: "/plugins/available/heart-of-gold-plugin/install",
|
||||||
},
|
},
|
||||||
updateWithRestart: {
|
updateWithRestart: {
|
||||||
href: "/plugins/available/heart-of-gold-plugin/install?restart=true"
|
href: "/plugins/available/heart-of-gold-plugin/install?restart=true",
|
||||||
},
|
},
|
||||||
uninstall: {
|
uninstall: {
|
||||||
href: "/plugins/installed/heart-of-gold-plugin/uninstall"
|
href: "/plugins/installed/heart-of-gold-plugin/uninstall",
|
||||||
},
|
},
|
||||||
uninstallWithRestart: {
|
uninstallWithRestart: {
|
||||||
href: "/plugins/installed/heart-of-gold-plugin/uninstall?restart=true"
|
href: "/plugins/installed/heart-of-gold-plugin/uninstall?restart=true",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const installedCorePlugin: Plugin = {
|
const installedCorePlugin: Plugin = {
|
||||||
@@ -93,24 +95,25 @@ describe("Test plugin hooks", () => {
|
|||||||
name: "heart-of-gold-core-plugin",
|
name: "heart-of-gold-core-plugin",
|
||||||
pending: false,
|
pending: false,
|
||||||
markedForUninstall: false,
|
markedForUninstall: false,
|
||||||
|
type: "SCM",
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
optionalDependencies: [],
|
optionalDependencies: [],
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "/plugins/installed/heart-of-gold-core-plugin"
|
href: "/plugins/installed/heart-of-gold-core-plugin",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPluginCollection = (plugins: Plugin[]): PluginCollection => ({
|
const createPluginCollection = (plugins: Plugin[]): PluginCollection => ({
|
||||||
_links: {
|
_links: {
|
||||||
update: {
|
update: {
|
||||||
href: "/plugins/update"
|
href: "/plugins/update",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
plugins
|
plugins,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const createPendingPlugins = (
|
const createPendingPlugins = (
|
||||||
@@ -122,8 +125,8 @@ describe("Test plugin hooks", () => {
|
|||||||
_embedded: {
|
_embedded: {
|
||||||
new: newPlugins,
|
new: newPlugins,
|
||||||
update: updatePlugins,
|
update: updatePlugins,
|
||||||
uninstall: uninstallPlugins
|
uninstall: uninstallPlugins,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => fetchMock.reset());
|
afterEach(() => fetchMock.reset());
|
||||||
@@ -135,7 +138,7 @@ describe("Test plugin hooks", () => {
|
|||||||
setIndexLink(queryClient, "availablePlugins", "/availablePlugins");
|
setIndexLink(queryClient, "availablePlugins", "/availablePlugins");
|
||||||
fetchMock.get("/api/v2/availablePlugins", availablePlugins);
|
fetchMock.get("/api/v2/availablePlugins", availablePlugins);
|
||||||
const { result, waitFor } = renderHook(() => useAvailablePlugins(), {
|
const { result, waitFor } = renderHook(() => useAvailablePlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await waitFor(() => !!result.current.data);
|
await waitFor(() => !!result.current.data);
|
||||||
expect(result.current.data).toEqual(availablePlugins);
|
expect(result.current.data).toEqual(availablePlugins);
|
||||||
@@ -149,7 +152,7 @@ describe("Test plugin hooks", () => {
|
|||||||
setIndexLink(queryClient, "installedPlugins", "/installedPlugins");
|
setIndexLink(queryClient, "installedPlugins", "/installedPlugins");
|
||||||
fetchMock.get("/api/v2/installedPlugins", installedPlugins);
|
fetchMock.get("/api/v2/installedPlugins", installedPlugins);
|
||||||
const { result, waitFor } = renderHook(() => useInstalledPlugins(), {
|
const { result, waitFor } = renderHook(() => useInstalledPlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await waitFor(() => !!result.current.data);
|
await waitFor(() => !!result.current.data);
|
||||||
expect(result.current.data).toEqual(installedPlugins);
|
expect(result.current.data).toEqual(installedPlugins);
|
||||||
@@ -163,7 +166,7 @@ describe("Test plugin hooks", () => {
|
|||||||
setIndexLink(queryClient, "pendingPlugins", "/pendingPlugins");
|
setIndexLink(queryClient, "pendingPlugins", "/pendingPlugins");
|
||||||
fetchMock.get("/api/v2/pendingPlugins", pendingPlugins);
|
fetchMock.get("/api/v2/pendingPlugins", pendingPlugins);
|
||||||
const { result, waitFor } = renderHook(() => usePendingPlugins(), {
|
const { result, waitFor } = renderHook(() => usePendingPlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await waitFor(() => !!result.current.data);
|
await waitFor(() => !!result.current.data);
|
||||||
expect(result.current.data).toEqual(pendingPlugins);
|
expect(result.current.data).toEqual(pendingPlugins);
|
||||||
@@ -179,7 +182,7 @@ describe("Test plugin hooks", () => {
|
|||||||
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install?restart=true", installedPlugin);
|
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install?restart=true", installedPlugin);
|
||||||
fetchMock.get("/api/v2/", "Restarted");
|
fetchMock.get("/api/v2/", "Restarted");
|
||||||
const { result, waitFor, waitForNextUpdate } = renderHook(() => useInstallPlugin(), {
|
const { result, waitFor, waitForNextUpdate } = renderHook(() => useInstallPlugin(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { install } = result.current;
|
const { install } = result.current;
|
||||||
@@ -199,7 +202,7 @@ describe("Test plugin hooks", () => {
|
|||||||
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
||||||
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install", installedPlugin);
|
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install", installedPlugin);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useInstallPlugin(), {
|
const { result, waitForNextUpdate } = renderHook(() => useInstallPlugin(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { install } = result.current;
|
const { install } = result.current;
|
||||||
@@ -221,7 +224,7 @@ describe("Test plugin hooks", () => {
|
|||||||
fetchMock.post("/api/v2/plugins/installed/heart-of-gold-plugin/uninstall?restart=true", availablePlugin);
|
fetchMock.post("/api/v2/plugins/installed/heart-of-gold-plugin/uninstall?restart=true", availablePlugin);
|
||||||
fetchMock.get("/api/v2/", "Restarted");
|
fetchMock.get("/api/v2/", "Restarted");
|
||||||
const { result, waitForNextUpdate, waitFor } = renderHook(() => useUninstallPlugin(), {
|
const { result, waitForNextUpdate, waitFor } = renderHook(() => useUninstallPlugin(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { uninstall } = result.current;
|
const { uninstall } = result.current;
|
||||||
@@ -241,7 +244,7 @@ describe("Test plugin hooks", () => {
|
|||||||
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
||||||
fetchMock.post("/api/v2/plugins/installed/heart-of-gold-plugin/uninstall", availablePlugin);
|
fetchMock.post("/api/v2/plugins/installed/heart-of-gold-plugin/uninstall", availablePlugin);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useUninstallPlugin(), {
|
const { result, waitForNextUpdate } = renderHook(() => useUninstallPlugin(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { uninstall } = result.current;
|
const { uninstall } = result.current;
|
||||||
@@ -263,7 +266,7 @@ describe("Test plugin hooks", () => {
|
|||||||
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install?restart=true", installedPlugin);
|
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install?restart=true", installedPlugin);
|
||||||
fetchMock.get("/api/v2/", "Restarted");
|
fetchMock.get("/api/v2/", "Restarted");
|
||||||
const { result, waitForNextUpdate, waitFor } = renderHook(() => useUpdatePlugins(), {
|
const { result, waitForNextUpdate, waitFor } = renderHook(() => useUpdatePlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { update } = result.current;
|
const { update } = result.current;
|
||||||
@@ -282,7 +285,7 @@ describe("Test plugin hooks", () => {
|
|||||||
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
||||||
fetchMock.post("/api/v2/plugins/update", installedPlugin);
|
fetchMock.post("/api/v2/plugins/update", installedPlugin);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { update } = result.current;
|
const { update } = result.current;
|
||||||
@@ -300,7 +303,7 @@ describe("Test plugin hooks", () => {
|
|||||||
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
||||||
fetchMock.post("/api/v2/plugins/update", installedPlugin);
|
fetchMock.post("/api/v2/plugins/update", installedPlugin);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { update } = result.current;
|
const { update } = result.current;
|
||||||
@@ -318,7 +321,7 @@ describe("Test plugin hooks", () => {
|
|||||||
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
queryClient.setQueryData(["plugins", "pending"], createPendingPlugins());
|
||||||
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install", installedPlugin);
|
fetchMock.post("/api/v2/plugins/available/heart-of-gold-plugin/install", installedPlugin);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
const { result, waitForNextUpdate } = renderHook(() => useUpdatePlugins(), {
|
||||||
wrapper: createWrapper(undefined, queryClient)
|
wrapper: createWrapper(undefined, queryClient),
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
const { update } = result.current;
|
const { update } = result.current;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
import { HalRepresentation, HalRepresentationWithEmbedded } from "./hal";
|
import { HalRepresentation, HalRepresentationWithEmbedded } from "./hal";
|
||||||
|
|
||||||
|
type PluginType = "SCM" | "CLOUDOGU";
|
||||||
|
|
||||||
export type Plugin = HalRepresentation & {
|
export type Plugin = HalRepresentation & {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@@ -34,6 +36,7 @@ export type Plugin = HalRepresentation & {
|
|||||||
category: string;
|
category: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
pending: boolean;
|
pending: boolean;
|
||||||
|
type: PluginType;
|
||||||
markedForUninstall?: boolean;
|
markedForUninstall?: boolean;
|
||||||
dependencies: string[];
|
dependencies: string[];
|
||||||
optionalDependencies: string[];
|
optionalDependencies: string[];
|
||||||
@@ -43,7 +46,8 @@ export type PluginCollection = HalRepresentationWithEmbedded<{
|
|||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const isPluginCollection = (input: HalRepresentation): input is PluginCollection => input._embedded ? "plugins" in input._embedded : false;
|
export const isPluginCollection = (input: HalRepresentation): input is PluginCollection =>
|
||||||
|
input._embedded ? "plugins" in input._embedded : false;
|
||||||
|
|
||||||
export type PluginGroup = {
|
export type PluginGroup = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -47,7 +47,8 @@
|
|||||||
"title": {
|
"title": {
|
||||||
"install": "{{name}} Plugin installieren",
|
"install": "{{name}} Plugin installieren",
|
||||||
"update": "{{name}} Plugin aktualisieren",
|
"update": "{{name}} Plugin aktualisieren",
|
||||||
"uninstall": "{{name}} Plugin deinstallieren"
|
"uninstall": "{{name}} Plugin deinstallieren",
|
||||||
|
"cloudoguInstall": "Plugin über myCloudogu installieren"
|
||||||
},
|
},
|
||||||
"restart": "Neustarten, um Plugin-Änderungen wirksam zu machen",
|
"restart": "Neustarten, um Plugin-Änderungen wirksam zu machen",
|
||||||
"install": "Installieren",
|
"install": "Installieren",
|
||||||
@@ -67,6 +68,8 @@
|
|||||||
"version": "Version",
|
"version": "Version",
|
||||||
"currentVersion": "Installierte Version",
|
"currentVersion": "Installierte Version",
|
||||||
"newVersion": "Neue Version",
|
"newVersion": "Neue Version",
|
||||||
|
"cloudoguInstallInfo": "Dieses Plugin ist nur über myCloudogu erhältlich. Zum Installieren folgen Sie bitte der Anleitung.",
|
||||||
|
"cloudoguInstall": "Zur Installationsanleitung",
|
||||||
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!",
|
"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!",
|
"optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!",
|
||||||
"dependencies": "Abhängigkeiten",
|
"dependencies": "Abhängigkeiten",
|
||||||
|
|||||||
@@ -47,7 +47,8 @@
|
|||||||
"title": {
|
"title": {
|
||||||
"install": "Install {{name}} Plugin",
|
"install": "Install {{name}} Plugin",
|
||||||
"update": "Update {{name}} Plugin",
|
"update": "Update {{name}} Plugin",
|
||||||
"uninstall": "Uninstall {{name}} Plugin"
|
"uninstall": "Uninstall {{name}} Plugin",
|
||||||
|
"cloudoguInstall": "Get plugin from myCloudogu"
|
||||||
},
|
},
|
||||||
"restart": "Restart to make plugin changes effective",
|
"restart": "Restart to make plugin changes effective",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
@@ -67,6 +68,8 @@
|
|||||||
"version": "Version",
|
"version": "Version",
|
||||||
"currentVersion": "Installed version",
|
"currentVersion": "Installed version",
|
||||||
"newVersion": "New version",
|
"newVersion": "New version",
|
||||||
|
"cloudoguInstallInfo": "This plugin is only available via myCloudogu. Follow the instructions to install it.",
|
||||||
|
"cloudoguInstall": "To Installation Instructions",
|
||||||
"dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!",
|
"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!",
|
"optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!",
|
||||||
"dependencies": "Dependencies",
|
"dependencies": "Dependencies",
|
||||||
|
|||||||
@@ -58,12 +58,18 @@ const PluginEntry: FC<Props> = ({ plugin, openModal }) => {
|
|||||||
const isInstallable = plugin._links.install && (plugin._links.install as Link).href;
|
const isInstallable = plugin._links.install && (plugin._links.install as Link).href;
|
||||||
const isUpdatable = plugin._links.update && (plugin._links.update as Link).href;
|
const isUpdatable = plugin._links.update && (plugin._links.update as Link).href;
|
||||||
const isUninstallable = plugin._links.uninstall && (plugin._links.uninstall as Link).href;
|
const isUninstallable = plugin._links.uninstall && (plugin._links.uninstall as Link).href;
|
||||||
|
const isCloudoguPlugin = plugin.type === "CLOUDOGU";
|
||||||
|
|
||||||
const pendingSpinner = () => (
|
const pendingSpinner = () => (
|
||||||
<Icon className="fa-spin fa-lg" name="spinner" color={plugin.markedForUninstall ? "danger" : "info"} />
|
<Icon className="fa-spin fa-lg" name="spinner" color={plugin.markedForUninstall ? "danger" : "info"} />
|
||||||
);
|
);
|
||||||
const actionBar = () => (
|
const actionBar = () => (
|
||||||
<ActionbarWrapper className="is-flex">
|
<ActionbarWrapper className="is-flex">
|
||||||
|
{isCloudoguPlugin && (
|
||||||
|
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}>
|
||||||
|
<Icon title={t("plugins.modal.cloudoguInstall")} name="link" color="success-dark" />
|
||||||
|
</IconWrapper>
|
||||||
|
)}
|
||||||
{isInstallable && (
|
{isInstallable && (
|
||||||
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}>
|
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}>
|
||||||
<Icon title={t("plugins.modal.install")} name="download" color="info" />
|
<Icon title={t("plugins.modal.install")} name="download" color="info" />
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PluginGroupEntry: FC<Props> = ({ openModal, group }) => {
|
const PluginGroupEntry: FC<Props> = ({ openModal, group }) => {
|
||||||
const entries = group.plugins.map(plugin => {
|
const entries = group.plugins.map((plugin) => {
|
||||||
return <PluginEntry plugin={plugin} openModal={openModal} key={plugin.name} />;
|
return <PluginEntry plugin={plugin} openModal={openModal} key={plugin.name} />;
|
||||||
});
|
});
|
||||||
return <CardColumnGroup name={group.name} elements={entries} />;
|
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const PluginList: FC<Props> = ({ plugins, openModal }) => {
|
|||||||
const groups = groupByCategory(plugins);
|
const groups = groupByCategory(plugins);
|
||||||
return (
|
return (
|
||||||
<div className="content is-plugin-page">
|
<div className="content is-plugin-page">
|
||||||
{groups.map(group => {
|
{groups.map((group) => {
|
||||||
return <PluginGroupEntry group={group} openModal={openModal} key={group.name} />;
|
return <PluginGroupEntry group={group} openModal={openModal} key={group.name} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React, { FC, useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Plugin } from "@scm-manager/ui-types";
|
import { Link, Plugin } from "@scm-manager/ui-types";
|
||||||
import { Button, ButtonGroup, Checkbox, ErrorNotification, Modal, Notification } from "@scm-manager/ui-components";
|
import { Button, ButtonGroup, Checkbox, ErrorNotification, Modal, Notification } from "@scm-manager/ui-components";
|
||||||
import SuccessNotification from "./SuccessNotification";
|
import SuccessNotification from "./SuccessNotification";
|
||||||
import { useInstallPlugin, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api";
|
import { useInstallPlugin, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api";
|
||||||
@@ -33,13 +33,17 @@ import { PluginAction } from "../containers/PluginsOverview";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
pluginAction: string;
|
pluginAction: PluginAction;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListParent = styled.div`
|
type ParentWithPluginAction = {
|
||||||
|
pluginAction?: PluginAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListParent = styled.div.attrs((props) => ({}))<ParentWithPluginAction>`
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
min-width: ${props => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")};
|
min-width: ${(props) => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")};
|
||||||
text-align: left;
|
text-align: left;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -63,9 +67,12 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
|||||||
}
|
}
|
||||||
}, [isDone]);
|
}, [isDone]);
|
||||||
|
|
||||||
const handlePluginAction = (e: Event) => {
|
const handlePluginAction = (e: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
switch (pluginAction) {
|
switch (pluginAction) {
|
||||||
|
case PluginAction.CLOUDOGU:
|
||||||
|
window.open((plugin._links.cloudoguInstall as Link).href, "_blank");
|
||||||
|
break;
|
||||||
case PluginAction.INSTALL:
|
case PluginAction.INSTALL:
|
||||||
install(plugin, { restart: shouldRestart });
|
install(plugin, { restart: shouldRestart });
|
||||||
break;
|
break;
|
||||||
@@ -76,7 +83,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
|||||||
update(plugin, { restart: shouldRestart });
|
update(plugin, { restart: shouldRestart });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unkown plugin action ${pluginAction}`);
|
throw new Error(`Unknown plugin action ${pluginAction}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,7 +179,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else if (pluginAction !== PluginAction.CLOUDOGU) {
|
||||||
return <Notification type="warning">{t("plugins.modal.manualRestartRequired")}</Notification>;
|
return <Notification type="warning">{t("plugins.modal.manualRestartRequired")}</Notification>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -192,6 +199,13 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
|||||||
</ListParent>
|
</ListParent>
|
||||||
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild>
|
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild>
|
||||||
</div>
|
</div>
|
||||||
|
{pluginAction === PluginAction.CLOUDOGU && (
|
||||||
|
<div className="field is-horizontal">
|
||||||
|
<Notification type="info" className="is-full-width">
|
||||||
|
{t("plugins.modal.cloudoguInstallInfo")}
|
||||||
|
</Notification>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{pluginAction === PluginAction.INSTALL && (
|
{pluginAction === PluginAction.INSTALL && (
|
||||||
<div className="field is-horizontal">
|
<div className="field is-horizontal">
|
||||||
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}>
|
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}>
|
||||||
@@ -230,7 +244,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t(`plugins.modal.title.${pluginAction}`, {
|
title={t(`plugins.modal.title.${pluginAction}`, {
|
||||||
name: plugin.displayName ? plugin.displayName : plugin.name
|
name: plugin.displayName ? plugin.displayName : plugin.name,
|
||||||
})}
|
})}
|
||||||
closeFunction={onClose}
|
closeFunction={onClose}
|
||||||
body={body}
|
body={body}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
Loading,
|
Loading,
|
||||||
Notification,
|
Notification,
|
||||||
Subtitle,
|
Subtitle,
|
||||||
Title
|
Title,
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import PluginsList from "../components/PluginList";
|
import PluginsList from "../components/PluginList";
|
||||||
import PluginTopActions from "../components/PluginTopActions";
|
import PluginTopActions from "../components/PluginTopActions";
|
||||||
@@ -47,13 +47,14 @@ import PluginModal from "../components/PluginModal";
|
|||||||
export enum PluginAction {
|
export enum PluginAction {
|
||||||
INSTALL = "install",
|
INSTALL = "install",
|
||||||
UPDATE = "update",
|
UPDATE = "update",
|
||||||
UNINSTALL = "uninstall"
|
UNINSTALL = "uninstall",
|
||||||
|
CLOUDOGU = "cloudoguInstall",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginModalContent = {
|
export type PluginModalContent = {
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
action: PluginAction;
|
action: PluginAction;
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
installed: boolean;
|
installed: boolean;
|
||||||
@@ -64,12 +65,12 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
|||||||
const {
|
const {
|
||||||
data: availablePlugins,
|
data: availablePlugins,
|
||||||
isLoading: isLoadingAvailablePlugins,
|
isLoading: isLoadingAvailablePlugins,
|
||||||
error: availablePluginsError
|
error: availablePluginsError,
|
||||||
} = useAvailablePlugins({ enabled: !installed });
|
} = useAvailablePlugins({ enabled: !installed });
|
||||||
const {
|
const {
|
||||||
data: installedPlugins,
|
data: installedPlugins,
|
||||||
isLoading: isLoadingInstalledPlugins,
|
isLoading: isLoadingInstalledPlugins,
|
||||||
error: installedPluginsError
|
error: installedPluginsError,
|
||||||
} = useInstalledPlugins({ enabled: installed });
|
} = useInstalledPlugins({ enabled: installed });
|
||||||
const { data: pendingPlugins, isLoading: isLoadingPendingPlugins, error: pendingPluginsError } = usePendingPlugins();
|
const { data: pendingPlugins, isLoading: isLoadingPendingPlugins, error: pendingPluginsError } = usePendingPlugins();
|
||||||
const [showPendingModal, setShowPendingModal] = useState(false);
|
const [showPendingModal, setShowPendingModal] = useState(false);
|
||||||
@@ -166,7 +167,7 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
|||||||
const computeUpdateAllSize = () => {
|
const computeUpdateAllSize = () => {
|
||||||
const outdatedPlugins = collection?._embedded.plugins.filter((p: Plugin) => p._links.update).length;
|
const outdatedPlugins = collection?._embedded.plugins.filter((p: Plugin) => p._links.update).length;
|
||||||
return t("plugins.outdatedPlugins", {
|
return t("plugins.outdatedPlugins", {
|
||||||
count: outdatedPlugins
|
count: outdatedPlugins,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,28 +188,14 @@ const PluginsOverview: FC<Props> = ({ installed }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (showCancelModal && pendingPlugins) {
|
if (showCancelModal && pendingPlugins) {
|
||||||
return (
|
return <CancelPendingActionModal onClose={() => setShowCancelModal(false)} pendingPlugins={pendingPlugins} />;
|
||||||
<CancelPendingActionModal
|
|
||||||
onClose={() => setShowCancelModal(false)}
|
|
||||||
pendingPlugins={pendingPlugins}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (showUpdateAllModal && collection) {
|
if (showUpdateAllModal && collection) {
|
||||||
return (
|
return <UpdateAllActionModal onClose={() => setShowUpdateAllModal(false)} installedPlugins={collection} />;
|
||||||
<UpdateAllActionModal
|
|
||||||
onClose={() => setShowUpdateAllModal(false)}
|
|
||||||
installedPlugins={collection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (pluginModalContent) {
|
if (pluginModalContent) {
|
||||||
const { action, plugin } = pluginModalContent;
|
const { action, plugin } = pluginModalContent;
|
||||||
return <PluginModal
|
return <PluginModal plugin={plugin} pluginAction={action} onClose={() => setPluginModalContent(null)} />;
|
||||||
plugin={plugin}
|
|
||||||
pluginAction={action}
|
|
||||||
onClose={() => setPluginModalContent(null)}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -98,6 +97,7 @@ public class AvailablePluginResource {
|
|||||||
PluginPermissions.read().check();
|
PluginPermissions.read().check();
|
||||||
List<InstalledPlugin> installed = pluginManager.getInstalled();
|
List<InstalledPlugin> installed = pluginManager.getInstalled();
|
||||||
List<AvailablePlugin> available = pluginManager.getAvailable().stream().filter(a -> notInstalled(a, installed)).collect(Collectors.toList());
|
List<AvailablePlugin> available = pluginManager.getAvailable().stream().filter(a -> notInstalled(a, installed)).collect(Collectors.toList());
|
||||||
|
|
||||||
return Response.ok(collectionMapper.mapAvailable(available)).build();
|
return Response.ok(collectionMapper.mapAvailable(available)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import de.otto.edison.hal.Links;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import sonia.scm.plugin.PluginInformation;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ public class PluginDto extends HalRepresentation {
|
|||||||
private String author;
|
private String author;
|
||||||
private String category;
|
private String category;
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
|
private PluginInformation.PluginType type = PluginInformation.PluginType.SCM;
|
||||||
private boolean pending;
|
private boolean pending;
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private Boolean core;
|
private Boolean core;
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import sonia.scm.plugin.PluginInformation;
|
|||||||
import sonia.scm.plugin.PluginPermissions;
|
import sonia.scm.plugin.PluginPermissions;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -71,6 +70,9 @@ public abstract class PluginDtoMapper {
|
|||||||
PluginDto dto = createDtoForAvailable(plugin);
|
PluginDto dto = createDtoForAvailable(plugin);
|
||||||
map(dto, plugin);
|
map(dto, plugin);
|
||||||
dto.setPending(plugin.isPending());
|
dto.setPending(plugin.isPending());
|
||||||
|
if (dto.getType() == null) {
|
||||||
|
dto.setType(PluginInformation.PluginType.SCM);
|
||||||
|
}
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +93,14 @@ public abstract class PluginDtoMapper {
|
|||||||
.self(information.getName()));
|
.self(information.getName()));
|
||||||
|
|
||||||
if (!plugin.isPending() && PluginPermissions.write().isPermitted()) {
|
if (!plugin.isPending() && PluginPermissions.write().isPermitted()) {
|
||||||
String href = resourceLinks.availablePlugin().install(information.getName());
|
boolean isCloudoguPlugin = plugin.getDescriptor().getInformation().getType() == PluginInformation.PluginType.CLOUDOGU;
|
||||||
appendLink(links, "install", href);
|
if (isCloudoguPlugin) {
|
||||||
|
Optional<String> cloudoguInstallLink = plugin.getDescriptor().getInstallLink();
|
||||||
|
cloudoguInstallLink.ifPresent(link -> links.single(link("cloudoguInstall", link)));
|
||||||
|
} else {
|
||||||
|
String href = resourceLinks.availablePlugin().install(information.getName());
|
||||||
|
appendLink(links, "install", href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PluginDto(links.build());
|
return new PluginDto(links.build());
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ import javax.ws.rs.Path;
|
|||||||
@Path("v2/plugins")
|
@Path("v2/plugins")
|
||||||
public class PluginRootResource {
|
public class PluginRootResource {
|
||||||
|
|
||||||
private Provider<InstalledPluginResource> installedPluginResourceProvider;
|
private final Provider<InstalledPluginResource> installedPluginResourceProvider;
|
||||||
private Provider<AvailablePluginResource> availablePluginResourceProvider;
|
private final Provider<AvailablePluginResource> availablePluginResourceProvider;
|
||||||
private Provider<PendingPluginResource> pendingPluginResourceProvider;
|
private final Provider<PendingPluginResource> pendingPluginResourceProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PluginRootResource(Provider<InstalledPluginResource> installedPluginResourceProvider, Provider<AvailablePluginResource> availablePluginResourceProvider, Provider<PendingPluginResource> pendingPluginResourceProvider) {
|
public PluginRootResource(Provider<InstalledPluginResource> installedPluginResourceProvider, Provider<AvailablePluginResource> availablePluginResourceProvider, Provider<PendingPluginResource> pendingPluginResourceProvider) {
|
||||||
|
|||||||
@@ -70,26 +70,27 @@ public final class PluginCenterDto implements Serializable {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class Plugin {
|
public static class Plugin {
|
||||||
|
|
||||||
private String name;
|
private final String name;
|
||||||
private String version;
|
private final String version;
|
||||||
private String displayName;
|
private final String displayName;
|
||||||
private String description;
|
private final String description;
|
||||||
private String category;
|
private final String category;
|
||||||
private String author;
|
private final String author;
|
||||||
private String avatarUrl;
|
private final String avatarUrl;
|
||||||
private String sha256sum;
|
private final String sha256sum;
|
||||||
|
private PluginInformation.PluginType type;
|
||||||
|
|
||||||
@XmlElement(name = "conditions")
|
@XmlElement(name = "conditions")
|
||||||
private Condition conditions;
|
private final Condition conditions;
|
||||||
|
|
||||||
@XmlElement(name = "dependencies")
|
@XmlElement(name = "dependencies")
|
||||||
private Set<String> dependencies;
|
private final Set<String> dependencies;
|
||||||
|
|
||||||
@XmlElement(name = "optionalDependencies")
|
@XmlElement(name = "optionalDependencies")
|
||||||
private Set<String> optionalDependencies;
|
private final Set<String> optionalDependencies;
|
||||||
|
|
||||||
@XmlElement(name = "_links")
|
@XmlElement(name = "_links")
|
||||||
private Map<String, Link> links;
|
private final Map<String, Link> links;
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@@ -98,9 +99,9 @@ public final class PluginCenterDto implements Serializable {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class Condition {
|
public static class Condition {
|
||||||
|
|
||||||
private List<String> os;
|
private final List<String> os;
|
||||||
private String arch;
|
private final String arch;
|
||||||
private String minVersion;
|
private final String minVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
|||||||
@@ -31,22 +31,32 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class PluginCenterDtoMapper {
|
public abstract class PluginCenterDtoMapper {
|
||||||
|
|
||||||
static final PluginCenterDtoMapper INSTANCE = Mappers.getMapper(PluginCenterDtoMapper.class);
|
static final PluginCenterDtoMapper INSTANCE = Mappers.getMapper(PluginCenterDtoMapper.class);
|
||||||
|
|
||||||
abstract PluginInformation map(PluginCenterDto.Plugin plugin);
|
abstract PluginInformation map(PluginCenterDto.Plugin plugin);
|
||||||
|
|
||||||
abstract PluginCondition map(PluginCenterDto.Condition condition);
|
abstract PluginCondition map(PluginCenterDto.Condition condition);
|
||||||
|
|
||||||
Set<AvailablePlugin> map(PluginCenterDto pluginCenterDto) {
|
Set<AvailablePlugin> map(PluginCenterDto pluginCenterDto) {
|
||||||
Set<AvailablePlugin> plugins = new HashSet<>();
|
Set<AvailablePlugin> plugins = new HashSet<>();
|
||||||
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
|
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
|
||||||
String url = plugin.getLinks().get("download").getHref();
|
String url = plugin.getLinks().get("download").getHref();
|
||||||
|
String installLink = getInstallLink(plugin);
|
||||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||||
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum()
|
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum(), installLink
|
||||||
);
|
);
|
||||||
plugins.add(new AvailablePlugin(descriptor));
|
plugins.add(new AvailablePlugin(descriptor));
|
||||||
}
|
}
|
||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getInstallLink(PluginCenterDto.Plugin plugin) {
|
||||||
|
PluginCenterDto.Link link = plugin.getLinks().get("install");
|
||||||
|
if (link != null) {
|
||||||
|
return link.getHref();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import java.net.URI;
|
|||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.plugin.PluginInformation.PluginType.*;
|
||||||
import static sonia.scm.plugin.PluginTestHelper.createAvailable;
|
import static sonia.scm.plugin.PluginTestHelper.createAvailable;
|
||||||
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
||||||
|
|
||||||
@@ -85,9 +86,14 @@ class PluginDtoMapperTest {
|
|||||||
assertThat(dto.getAuthor()).isEqualTo("Sebastian Sdorra");
|
assertThat(dto.getAuthor()).isEqualTo("Sebastian Sdorra");
|
||||||
assertThat(dto.getCategory()).isEqualTo("Authentication");
|
assertThat(dto.getCategory()).isEqualTo("Authentication");
|
||||||
assertThat(dto.getAvatarUrl()).isEqualTo("https://avatar.scm-manager.org/plugins/cas.png");
|
assertThat(dto.getAvatarUrl()).isEqualTo("https://avatar.scm-manager.org/plugins/cas.png");
|
||||||
|
assertThat(dto.getType()).isEqualTo(SCM);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginInformation createPluginInformation() {
|
private PluginInformation createPluginInformation() {
|
||||||
|
return createPluginInformation(SCM);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginInformation createPluginInformation(PluginInformation.PluginType type) {
|
||||||
PluginInformation information = new PluginInformation();
|
PluginInformation information = new PluginInformation();
|
||||||
information.setName("scm-cas-plugin");
|
information.setName("scm-cas-plugin");
|
||||||
information.setVersion("1.0.0");
|
information.setVersion("1.0.0");
|
||||||
@@ -95,6 +101,7 @@ class PluginDtoMapperTest {
|
|||||||
information.setAuthor("Sebastian Sdorra");
|
information.setAuthor("Sebastian Sdorra");
|
||||||
information.setCategory("Authentication");
|
information.setCategory("Authentication");
|
||||||
information.setAvatarUrl("https://avatar.scm-manager.org/plugins/cas.png");
|
information.setAvatarUrl("https://avatar.scm-manager.org/plugins/cas.png");
|
||||||
|
information.setType(type);
|
||||||
return information;
|
return information;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +142,18 @@ class PluginDtoMapperTest {
|
|||||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install");
|
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendCloudoguInstallLink() {
|
||||||
|
when(subject.isPermitted("plugin:write")).thenReturn(true);
|
||||||
|
AvailablePlugin plugin = createAvailable(createPluginInformation(CLOUDOGU));
|
||||||
|
|
||||||
|
PluginDto dto = mapper.mapAvailable(plugin);
|
||||||
|
|
||||||
|
assertThat(dto.getType()).isEqualTo(CLOUDOGU);
|
||||||
|
assertThat(dto.getLinks().getLinkBy("cloudoguInstall").get().getHref())
|
||||||
|
.isEqualTo("mycloudogu.com/install/my_plugin");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldAppendInstallWithRestartLink() {
|
void shouldAppendInstallWithRestartLink() {
|
||||||
when(restarter.isSupported()).thenReturn(true);
|
when(restarter.isSupported()).thenReturn(true);
|
||||||
|
|||||||
@@ -58,13 +58,14 @@ class PluginCenterDtoMapperTest {
|
|||||||
void shouldMapSinglePlugin() {
|
void shouldMapSinglePlugin() {
|
||||||
Plugin plugin = new Plugin(
|
Plugin plugin = new Plugin(
|
||||||
"scm-hitchhiker-plugin",
|
"scm-hitchhiker-plugin",
|
||||||
|
"2.0.0",
|
||||||
"SCM Hitchhiker Plugin",
|
"SCM Hitchhiker Plugin",
|
||||||
"plugin for hitchhikers",
|
"plugin for hitchhikers",
|
||||||
"Travel",
|
"Travel",
|
||||||
"2.0.0",
|
|
||||||
"trillian",
|
"trillian",
|
||||||
"http://avatar.url",
|
"http://avatar.url",
|
||||||
"555000444",
|
"555000444",
|
||||||
|
PluginInformation.PluginType.SCM,
|
||||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||||
ImmutableSet.of("scm-review-plugin"),
|
ImmutableSet.of("scm-review-plugin"),
|
||||||
ImmutableSet.of(),
|
ImmutableSet.of(),
|
||||||
@@ -93,13 +94,14 @@ class PluginCenterDtoMapperTest {
|
|||||||
void shouldMapMultiplePlugins() {
|
void shouldMapMultiplePlugins() {
|
||||||
Plugin plugin1 = new Plugin(
|
Plugin plugin1 = new Plugin(
|
||||||
"scm-review-plugin",
|
"scm-review-plugin",
|
||||||
|
"2.1.0",
|
||||||
"SCM Hitchhiker Plugin",
|
"SCM Hitchhiker Plugin",
|
||||||
"plugin for hitchhikers",
|
"plugin for hitchhikers",
|
||||||
"Travel",
|
"Travel",
|
||||||
"2.1.0",
|
|
||||||
"trillian",
|
"trillian",
|
||||||
"https://avatar.url",
|
"https://avatar.url",
|
||||||
"12345678aa",
|
"12345678aa",
|
||||||
|
PluginInformation.PluginType.SCM,
|
||||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||||
ImmutableSet.of("scm-review-plugin"),
|
ImmutableSet.of("scm-review-plugin"),
|
||||||
ImmutableSet.of(),
|
ImmutableSet.of(),
|
||||||
@@ -108,13 +110,14 @@ class PluginCenterDtoMapperTest {
|
|||||||
|
|
||||||
Plugin plugin2 = new Plugin(
|
Plugin plugin2 = new Plugin(
|
||||||
"scm-hitchhiker-plugin",
|
"scm-hitchhiker-plugin",
|
||||||
|
"2.0.0",
|
||||||
"SCM Hitchhiker Plugin",
|
"SCM Hitchhiker Plugin",
|
||||||
"plugin for hitchhikers",
|
"plugin for hitchhikers",
|
||||||
"Travel",
|
"Travel",
|
||||||
"2.0.0",
|
|
||||||
"dent",
|
"dent",
|
||||||
"http://avatar.url",
|
"http://avatar.url",
|
||||||
"555000444",
|
"555000444",
|
||||||
|
PluginInformation.PluginType.CLOUDOGU,
|
||||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||||
ImmutableSet.of("scm-review-plugin"),
|
ImmutableSet.of("scm-review-plugin"),
|
||||||
ImmutableSet.of(),
|
ImmutableSet.of(),
|
||||||
@@ -132,6 +135,8 @@ class PluginCenterDtoMapperTest {
|
|||||||
assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion());
|
assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion());
|
||||||
assertThat(pluginInformation2.getAuthor()).isEqualTo(plugin2.getAuthor());
|
assertThat(pluginInformation2.getAuthor()).isEqualTo(plugin2.getAuthor());
|
||||||
assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion());
|
assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion());
|
||||||
|
assertThat(pluginInformation1.getType()).isEqualTo(PluginInformation.PluginType.SCM);
|
||||||
|
assertThat(pluginInformation2.getType()).isEqualTo(PluginInformation.PluginType.CLOUDOGU);
|
||||||
assertThat(resultSet.size()).isEqualTo(2);
|
assertThat(resultSet.size()).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ package sonia.scm.plugin;
|
|||||||
|
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -62,6 +64,7 @@ public class PluginTestHelper {
|
|||||||
public static AvailablePlugin createAvailable(PluginInformation information) {
|
public static AvailablePlugin createAvailable(PluginInformation information) {
|
||||||
AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class);
|
AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class);
|
||||||
lenient().when(descriptor.getInformation()).thenReturn(information);
|
lenient().when(descriptor.getInformation()).thenReturn(information);
|
||||||
|
lenient().when(descriptor.getInstallLink()).thenReturn(Optional.of("mycloudogu.com/install/my_plugin"));
|
||||||
return new AvailablePlugin(descriptor);
|
return new AvailablePlugin(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user