From 5f905ce0202388a22ed963cd7767616d8838d3e2 Mon Sep 17 00:00:00 2001 From: Sam Gibbs <34915414+iamsamgibbs@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:06:44 +0100 Subject: [PATCH] feat: improve failed extension install, initialise and setUserRole transaction handling --- .../partials/ExtensionDetailsHeader/hooks.tsx | 143 ++++++++++++------ .../pages/ExtensionDetailsPage/utils.tsx | 32 +++- src/i18n/en.json | 2 + .../extensions/extensionInstallAndEnable.ts | 36 ++++- 4 files changed, 162 insertions(+), 51 deletions(-) diff --git a/src/components/frame/Extensions/pages/ExtensionDetailsPage/partials/ExtensionDetailsHeader/hooks.tsx b/src/components/frame/Extensions/pages/ExtensionDetailsPage/partials/ExtensionDetailsHeader/hooks.tsx index 7bd3dac6b13..736b16a3fbb 100644 --- a/src/components/frame/Extensions/pages/ExtensionDetailsPage/partials/ExtensionDetailsHeader/hooks.tsx +++ b/src/components/frame/Extensions/pages/ExtensionDetailsPage/partials/ExtensionDetailsHeader/hooks.tsx @@ -10,6 +10,7 @@ import { waitForDbAfterExtensionAction } from '~frame/Extensions/pages/Extension import useAsyncFunction from '~hooks/useAsyncFunction.ts'; import useExtensionData, { ExtensionMethods } from '~hooks/useExtensionData.ts'; import { ActionTypes } from '~redux'; +import { ExtensionInstallAndEnableErrorStep } from '~redux/sagas/extensions/extensionInstallAndEnable.ts'; import Toast from '~shared/Extensions/Toast/index.ts'; import { type AnyExtensionData } from '~types/extensions.ts'; @@ -75,7 +76,7 @@ export const useInstall = (extensionData: AnyExtensionData) => { const [isLoading, setIsLoading] = useState(false); - const showErrorToast = useCallback(() => { + const showInstallErrorToast = useCallback(() => { toast.error( { ); }, []); - const handleInstallSuccess = useCallback(async () => { - setIsLoading(true); - setWaitingForActionConfirmation(true); + const showInitialiseErrorToast = useCallback(() => { + toast.error( + , + ); + }, []); - try { - await waitForDbAfterExtensionAction({ - method: ExtensionMethods.INSTALL, - refetchExtensionData, - }); + const showSetUserRolesErrorToast = useCallback(() => { + toast.error( + , + ); + }, []); - toast.success( - , - ); + const handleInstallSuccess = useCallback( + async ({ + initialiseTransactionFailed, + setUserRolesTransactionFailed, + }: { + initialiseTransactionFailed?: boolean; + setUserRolesTransactionFailed?: boolean; + }) => { + setIsLoading(true); + setWaitingForActionConfirmation(true); + + try { + await waitForDbAfterExtensionAction({ + method: ExtensionMethods.INSTALL, + refetchExtensionData, + initialiseTransactionFailed, + setUserRolesTransactionFailed, + }); + + toast.success( + , + ); + + if (initialiseTransactionFailed) { + showInitialiseErrorToast(); + } - if (extensionData.initializationParams || extensionData.configurable) { - // Reset the form to the default values using most recent extension data - const updatedExtensionData = await refetchExtensionData(); - if (updatedExtensionData) { - reset(getExtensionSettingsDefaultValues(updatedExtensionData)); - setActiveTab(ExtensionDetailsPageTabId.Settings); + if (setUserRolesTransactionFailed) { + showSetUserRolesErrorToast(); } + + if (extensionData.initializationParams || extensionData.configurable) { + // Reset the form to the default values using most recent extension data + const updatedExtensionData = await refetchExtensionData(); + if (updatedExtensionData) { + reset(getExtensionSettingsDefaultValues(updatedExtensionData)); + setActiveTab(ExtensionDetailsPageTabId.Settings); + } + } + } catch { + showInstallErrorToast(); + } finally { + setIsLoading(false); + setWaitingForActionConfirmation(false); } - } catch { - showErrorToast(); - } finally { - setIsLoading(false); - setWaitingForActionConfirmation(false); - } - }, [ - extensionData.configurable, - extensionData.initializationParams, - refetchExtensionData, - reset, - setActiveTab, - setWaitingForActionConfirmation, - showErrorToast, - ]); - - const handleInstallError = useCallback(() => { - showErrorToast(); - }, [showErrorToast]); + }, + [ + extensionData.configurable, + extensionData.initializationParams, + refetchExtensionData, + reset, + setActiveTab, + setWaitingForActionConfirmation, + showInstallErrorToast, + showInitialiseErrorToast, + showSetUserRolesErrorToast, + ], + ); + + const handleInstallError = useCallback( + (error: any) => { + const { step } = error; + + if (step === ExtensionInstallAndEnableErrorStep.Initialise) { + handleInstallSuccess({ initialiseTransactionFailed: true }); + return; + } + + if (step === ExtensionInstallAndEnableErrorStep.SetUserRoles) { + handleInstallSuccess({ setUserRolesTransactionFailed: true }); + return; + } + + showInstallErrorToast(); + }, + [showInstallErrorToast, handleInstallSuccess], + ); return { handleInstallSuccess, diff --git a/src/components/frame/Extensions/pages/ExtensionDetailsPage/utils.tsx b/src/components/frame/Extensions/pages/ExtensionDetailsPage/utils.tsx index dcd6b8cb092..192cc12de7c 100644 --- a/src/components/frame/Extensions/pages/ExtensionDetailsPage/utils.tsx +++ b/src/components/frame/Extensions/pages/ExtensionDetailsPage/utils.tsx @@ -19,7 +19,15 @@ export const waitForDbAfterExtensionAction = ( latestVersion: number; } | { - method: Exclude; + method: ExtensionMethods.INSTALL; + initialiseTransactionFailed?: boolean; + setUserRolesTransactionFailed?: boolean; + } + | { + method: Exclude< + ExtensionMethods, + ExtensionMethods.UPGRADE | ExtensionMethods.INSTALL + >; } ), ) => { @@ -50,6 +58,22 @@ export const waitForDbAfterExtensionAction = ( switch (args.method) { case ExtensionMethods.INSTALL: { + if ( + extensionData?.autoEnableAfterInstall && + !!args.initialiseTransactionFailed + ) { + condition = !!extensionData; + break; + } + + if ( + extensionData?.autoEnableAfterInstall && + !!args.setUserRolesTransactionFailed + ) { + condition = !!extensionData.isEnabled; + break; + } + if (extensionData?.autoEnableAfterInstall) { condition = !!extensionData.isEnabled && @@ -77,7 +101,11 @@ export const waitForDbAfterExtensionAction = ( } case ExtensionMethods.ENABLE: { - condition = !!extensionData?.isInitialized; + // condition = !!extensionData?.isInitialized; + + condition = + !!extensionData?.isEnabled && + extensionData?.missingColonyPermissions.length === 0; break; } diff --git a/src/i18n/en.json b/src/i18n/en.json index 802819f333c..3f8661965c6 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -970,6 +970,8 @@ "extensionInstallAndEnable.toast.description.success": "The extension has been installed and enabled.", "extensionInstallAndEnable.toast.title.error": "Extension failed to install or enable", "extensionInstallAndEnable.toast.description.error": "Due to a transaction error, the extension was not installed or enabled.", + "extensionSetUserRoles.toast.title.error": "Failed to assign extension permissions", + "extensionSetUserRoles.toast.description.error": "Due to a transaction error, the extension was not assigned the permissions it needs to work.", "extensionSaveChanges.toast.title.success": "Updated", "extensionSaveChanges.toast.description.success": "The extension parameters have been successfully updated.", "extensionSaveChanges.toast.title.error": "Extension failed to update", diff --git a/src/redux/sagas/extensions/extensionInstallAndEnable.ts b/src/redux/sagas/extensions/extensionInstallAndEnable.ts index b91eebbec17..ae9f6baaf4e 100644 --- a/src/redux/sagas/extensions/extensionInstallAndEnable.ts +++ b/src/redux/sagas/extensions/extensionInstallAndEnable.ts @@ -29,6 +29,12 @@ import { getColonyManager, } from '../utils/index.ts'; +export enum ExtensionInstallAndEnableErrorStep { + InstallExtension = 'installExtension', + Initialise = 'initialise', + SetUserRoles = 'setUserRoles', +} + // Saga will attempt to // 1. Install extension // Then, if enabledAutomaticallyAfterInstall is true @@ -109,8 +115,14 @@ export function* extensionInstallAndEnable({ yield takeFrom(setUserRoles.channel, ActionTypes.TRANSACTION_CREATED); } - yield initiateTransaction(installExtension.id); - yield waitForTxResult(installExtension.channel); + try { + yield initiateTransaction(installExtension.id); + yield waitForTxResult(installExtension.channel); + } catch (error) { + // Declare an error step for the error handler + error.step = ExtensionInstallAndEnableErrorStep.InstallExtension; + throw error; + } // If not enabledAutomaticallyAfterInstall return success here if (!autoEnableAfterInstall) { @@ -158,8 +170,14 @@ export function* extensionInstallAndEnable({ const initParams = modifyParams(initializationParams, defaultParams); yield transactionSetParams(initialise.id, initParams); - yield initiateTransaction(initialise.id); - yield waitForTxResult(initialise.channel); + try { + yield initiateTransaction(initialise.id); + yield waitForTxResult(initialise.channel); + } catch (error) { + // Declare an error step for the error handler + error.step = ExtensionInstallAndEnableErrorStep.Initialise; + throw error; + } } const [permissionDomainId, childSkillIndex] = yield getPermissionProofs( @@ -181,8 +199,14 @@ export function* extensionInstallAndEnable({ bytes32Roles, ]); - yield initiateTransaction(setUserRoles.id); - yield waitForTxResult(setUserRoles.channel); + try { + yield initiateTransaction(setUserRoles.id); + yield waitForTxResult(setUserRoles.channel); + } catch (error) { + // Declare an error step for the error handler + error.step = ExtensionInstallAndEnableErrorStep.SetUserRoles; + throw error; + } yield put({ type: ActionTypes.EXTENSION_INSTALL_AND_ENABLE_SUCCESS,