From 6be8e86c45bd64d000bc95d2ef2d68220e930c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20G=C3=B3mez=20Morales?= Date: Thu, 28 Nov 2024 14:53:48 +0100 Subject: [PATCH] fix(editor): Handle source control initialization to prevent UI form crashing (#11776) --- ...ourceControlInitializationErrorMessage.vue | 17 +++++++++++++ packages/editor-ui/src/init.test.ts | 23 ++++++++++++++++++ packages/editor-ui/src/init.ts | 24 ++++++++++++++++--- .../src/plugins/i18n/locales/en.json | 3 +++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 packages/editor-ui/src/components/SourceControlInitializationErrorMessage.vue diff --git a/packages/editor-ui/src/components/SourceControlInitializationErrorMessage.vue b/packages/editor-ui/src/components/SourceControlInitializationErrorMessage.vue new file mode 100644 index 0000000000..f7c1bc7022 --- /dev/null +++ b/packages/editor-ui/src/components/SourceControlInitializationErrorMessage.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/editor-ui/src/init.test.ts b/packages/editor-ui/src/init.test.ts index d7b381e3f5..d4e366988b 100644 --- a/packages/editor-ui/src/init.test.ts +++ b/packages/editor-ui/src/init.test.ts @@ -8,6 +8,13 @@ import { createTestingPinia } from '@pinia/testing'; import { setActivePinia } from 'pinia'; import { useSettingsStore } from '@/stores/settings.store'; import { useVersionsStore } from '@/stores/versions.store'; +import { AxiosError } from 'axios'; + +const showMessage = vi.fn(); + +vi.mock('@/composables/useToast', () => ({ + useToast: () => ({ showMessage }), +})); vi.mock('@/stores/users.store', () => ({ useUsersStore: vi.fn().mockReturnValue({ initialize: vi.fn() }), @@ -108,5 +115,21 @@ describe('Init', () => { expect(cloudStoreSpy).toHaveBeenCalledTimes(1); }); + + it('should handle source control initialization error', async () => { + vi.mocked(useUsersStore).mockReturnValue({ currentUser: { id: '123' } } as ReturnType< + typeof useUsersStore + >); + vi.spyOn(sourceControlStore, 'getPreferences').mockRejectedValueOnce( + new AxiosError('Something went wrong', '404'), + ); + const consoleSpy = vi.spyOn(window.console, 'error'); + await initializeAuthenticatedFeatures(false); + expect(showMessage).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to initialize source control store', + expect.anything(), + ); + }); }); }); diff --git a/packages/editor-ui/src/init.ts b/packages/editor-ui/src/init.ts index dfdcbe42d2..8b3a69f820 100644 --- a/packages/editor-ui/src/init.ts +++ b/packages/editor-ui/src/init.ts @@ -1,3 +1,4 @@ +import { h } from 'vue'; import { useCloudPlanStore } from '@/stores/cloudPlan.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useRootStore } from '@/stores/root.store'; @@ -8,6 +9,9 @@ import { useExternalHooks } from '@/composables/useExternalHooks'; import { useVersionsStore } from '@/stores/versions.store'; import { useProjectsStore } from '@/stores/projects.store'; import { useRolesStore } from './stores/roles.store'; +import { useToast } from '@/composables/useToast'; +import { useI18n } from '@/composables/useI18n'; +import SourceControlInitializationErrorMessage from '@/components/SourceControlInitializationErrorMessage.vue'; let coreInitialized = false; let authenticatedFeaturesInitialized = false; @@ -41,8 +45,10 @@ export async function initializeCore() { /** * Initializes the features of the application that require an authenticated user */ -export async function initializeAuthenticatedFeatures() { - if (authenticatedFeaturesInitialized) { +export async function initializeAuthenticatedFeatures( + initialized: boolean = authenticatedFeaturesInitialized, +) { + if (initialized) { return; } @@ -51,6 +57,8 @@ export async function initializeAuthenticatedFeatures() { return; } + const i18n = useI18n(); + const toast = useToast(); const sourceControlStore = useSourceControlStore(); const settingsStore = useSettingsStore(); const rootStore = useRootStore(); @@ -60,7 +68,17 @@ export async function initializeAuthenticatedFeatures() { const rolesStore = useRolesStore(); if (sourceControlStore.isEnterpriseSourceControlEnabled) { - await sourceControlStore.getPreferences(); + try { + await sourceControlStore.getPreferences(); + } catch (e) { + toast.showMessage({ + title: i18n.baseText('settings.sourceControl.connection.error'), + message: h(SourceControlInitializationErrorMessage), + type: 'error', + duration: 0, + }); + console.error('Failed to initialize source control store', e); + } } if (settingsStore.isTemplatesEnabled) { diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index a9d0045c53..fd17df3433 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1881,6 +1881,9 @@ "settings.sourceControl.actionBox.description": "Use multiple instances for different environments (dev, prod, etc.), deploying between them via a Git repository.", "settings.sourceControl.actionBox.description.link": "More info", "settings.sourceControl.actionBox.buttonText": "See plans", + "settings.sourceControl.connection.error": "Source control failed to connect", + "settings.sourceControl.connection.error.message": "We couldn't find the repository connected to this instance. Please double-check your {link} on this instance.", + "settings.sourceControl.connection.error.link": "Git configuration", "settings.sourceControl.description": "Use multiple instances for different environments (dev, prod, etc.), deploying between them via a Git repository. {link}", "settings.sourceControl.description.link": "More info", "settings.sourceControl.gitConfig": "Git configuration",