Files
SCM-Manager/scm-ui/ui-api/src/plugins.ts

250 lines
7.5 KiB
TypeScript
Raw Normal View History

/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { ApiResult, useIndexLink, useRequiredIndexLink } from "./base";
import { isPluginCollection, PendingPlugins, Plugin, PluginCollection } from "@scm-manager/ui-types";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { apiClient } from "@scm-manager/ui-components";
import { requiredLink } from "./links";
type WaitForRestartOptions = {
initialDelay?: number;
timeout?: number;
};
const waitForRestartAfter = (
promise: Promise<any>,
{ initialDelay = 1000, timeout = 500 }: WaitForRestartOptions = {}
): Promise<void> => {
const endTime = Number(new Date()) + 60000;
let started = false;
const executor = <T = any>(data: T) => (resolve: (result: T) => void, reject: (error: Error) => void) => {
// we need some initial delay
if (!started) {
started = true;
setTimeout(executor(data), initialDelay, resolve, reject);
} else {
apiClient
.get("")
.then(() => resolve(data))
.catch(() => {
if (Number(new Date()) < endTime) {
setTimeout(executor(data), timeout, resolve, reject);
} else {
reject(new Error("timeout reached"));
}
});
}
};
return promise.then(data => new Promise<void>(executor(data)));
};
export type UseAvailablePluginsOptions = {
enabled?: boolean;
};
export const useAvailablePlugins = ({ enabled }: UseAvailablePluginsOptions = {}): ApiResult<PluginCollection> => {
const indexLink = useRequiredIndexLink("availablePlugins");
return useQuery<PluginCollection, Error>(
["plugins", "available"],
() => apiClient.get(indexLink).then(response => response.json()),
{
enabled,
retry: 3
}
);
};
export type UseInstalledPluginsOptions = {
enabled?: boolean;
};
export const useInstalledPlugins = ({ enabled }: UseInstalledPluginsOptions = {}): ApiResult<PluginCollection> => {
const indexLink = useRequiredIndexLink("installedPlugins");
return useQuery<PluginCollection, Error>(
["plugins", "installed"],
() => apiClient.get(indexLink).then(response => response.json()),
{
enabled,
retry: 3
}
);
};
export const usePendingPlugins = (): ApiResult<PendingPlugins> => {
const indexLink = useIndexLink("pendingPlugins");
return useQuery<PendingPlugins, Error>(
["plugins", "pending"],
() => apiClient.get(indexLink!).then(response => response.json()),
{
enabled: !!indexLink,
retry: 3
}
);
};
const linkWithRestart = (link: string, restart?: boolean) => {
if (restart) {
return link + "WithRestart";
}
return link;
};
type RestartOptions = WaitForRestartOptions & {
restart?: boolean;
};
type PluginActionOptions = {
plugin: Plugin;
restartOptions: RestartOptions;
};
export const useInstallPlugin = () => {
const queryClient = useQueryClient();
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PluginActionOptions>(
({ plugin, restartOptions: { restart, ...waitForRestartOptions } }) => {
const promise = apiClient.post(requiredLink(plugin, linkWithRestart("install", restart)));
if (restart) {
return waitForRestartAfter(promise, waitForRestartOptions);
}
return promise;
},
{
onSuccess: () => queryClient.invalidateQueries("plugins")
}
);
return {
install: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
mutate({
plugin,
restartOptions
}),
isLoading,
error,
data,
isInstalled: !!data
};
};
export const useUninstallPlugin = () => {
const queryClient = useQueryClient();
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PluginActionOptions>(
({ plugin, restartOptions: { restart, ...waitForRestartOptions } }) => {
const promise = apiClient.post(requiredLink(plugin, linkWithRestart("uninstall", restart)));
if (restart) {
return waitForRestartAfter(promise, waitForRestartOptions);
}
return promise;
},
{
onSuccess: () => queryClient.invalidateQueries("plugins")
}
);
return {
uninstall: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
mutate({
plugin,
restartOptions
}),
isLoading,
error,
isUninstalled: !!data
};
};
type UpdatePluginsOptions = {
plugins: Plugin | PluginCollection;
restartOptions: RestartOptions;
};
export const useUpdatePlugins = () => {
const queryClient = useQueryClient();
const { mutate, isLoading, error, data } = useMutation<unknown, Error, UpdatePluginsOptions>(
({ plugins, restartOptions: { restart, ...waitForRestartOptions } }) => {
const isCollection = isPluginCollection(plugins);
const promise = apiClient.post(
requiredLink(plugins, isCollection ? "update" : linkWithRestart("update", restart))
);
if (restart && !isCollection) {
return waitForRestartAfter(promise, waitForRestartOptions);
}
return promise;
},
{
onSuccess: () => queryClient.invalidateQueries("plugins")
}
);
return {
update: (plugin: Plugin | PluginCollection, restartOptions: RestartOptions = {}) =>
mutate({
plugins: plugin,
restartOptions
}),
isLoading,
error,
isUpdated: !!data
};
};
type ExecutePendingPlugins = {
pending: PendingPlugins;
restartOptions: WaitForRestartOptions;
};
export const useExecutePendingPlugins = () => {
const queryClient = useQueryClient();
const { mutate, isLoading, error, data } = useMutation<unknown, Error, ExecutePendingPlugins>(
({ pending, restartOptions }) =>
waitForRestartAfter(apiClient.post(requiredLink(pending, "execute")), restartOptions),
{
onSuccess: () => queryClient.invalidateQueries("plugins")
}
);
return {
update: (pending: PendingPlugins, restartOptions: WaitForRestartOptions = {}) =>
mutate({ pending, restartOptions }),
isLoading,
error,
isExecuted: !!data
};
};
export const useCancelPendingPlugins = () => {
const queryClient = useQueryClient();
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PendingPlugins>(
pending => apiClient.post(requiredLink(pending, "cancel")),
{
onSuccess: () => queryClient.invalidateQueries("plugins")
}
);
return {
update: (pending: PendingPlugins) => mutate(pending),
isLoading,
error,
isCancelled: !!data
};
};