/* * 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, { initialDelay = 1000, timeout = 500 }: WaitForRestartOptions = {} ): Promise => { const endTime = Number(new Date()) + 60000; let started = false; const executor = (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(executor(data))); }; export type UseAvailablePluginsOptions = { enabled?: boolean; }; export const useAvailablePlugins = ({ enabled }: UseAvailablePluginsOptions = {}): ApiResult => { const indexLink = useRequiredIndexLink("availablePlugins"); return useQuery( ["plugins", "available"], () => apiClient.get(indexLink).then(response => response.json()), { enabled, retry: 3 } ); }; export type UseInstalledPluginsOptions = { enabled?: boolean; }; export const useInstalledPlugins = ({ enabled }: UseInstalledPluginsOptions = {}): ApiResult => { const indexLink = useRequiredIndexLink("installedPlugins"); return useQuery( ["plugins", "installed"], () => apiClient.get(indexLink).then(response => response.json()), { enabled, retry: 3 } ); }; export const usePendingPlugins = (): ApiResult => { const indexLink = useIndexLink("pendingPlugins"); return useQuery( ["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( ({ 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( ({ 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( ({ 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( ({ 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( pending => apiClient.post(requiredLink(pending, "cancel")), { onSuccess: () => queryClient.invalidateQueries("plugins") } ); return { update: (pending: PendingPlugins) => mutate(pending), isLoading, error, isCancelled: !!data }; };