From ba27c987dc2c23dc6f3e0e549518f2a42894aa3f Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 19 Jul 2024 08:35:36 -0400 Subject: [PATCH] refactor(editor): Migrate settings.store to composition API (no-changelog) (#10022) Co-authored-by: Elias Meire --- packages/editor-ui/src/__tests__/defaults.ts | 2 +- .../__tests__/Assignment.test.ts | 7 +- .../__tests__/AssignmentCollection.test.ts | 15 +- .../CredentialEdit/CredentialEdit.vue | 4 +- .../CredentialEdit/CredentialSharing.ee.vue | 2 +- .../src/components/EnterpriseEdition.ee.vue | 2 +- .../src/components/InviteUsersModal.vue | 6 +- .../components/MainHeader/WorkflowDetails.vue | 2 +- .../src/components/NodeDetailsView.vue | 2 +- .../editor-ui/src/components/VariablesRow.vue | 4 +- .../src/components/WorkflowSettings.vue | 2 +- .../src/components/WorkflowShareModal.ee.vue | 2 +- .../executions/ExecutionsFilter.vue | 4 +- .../editor-ui/src/composables/useDebugInfo.ts | 49 +- .../src/composables/useExecutionDebugging.ts | 4 +- .../src/stores/__tests__/posthog.test.ts | 2 +- .../editor-ui/src/stores/__tests__/ui.test.ts | 12 +- .../editor-ui/src/stores/auditLogs.store.ts | 4 +- .../editor-ui/src/stores/credentials.store.ts | 4 +- .../src/stores/externalSecrets.ee.store.ts | 4 +- .../editor-ui/src/stores/settings.store.ts | 786 +++++++++--------- .../src/stores/sourceControl.store.ts | 4 +- packages/editor-ui/src/stores/sso.store.ts | 4 +- .../editor-ui/src/stores/templates.store.ts | 18 +- .../src/stores/workflows.ee.store.ts | 2 +- .../checks/isEnterpriseFeatureEnabled.test.ts | 57 +- .../rbac/checks/isEnterpriseFeatureEnabled.ts | 4 +- .../utils/rbac/middleware/enterprise.test.ts | 42 +- .../editor-ui/src/views/CredentialsView.vue | 5 +- packages/editor-ui/src/views/NodeView.v2.vue | 4 +- packages/editor-ui/src/views/NodeView.vue | 6 +- .../src/views/SettingsLogStreamingView.vue | 2 +- .../editor-ui/src/views/SettingsUsersView.vue | 2 +- .../editor-ui/src/views/VariablesView.spec.ts | 13 +- .../editor-ui/src/views/VariablesView.vue | 4 +- .../editor-ui/src/views/WorkflowsView.vue | 2 +- .../views/__tests__/SettingsUsersView.test.ts | 9 +- packages/workflow/src/Interfaces.ts | 2 +- 38 files changed, 585 insertions(+), 514 deletions(-) diff --git a/packages/editor-ui/src/__tests__/defaults.ts b/packages/editor-ui/src/__tests__/defaults.ts index 2a1d029155..b6c22cbf7e 100644 --- a/packages/editor-ui/src/__tests__/defaults.ts +++ b/packages/editor-ui/src/__tests__/defaults.ts @@ -23,7 +23,7 @@ export const defaultSettings: IN8nUISettings = { logStreaming: false, debugInEditor: false, advancedExecutionFilters: false, - variables: true, + variables: false, sourceControl: false, auditLogs: false, showNonProdBanner: false, diff --git a/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts b/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts index 6d2f438afe..ba5617ec85 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts +++ b/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts @@ -2,9 +2,14 @@ import { createComponentRenderer } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; import Assignment from '../Assignment.vue'; +import { defaultSettings } from '@/__tests__/defaults'; +import { STORES } from '@/constants'; +import { merge } from 'lodash-es'; const DEFAULT_SETUP = { - pinia: createTestingPinia(), + pinia: createTestingPinia({ + initialState: { [STORES.SETTINGS]: { settings: merge({}, defaultSettings) } }, + }), props: { path: 'parameters.fields.0', modelValue: { diff --git a/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts b/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts index 3cb22cebbf..d066e6b218 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts +++ b/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts @@ -5,10 +5,16 @@ import userEvent from '@testing-library/user-event'; import { fireEvent, within } from '@testing-library/vue'; import * as workflowHelpers from '@/composables/useWorkflowHelpers'; import AssignmentCollection from '../AssignmentCollection.vue'; -import { createPinia, setActivePinia } from 'pinia'; +import { STORES } from '@/constants'; +import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils'; const DEFAULT_SETUP = { - pinia: createTestingPinia(), + pinia: createTestingPinia({ + initialState: { + [STORES.SETTINGS]: SETTINGS_STORE_DEFAULT_STATE, + }, + stubActions: false, + }), props: { path: 'parameters.fields', node: { @@ -97,10 +103,7 @@ describe('AssignmentCollection.vue', () => { }); it('can add assignments by drag and drop (and infer type)', async () => { - const pinia = createPinia(); - setActivePinia(pinia); - - const { getByTestId, findAllByTestId } = renderComponent({ pinia }); + const { getByTestId, findAllByTestId } = renderComponent(); const dropArea = getByTestId('drop-area'); await dropAssignment({ key: 'boolKey', value: true, dropArea }); diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index ef905d9d7c..28f67d64ea 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -780,7 +780,7 @@ async function saveCredential(): Promise { }; if ( - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) && + settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing] && credentialData.value.sharedWithProjects ) { credentialDetails.sharedWithProjects = credentialData.value @@ -817,7 +817,7 @@ async function saveCredential(): Promise { type: 'success', }); } else { - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { credentialDetails.sharedWithProjects = credentialData.value .sharedWithProjects as ProjectSharingData[]; } diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 69b3f8a79b..133b00ca4d 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -156,7 +156,7 @@ export default defineComponent({ ]; }, isSharingEnabled(): boolean { - return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); + return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]; }, credentialOwnerName(): string { return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`); diff --git a/packages/editor-ui/src/components/EnterpriseEdition.ee.vue b/packages/editor-ui/src/components/EnterpriseEdition.ee.vue index 9077f0137e..a2dec56888 100644 --- a/packages/editor-ui/src/components/EnterpriseEdition.ee.vue +++ b/packages/editor-ui/src/components/EnterpriseEdition.ee.vue @@ -23,7 +23,7 @@ export default defineComponent({ ...mapStores(useSettingsStore), canAccess(): boolean { return this.features.reduce((acc: boolean, feature) => { - return acc && !!this.settingsStore.isEnterpriseFeatureEnabled(feature); + return acc && !!this.settingsStore.isEnterpriseFeatureEnabled[feature]; }, true); }, }, diff --git a/packages/editor-ui/src/components/InviteUsersModal.vue b/packages/editor-ui/src/components/InviteUsersModal.vue index 8f245b9797..dab656c953 100644 --- a/packages/editor-ui/src/components/InviteUsersModal.vue +++ b/packages/editor-ui/src/components/InviteUsersModal.vue @@ -190,9 +190,9 @@ export default defineComponent({ : []; }, isAdvancedPermissionsEnabled(): boolean { - return this.settingsStore.isEnterpriseFeatureEnabled( - EnterpriseEditionFeature.AdvancedPermissions, - ); + return this.settingsStore.isEnterpriseFeatureEnabled[ + EnterpriseEditionFeature.AdvancedPermissions + ]; }, }, methods: { diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index f85708d45e..c80ad97994 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -208,7 +208,7 @@ const workflowMenuItems = computed(() => { }); const isWorkflowHistoryFeatureEnabled = computed(() => { - return settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.WorkflowHistory); + return settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.WorkflowHistory]; }); const workflowHistoryRoute = computed<{ name: string; params: { workflowId: string } }>(() => { diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index a278afdb53..54a0336aec 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -439,7 +439,7 @@ const foreignCredentials = computed(() => { const usedCredentials = workflowsStore.usedCredentials; const foreignCredentialsArray: string[] = []; - if (credentials && settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + if (credentials && settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { Object.values(credentials).forEach((credential) => { if ( credential.id && diff --git a/packages/editor-ui/src/components/VariablesRow.vue b/packages/editor-ui/src/components/VariablesRow.vue index 75702cadad..3f45bda4f6 100644 --- a/packages/editor-ui/src/components/VariablesRow.vue +++ b/packages/editor-ui/src/components/VariablesRow.vue @@ -50,8 +50,8 @@ const valueInputRef = ref(); const usage = ref(`$vars.${props.data.name}`); -const isFeatureEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables), +const isFeatureEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables], ); onMounted(() => { diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 5911e99f6a..85a26bee70 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -480,7 +480,7 @@ export default defineComponent({ return this.usersStore.currentUser; }, isSharingEnabled(): boolean { - return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); + return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]; }, workflowOwnerName(): string { const fallback = this.$locale.baseText( diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue index 3b25152eb3..8aa11f4b5a 100644 --- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue +++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue @@ -197,7 +197,7 @@ export default defineComponent({ useRolesStore, ), isSharingEnabled(): boolean { - return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); + return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]; }, modalTitle(): string { if (this.isHomeTeamProject) { diff --git a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue index 4155ba9433..b3f59e3424 100644 --- a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue +++ b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue @@ -43,8 +43,8 @@ const debouncedEmit = debounce(emit, { }); const isCustomDataFilterTracked = ref(false); -const isAdvancedExecutionFilterEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AdvancedExecutionFilters), +const isAdvancedExecutionFilterEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedExecutionFilters], ); const showTags = computed(() => false); diff --git a/packages/editor-ui/src/composables/useDebugInfo.ts b/packages/editor-ui/src/composables/useDebugInfo.ts index 89acc06c61..ed573066a7 100644 --- a/packages/editor-ui/src/composables/useDebugInfo.ts +++ b/packages/editor-ui/src/composables/useDebugInfo.ts @@ -1,3 +1,4 @@ +import { useRootStore } from '@/stores/root.store'; import { useSettingsStore } from '@/stores/settings.store'; import type { WorkflowSettings } from 'n8n-workflow'; @@ -38,60 +39,62 @@ type DebugInfo = { }; export function useDebugInfo() { - const store = useSettingsStore(); + const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const coreInfo = () => { return { - n8nVersion: store.versionCli, + n8nVersion: rootStore.versionCli, platform: - store.isDocker && store.deploymentType === 'cloud' + settingsStore.isDocker && settingsStore.deploymentType === 'cloud' ? 'docker (cloud)' - : store.isDocker + : settingsStore.isDocker ? 'docker (self-hosted)' : 'npm', - nodeJsVersion: store.nodeJsVersion, + nodeJsVersion: settingsStore.nodeJsVersion, database: - store.databaseType === 'postgresdb' + settingsStore.databaseType === 'postgresdb' ? 'postgres' - : store.databaseType === 'mysqldb' + : settingsStore.databaseType === 'mysqldb' ? 'mysql' - : store.databaseType, - executionMode: store.isQueueModeEnabled ? 'scaling' : 'regular', - concurrency: store.settings.concurrency, - license: store.isCommunityPlan + : settingsStore.databaseType, + executionMode: settingsStore.isQueueModeEnabled ? 'scaling' : 'regular', + concurrency: settingsStore.settings.concurrency, + license: settingsStore.isCommunityPlan ? 'community' - : store.settings.license.environment === 'production' + : settingsStore.settings.license.environment === 'production' ? 'enterprise (production)' : 'enterprise (sandbox)', - consumerId: store.consumerId, + consumerId: settingsStore.consumerId, } as const; }; const storageInfo = (): DebugInfo['storage'] => { return { - success: store.saveDataSuccessExecution, - error: store.saveDataErrorExecution, - progress: store.saveDataProgressExecution, - manual: store.saveManualExecutions, - binaryMode: store.binaryDataMode === 'default' ? 'memory' : store.binaryDataMode, + success: settingsStore.saveDataSuccessExecution, + error: settingsStore.saveDataErrorExecution, + progress: settingsStore.saveDataProgressExecution, + manual: settingsStore.saveManualExecutions, + binaryMode: + settingsStore.binaryDataMode === 'default' ? 'memory' : settingsStore.binaryDataMode, }; }; const pruningInfo = () => { - if (!store.pruning.isEnabled) return { enabled: false } as const; + if (!settingsStore.pruning.isEnabled) return { enabled: false } as const; return { enabled: true, - maxAge: `${store.pruning.maxAge} hours`, - maxCount: `${store.pruning.maxCount} executions`, + maxAge: `${settingsStore.pruning.maxAge} hours`, + maxCount: `${settingsStore.pruning.maxCount} executions`, } as const; }; const securityInfo = () => { const info: DebugInfo['security'] = {}; - if (!store.security.blockFileAccessToN8nFiles) info.blockFileAccessToN8nFiles = false; - if (!store.security.secureCookie) info.secureCookie = false; + if (!settingsStore.security.blockFileAccessToN8nFiles) info.blockFileAccessToN8nFiles = false; + if (!settingsStore.security.secureCookie) info.secureCookie = false; if (Object.keys(info).length === 0) return; diff --git a/packages/editor-ui/src/composables/useExecutionDebugging.ts b/packages/editor-ui/src/composables/useExecutionDebugging.ts index b137468ad6..05d7144fef 100644 --- a/packages/editor-ui/src/composables/useExecutionDebugging.ts +++ b/packages/editor-ui/src/composables/useExecutionDebugging.ts @@ -28,8 +28,8 @@ export const useExecutionDebugging = () => { const settingsStore = useSettingsStore(); const uiStore = useUIStore(); - const isDebugEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.DebugInEditor), + const isDebugEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DebugInEditor], ); const applyExecutionData = async (executionId: string): Promise => { diff --git a/packages/editor-ui/src/stores/__tests__/posthog.test.ts b/packages/editor-ui/src/stores/__tests__/posthog.test.ts index f06e053935..75f0c40b29 100644 --- a/packages/editor-ui/src/stores/__tests__/posthog.test.ts +++ b/packages/editor-ui/src/stores/__tests__/posthog.test.ts @@ -44,7 +44,7 @@ function setCurrentUser() { } function resetStores() { - useSettingsStore().$reset(); + useSettingsStore().reset(); useUsersStore().reset(); } diff --git a/packages/editor-ui/src/stores/__tests__/ui.test.ts b/packages/editor-ui/src/stores/__tests__/ui.test.ts index 5ca85a010b..ace9ab3bca 100644 --- a/packages/editor-ui/src/stores/__tests__/ui.test.ts +++ b/packages/editor-ui/src/stores/__tests__/ui.test.ts @@ -3,9 +3,9 @@ import { generateUpgradeLinkUrl, useUIStore } from '@/stores/ui.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useUsersStore } from '@/stores/users.store'; import { merge } from 'lodash-es'; -import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils'; import { useCloudPlanStore } from '@/stores/cloudPlan.store'; import * as cloudPlanApi from '@/api/cloudPlans'; +import { defaultSettings } from '../../__tests__/defaults'; import { getTrialExpiredUserResponse, getTrialingUserResponse, @@ -34,7 +34,7 @@ function setUser(role: IRole) { function setupOwnerAndCloudDeployment() { setUser(ROLE.Owner); settingsStore.setSettings( - merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { + merge({}, defaultSettings, { n8nMetadata: { userId: '1', }, @@ -103,7 +103,7 @@ describe('UI store', () => { setUser(role as IRole); settingsStore.setSettings( - merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { + merge({}, defaultSettings, { deployment: { type, }, @@ -123,7 +123,7 @@ describe('UI store', () => { it('should add non-production license banner to stack based on enterprise settings', () => { settingsStore.setSettings( - merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { + merge({}, defaultSettings, { enterprise: { showNonProdBanner: true, }, @@ -134,7 +134,7 @@ describe('UI store', () => { it("should add V1 banner to stack if it's not dismissed", () => { settingsStore.setSettings( - merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { + merge({}, defaultSettings, { versionCli: '1.0.0', }), ); @@ -143,7 +143,7 @@ describe('UI store', () => { it("should not add V1 banner to stack if it's dismissed", () => { settingsStore.setSettings( - merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { + merge({}, defaultSettings, { versionCli: '1.0.0', banners: { dismissed: ['V1'], diff --git a/packages/editor-ui/src/stores/auditLogs.store.ts b/packages/editor-ui/src/stores/auditLogs.store.ts index 12c42dcfd0..fc121a00ff 100644 --- a/packages/editor-ui/src/stores/auditLogs.store.ts +++ b/packages/editor-ui/src/stores/auditLogs.store.ts @@ -6,8 +6,8 @@ import { useSettingsStore } from '@/stores/settings.store'; export const useAuditLogsStore = defineStore('auditLogs', () => { const settingsStore = useSettingsStore(); - const isEnterpriseAuditLogsFeatureEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AuditLogs), + const isEnterpriseAuditLogsFeatureEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AuditLogs], ); return { diff --git a/packages/editor-ui/src/stores/credentials.store.ts b/packages/editor-ui/src/stores/credentials.store.ts index 31fe3901a8..e675946614 100644 --- a/packages/editor-ui/src/stores/credentials.store.ts +++ b/packages/editor-ui/src/stores/credentials.store.ts @@ -314,7 +314,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => { projectId, ); - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { upsertCredential(credential); if (data.sharedWithProjects) { await setCredentialSharedWith({ @@ -389,7 +389,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => { sharedWithProjects: ProjectSharingData[]; credentialId: string; }): Promise => { - if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + if (useSettingsStore().isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { await credentialsEeApi.setCredentialSharedWith( useRootStore().restApiContext, payload.credentialId, diff --git a/packages/editor-ui/src/stores/externalSecrets.ee.store.ts b/packages/editor-ui/src/stores/externalSecrets.ee.store.ts index 92071fa30c..8dfff7d693 100644 --- a/packages/editor-ui/src/stores/externalSecrets.ee.store.ts +++ b/packages/editor-ui/src/stores/externalSecrets.ee.store.ts @@ -19,8 +19,8 @@ export const useExternalSecretsStore = defineStore('externalSecrets', () => { connectionState: {} as Record, }); - const isEnterpriseExternalSecretsEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets), + const isEnterpriseExternalSecretsEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets], ); const secrets = computed(() => state.secrets); diff --git a/packages/editor-ui/src/stores/settings.store.ts b/packages/editor-ui/src/stores/settings.store.ts index 53bbab6a08..1739af1bcb 100644 --- a/packages/editor-ui/src/stores/settings.store.ts +++ b/packages/editor-ui/src/stores/settings.store.ts @@ -1,27 +1,15 @@ -import { createApiKey, deleteApiKey, getApiKey } from '@/api/api-keys'; -import { - getLdapConfig, - getLdapSynchronizations, - runLdapSync, - testLdapConnection, - updateLdapConfig, -} from '@/api/ldap'; -import { getSettings, submitContactInfo } from '@/api/settings'; +import * as publicApiApi from '@/api/api-keys'; +import * as ldapApi from '@/api/ldap'; +import * as settingsApi from '@/api/settings'; import { testHealthEndpoint } from '@/api/templates'; -import type { - EnterpriseEditionFeatureValue, - ILdapConfig, - IN8nPromptResponse, - ISettingsState, -} from '@/Interface'; +import type { ILdapConfig } from '@/Interface'; import { STORES, INSECURE_CONNECTION_WARNING } from '@/constants'; import { UserManagementAuthenticationMethod } from '@/Interface'; import type { IDataObject, - LogLevel, IN8nUISettings, - ITelemetrySettings, WorkflowSettings, + IUserManagementSettings, } from 'n8n-workflow'; import { ExpressionEvaluatorProxy } from 'n8n-workflow'; import { defineStore } from 'pinia'; @@ -33,382 +21,422 @@ import { makeRestApiRequest } from '@/utils/apiUtils'; import { useTitleChange } from '@/composables/useTitleChange'; import { useToast } from '@/composables/useToast'; import { i18n } from '@/plugins/i18n'; +import { computed, ref } from 'vue'; -export const useSettingsStore = defineStore(STORES.SETTINGS, { - state: (): ISettingsState => ({ - initialized: false, - settings: {} as IN8nUISettings, - userManagement: { - quota: -1, - showSetupOnFirstLoad: false, - smtpSetup: false, - authenticationMethod: UserManagementAuthenticationMethod.Email, - }, - templatesEndpointHealthy: false, - api: { +export const useSettingsStore = defineStore(STORES.SETTINGS, () => { + const initialized = ref(false); + const settings = ref({} as IN8nUISettings); + const userManagement = ref({ + quota: -1, + showSetupOnFirstLoad: false, + smtpSetup: false, + authenticationMethod: UserManagementAuthenticationMethod.Email, + }); + const templatesEndpointHealthy = ref(false); + const api = ref({ + enabled: false, + latestVersion: 0, + path: '/', + swaggerUi: { enabled: false, - latestVersion: 0, - path: '/', - swaggerUi: { + }, + }); + const ldap = ref({ loginLabel: '', loginEnabled: false }); + const saml = ref({ loginLabel: '', loginEnabled: false }); + const mfa = ref({ enabled: false }); + const saveDataErrorExecution = ref('all'); + const saveDataSuccessExecution = ref('all'); + const saveManualExecutions = ref(false); + const saveDataProgressExecution = ref(false); + + const isDocker = computed(() => settings.value?.isDocker ?? false); + + const databaseType = computed(() => settings.value?.databaseType); + + const planName = computed(() => settings.value?.license.planName ?? 'Community'); + + const consumerId = computed(() => settings.value?.license.consumerId); + + const binaryDataMode = computed(() => settings.value?.binaryDataMode); + + const pruning = computed(() => settings.value?.pruning); + + const security = computed(() => ({ + blockFileAccessToN8nFiles: settings.value.security.blockFileAccessToN8nFiles, + secureCookie: settings.value.authCookie.secure, + })); + + const isEnterpriseFeatureEnabled = computed(() => settings.value.enterprise); + + const nodeJsVersion = computed(() => settings.value.nodeJsVersion); + + const concurrency = computed(() => settings.value.concurrency); + + const isPublicApiEnabled = computed(() => api.value.enabled); + + const isSwaggerUIEnabled = computed(() => api.value.swaggerUi.enabled); + + const isPreviewMode = computed(() => settings.value.previewMode); + + const publicApiLatestVersion = computed(() => api.value.latestVersion); + + const publicApiPath = computed(() => api.value.path); + + const isLdapLoginEnabled = computed(() => ldap.value.loginEnabled); + + const ldapLoginLabel = computed(() => ldap.value.loginLabel); + + const isSamlLoginEnabled = computed(() => saml.value.loginEnabled); + + const showSetupPage = computed(() => userManagement.value.showSetupOnFirstLoad); + + const deploymentType = computed(() => settings.value.deployment?.type || 'default'); + + const isDesktopDeployment = computed(() => + settings.value.deployment?.type.startsWith('desktop_'), + ); + + const isCloudDeployment = computed(() => settings.value.deployment?.type === 'cloud'); + + const isSmtpSetup = computed(() => userManagement.value.smtpSetup); + + const isPersonalizationSurveyEnabled = computed( + () => settings.value.telemetry?.enabled && settings.value.personalizationSurveyEnabled, + ); + + const telemetry = computed(() => settings.value.telemetry); + + const logLevel = computed(() => settings.value.logLevel); + + const isTelemetryEnabled = computed( + () => settings.value.telemetry && settings.value.telemetry.enabled, + ); + + const isMfaFeatureEnabled = computed(() => mfa.value.enabled); + + const areTagsEnabled = computed(() => + settings.value.workflowTagsDisabled !== undefined ? !settings.value.workflowTagsDisabled : true, + ); + + const isHiringBannerEnabled = computed(() => settings.value.hiringBannerEnabled); + + const isTemplatesEnabled = computed(() => + Boolean(settings.value.templates && settings.value.templates.enabled), + ); + + const isTemplatesEndpointReachable = computed(() => templatesEndpointHealthy.value); + + const templatesHost = computed(() => settings.value.templates.host); + + const pushBackend = computed(() => settings.value.pushBackend); + + const isCommunityNodesFeatureEnabled = computed(() => settings.value.communityNodesEnabled); + + const isNpmAvailable = computed(() => settings.value.isNpmAvailable); + + const allowedModules = computed(() => settings.value.allowedModules); + + const isQueueModeEnabled = computed(() => settings.value.executionMode === 'queue'); + + const isWorkerViewAvailable = computed(() => !!settings.value.enterprise?.workerView); + + const workflowCallerPolicyDefaultOption = computed( + () => settings.value.workflowCallerPolicyDefaultOption, + ); + + const isDefaultAuthenticationSaml = computed( + () => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Saml, + ); + + const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []); + + const isBelowUserQuota = computed( + () => + userManagement.value.quota === -1 || + userManagement.value.quota > useUsersStore().allUsers.length, + ); + + const isCommunityPlan = computed(() => planName.value.toLowerCase() === 'community'); + + const isDevRelease = computed(() => settings.value.releaseChannel === 'dev'); + + const setSettings = (newSettings: IN8nUISettings) => { + settings.value = newSettings; + userManagement.value = newSettings.userManagement; + if (userManagement.value) { + userManagement.value.showSetupOnFirstLoad = + !!settings.value.userManagement.showSetupOnFirstLoad; + } + api.value = settings.value.publicApi; + if (settings.value.sso?.ldap) { + ldap.value.loginEnabled = settings.value.sso.ldap.loginEnabled; + ldap.value.loginLabel = settings.value.sso.ldap.loginLabel; + } + if (settings.value.sso?.saml) { + saml.value.loginEnabled = settings.value.sso.saml.loginEnabled; + saml.value.loginLabel = settings.value.sso.saml.loginLabel; + } + + mfa.value.enabled = settings.value.mfa?.enabled; + + if (settings.value.enterprise?.showNonProdBanner) { + useUIStore().pushBannerToStack('NON_PRODUCTION_LICENSE'); + } + + if (settings.value.versionCli) { + useRootStore().setVersionCli(settings.value.versionCli); + } + + if ( + settings.value.authCookie.secure && + location.protocol === 'http:' && + !['localhost', '127.0.0.1'].includes(location.hostname) + ) { + document.write(INSECURE_CONNECTION_WARNING); + return; + } + + const isV1BannerDismissedPermanently = (settings.value.banners?.dismissed || []).includes('V1'); + if (!isV1BannerDismissedPermanently && settings.value.versionCli.startsWith('1.')) { + useUIStore().pushBannerToStack('V1'); + } + }; + + const setAllowedModules = (allowedModules: IN8nUISettings['allowedModules']) => { + settings.value.allowedModules = allowedModules; + }; + + const setSaveDataErrorExecution = (newValue: WorkflowSettings.SaveDataExecution) => { + saveDataErrorExecution.value = newValue; + }; + + const setSaveDataSuccessExecution = (newValue: WorkflowSettings.SaveDataExecution) => { + saveDataSuccessExecution.value = newValue; + }; + + const setSaveManualExecutions = (newValue: boolean) => { + saveManualExecutions.value = newValue; + }; + + const setSaveDataProgressExecution = (newValue: boolean) => { + saveDataProgressExecution.value = newValue; + }; + + const getSettings = async () => { + const rootStore = useRootStore(); + const fetchedSettings = await settingsApi.getSettings(rootStore.restApiContext); + setSettings(fetchedSettings); + settings.value.communityNodesEnabled = fetchedSettings.communityNodesEnabled; + setAllowedModules(fetchedSettings.allowedModules); + setSaveDataErrorExecution(fetchedSettings.saveDataErrorExecution); + setSaveDataSuccessExecution(fetchedSettings.saveDataSuccessExecution); + setSaveDataProgressExecution(fetchedSettings.saveExecutionProgress); + setSaveManualExecutions(fetchedSettings.saveManualExecutions); + + rootStore.setUrlBaseWebhook(fetchedSettings.urlBaseWebhook); + rootStore.setUrlBaseEditor(fetchedSettings.urlBaseEditor); + rootStore.setEndpointForm(fetchedSettings.endpointForm); + rootStore.setEndpointFormTest(fetchedSettings.endpointFormTest); + rootStore.setEndpointFormWaiting(fetchedSettings.endpointFormWaiting); + rootStore.setEndpointWebhook(fetchedSettings.endpointWebhook); + rootStore.setEndpointWebhookTest(fetchedSettings.endpointWebhookTest); + rootStore.setTimezone(fetchedSettings.timezone); + rootStore.setExecutionTimeout(fetchedSettings.executionTimeout); + rootStore.setMaxExecutionTimeout(fetchedSettings.maxExecutionTimeout); + rootStore.setInstanceId(fetchedSettings.instanceId); + rootStore.setOauthCallbackUrls(fetchedSettings.oauthCallbackUrls); + rootStore.setN8nMetadata(fetchedSettings.n8nMetadata || {}); + rootStore.setDefaultLocale(fetchedSettings.defaultLocale); + rootStore.setIsNpmAvailable(fetchedSettings.isNpmAvailable); + rootStore.setBinaryDataMode(fetchedSettings.binaryDataMode); + useVersionsStore().setVersionNotificationSettings(fetchedSettings.versionNotifications); + }; + + const initialize = async () => { + if (initialized.value) { + return; + } + + const { showToast } = useToast(); + try { + await getSettings(); + + ExpressionEvaluatorProxy.setEvaluator(settings.value.expressions.evaluator); + + // Re-compute title since settings are now available + useTitleChange().titleReset(); + + initialized.value = true; + } catch (e) { + showToast({ + title: i18n.baseText('startupError'), + message: i18n.baseText('startupError.message'), + type: 'error', + duration: 0, + dangerouslyUseHTMLString: true, + }); + + throw e; + } + }; + + const stopShowingSetupPage = () => { + userManagement.value.showSetupOnFirstLoad = false; + }; + + const disableTemplates = () => { + settings.value = { + ...settings.value, + templates: { + ...settings.value.templates, enabled: false, }, - }, - ldap: { - loginLabel: '', - loginEnabled: false, - }, - saml: { - loginLabel: '', - loginEnabled: false, - }, - mfa: { - enabled: false, - }, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - saveManualExecutions: false, - saveDataProgressExecution: false, - }), - getters: { - isDocker(): boolean { - return this.settings.isDocker; - }, - databaseType(): 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb' { - return this.settings.databaseType; - }, - planName(): string { - return this.settings.license?.planName ?? 'Community'; - }, - isCommunityPlan(): boolean { - return this.planName.toLowerCase() === 'community'; - }, - consumerId(): string { - return this.settings.license?.consumerId ?? 'unknown'; - }, - binaryDataMode(): 'default' | 'filesystem' | 's3' { - return this.settings.binaryDataMode; - }, - pruning(): { isEnabled: boolean; maxAge: number; maxCount: number } { - return this.settings.pruning; - }, - security(): { - blockFileAccessToN8nFiles: boolean; - secureCookie: boolean; - } { - return { - blockFileAccessToN8nFiles: this.settings.security.blockFileAccessToN8nFiles, - secureCookie: this.settings.authCookie.secure, - }; - }, - isEnterpriseFeatureEnabled() { - return (feature: EnterpriseEditionFeatureValue): boolean => - Boolean(this.settings.enterprise?.[feature]); - }, + }; + }; - versionCli(): string { - return this.settings.versionCli; - }, - nodeJsVersion(): string { - return this.settings.nodeJsVersion; - }, - concurrency(): number { - return this.settings.concurrency; - }, - isPublicApiEnabled(): boolean { - return this.api.enabled; - }, - isSwaggerUIEnabled(): boolean { - return this.api.swaggerUi.enabled; - }, - isPreviewMode(): boolean { - return this.settings.previewMode; - }, - publicApiLatestVersion(): number { - return this.api.latestVersion; - }, - publicApiPath(): string { - return this.api.path; - }, - isLdapLoginEnabled(): boolean { - return this.ldap.loginEnabled; - }, - ldapLoginLabel(): string { - return this.ldap.loginLabel; - }, - isSamlLoginEnabled(): boolean { - return this.saml.loginEnabled; - }, - samlLoginLabel(): string { - return this.saml.loginLabel; - }, - showSetupPage(): boolean { - return this.userManagement.showSetupOnFirstLoad === true; - }, - deploymentType(): string { - return this.settings.deployment?.type || 'default'; - }, - isDesktopDeployment(): boolean { - if (!this.settings.deployment) { - return false; - } - return this.settings.deployment?.type.startsWith('desktop_'); - }, - isCloudDeployment(): boolean { - return this.settings.deployment?.type === 'cloud'; - }, - isSmtpSetup(): boolean { - return this.userManagement.smtpSetup; - }, - isPersonalizationSurveyEnabled(): boolean { - return ( - this.settings.telemetry && - this.settings.telemetry.enabled && - this.settings.personalizationSurveyEnabled + const submitContactInfo = async (email: string) => { + try { + const usersStore = useUsersStore(); + return await settingsApi.submitContactInfo( + settings.value.instanceId, + usersStore.currentUserId || '', + email, ); - }, - telemetry(): ITelemetrySettings { - return this.settings.telemetry; - }, - logLevel(): LogLevel { - return this.settings.logLevel; - }, - isTelemetryEnabled(): boolean { - return this.settings.telemetry && this.settings.telemetry.enabled; - }, - isMfaFeatureEnabled(): boolean { - return this.settings?.mfa?.enabled; - }, - areTagsEnabled(): boolean { - return this.settings.workflowTagsDisabled !== undefined - ? !this.settings.workflowTagsDisabled - : true; - }, - isHiringBannerEnabled(): boolean { - return this.settings.hiringBannerEnabled; - }, - isTemplatesEnabled(): boolean { - return Boolean(this.settings.templates && this.settings.templates.enabled); - }, - isTemplatesEndpointReachable(): boolean { - return this.templatesEndpointHealthy; - }, - templatesHost(): string { - return this.settings.templates.host; - }, - pushBackend(): IN8nUISettings['pushBackend'] { - return this.settings.pushBackend; - }, - isCommunityNodesFeatureEnabled(): boolean { - return this.settings.communityNodesEnabled; - }, - isNpmAvailable(): boolean { - return this.settings.isNpmAvailable; - }, - allowedModules(): { builtIn?: string[]; external?: string[] } { - return this.settings.allowedModules; - }, - isQueueModeEnabled(): boolean { - return this.settings.executionMode === 'queue'; - }, - isWorkerViewAvailable(): boolean { - return !!this.settings.enterprise?.workerView; - }, - workflowCallerPolicyDefaultOption(): WorkflowSettings.CallerPolicy { - return this.settings.workflowCallerPolicyDefaultOption; - }, - isDefaultAuthenticationSaml(): boolean { - return this.userManagement.authenticationMethod === UserManagementAuthenticationMethod.Saml; - }, - permanentlyDismissedBanners(): string[] { - return this.settings.banners?.dismissed ?? []; - }, - isBelowUserQuota(): boolean { - const userStore = useUsersStore(); - return ( - this.userManagement.quota === -1 || this.userManagement.quota > userStore.allUsers.length - ); - }, - isDevRelease(): boolean { - return this.settings.releaseChannel === 'dev'; - }, - }, - actions: { - async initialize() { - if (this.initialized) { - return; - } + } catch (error) { + return; + } + }; - const { showToast } = useToast(); - try { - await this.getSettings(); + const testTemplatesEndpoint = async () => { + const timeout = new Promise((_, reject) => setTimeout(() => reject(), 2000)); + await Promise.race([testHealthEndpoint(templatesHost.value), timeout]); + templatesEndpointHealthy.value = true; + }; - ExpressionEvaluatorProxy.setEvaluator(this.settings.expressions.evaluator); + const getApiKey = async () => { + const rootStore = useRootStore(); + const { apiKey } = await publicApiApi.getApiKey(rootStore.restApiContext); + return apiKey; + }; - // Re-compute title since settings are now available - useTitleChange().titleReset(); + const createApiKey = async () => { + const rootStore = useRootStore(); + const { apiKey } = await publicApiApi.createApiKey(rootStore.restApiContext); + return apiKey; + }; - this.initialized = true; - } catch (e) { - showToast({ - title: i18n.baseText('startupError'), - message: i18n.baseText('startupError.message'), - type: 'error', - duration: 0, - dangerouslyUseHTMLString: true, - }); + const deleteApiKey = async () => { + const rootStore = useRootStore(); + await publicApiApi.deleteApiKey(rootStore.restApiContext); + }; - throw e; - } - }, - setSettings(settings: IN8nUISettings): void { - this.settings = settings; - this.userManagement = settings.userManagement; - if (this.userManagement) { - this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad; - } - this.api = settings.publicApi; - if (settings.sso?.ldap) { - this.ldap.loginEnabled = settings.sso.ldap.loginEnabled; - this.ldap.loginLabel = settings.sso.ldap.loginLabel; - } - if (settings.sso?.saml) { - this.saml.loginEnabled = settings.sso.saml.loginEnabled; - this.saml.loginLabel = settings.sso.saml.loginLabel; - } - if (settings.enterprise?.showNonProdBanner) { - useUIStore().pushBannerToStack('NON_PRODUCTION_LICENSE'); - } - if (settings.versionCli) { - useRootStore().setVersionCli(settings.versionCli); - } + const getLdapConfig = async () => { + const rootStore = useRootStore(); + return await ldapApi.getLdapConfig(rootStore.restApiContext); + }; - if ( - settings.authCookie.secure && - location.protocol === 'http:' && - !['localhost', '127.0.0.1'].includes(location.hostname) - ) { - document.write(INSECURE_CONNECTION_WARNING); - return; - } + const getLdapSynchronizations = async (pagination: { page: number }) => { + const rootStore = useRootStore(); + return await ldapApi.getLdapSynchronizations(rootStore.restApiContext, pagination); + }; - const isV1BannerDismissedPermanently = (settings.banners?.dismissed || []).includes('V1'); - if (!isV1BannerDismissedPermanently && useRootStore().versionCli.startsWith('1.')) { - useUIStore().pushBannerToStack('V1'); - } - }, - async getSettings(): Promise { - const rootStore = useRootStore(); - const settings = await getSettings(rootStore.restApiContext); + const testLdapConnection = async () => { + const rootStore = useRootStore(); + return await ldapApi.testLdapConnection(rootStore.restApiContext); + }; - this.setSettings(settings); - this.settings.communityNodesEnabled = settings.communityNodesEnabled; - this.setAllowedModules(settings.allowedModules); - this.setSaveDataErrorExecution(settings.saveDataErrorExecution); - this.setSaveDataSuccessExecution(settings.saveDataSuccessExecution); - this.setSaveDataProgressExecution(settings.saveExecutionProgress); - this.setSaveManualExecutions(settings.saveManualExecutions); + const updateLdapConfig = async (ldapConfig: ILdapConfig) => { + const rootStore = useRootStore(); + return await ldapApi.updateLdapConfig(rootStore.restApiContext, ldapConfig); + }; - rootStore.setUrlBaseWebhook(settings.urlBaseWebhook); - rootStore.setUrlBaseEditor(settings.urlBaseEditor); - rootStore.setEndpointForm(settings.endpointForm); - rootStore.setEndpointFormTest(settings.endpointFormTest); - rootStore.setEndpointFormWaiting(settings.endpointFormWaiting); - rootStore.setEndpointWebhook(settings.endpointWebhook); - rootStore.setEndpointWebhookTest(settings.endpointWebhookTest); - rootStore.setTimezone(settings.timezone); - rootStore.setExecutionTimeout(settings.executionTimeout); - rootStore.setMaxExecutionTimeout(settings.maxExecutionTimeout); - rootStore.setVersionCli(settings.versionCli); - rootStore.setInstanceId(settings.instanceId); - rootStore.setOauthCallbackUrls(settings.oauthCallbackUrls); - rootStore.setN8nMetadata(settings.n8nMetadata || {}); - rootStore.setDefaultLocale(settings.defaultLocale); - rootStore.setIsNpmAvailable(settings.isNpmAvailable); - rootStore.setBinaryDataMode(settings.binaryDataMode); + const runLdapSync = async (data: IDataObject) => { + const rootStore = useRootStore(); + return await ldapApi.runLdapSync(rootStore.restApiContext, data); + }; - useVersionsStore().setVersionNotificationSettings(settings.versionNotifications); - }, - stopShowingSetupPage(): void { - this.userManagement.showSetupOnFirstLoad = false; - }, - disableTemplates(): void { - this.settings = { - ...this.settings, - templates: { - ...this.settings.templates, - enabled: false, - }, - }; - }, - setAllowedModules(allowedModules: { builtIn?: string[]; external?: string[] }): void { - this.settings.allowedModules = allowedModules; - }, - async submitContactInfo(email: string): Promise { - try { - const usersStore = useUsersStore(); - return await submitContactInfo( - this.settings.instanceId, - usersStore.currentUserId || '', - email, - ); - } catch (error) { - return; - } - }, - async testTemplatesEndpoint(): Promise { - const timeout = new Promise((_, reject) => setTimeout(() => reject(), 2000)); - await Promise.race([testHealthEndpoint(this.templatesHost), timeout]); - this.templatesEndpointHealthy = true; - }, - async getApiKey(): Promise { - const rootStore = useRootStore(); - const { apiKey } = await getApiKey(rootStore.restApiContext); - return apiKey; - }, - async createApiKey(): Promise { - const rootStore = useRootStore(); - const { apiKey } = await createApiKey(rootStore.restApiContext); - return apiKey; - }, - async deleteApiKey(): Promise { - const rootStore = useRootStore(); - await deleteApiKey(rootStore.restApiContext); - }, - async getLdapConfig() { - const rootStore = useRootStore(); - return await getLdapConfig(rootStore.restApiContext); - }, - async getLdapSynchronizations(pagination: { page: number }) { - const rootStore = useRootStore(); - return await getLdapSynchronizations(rootStore.restApiContext, pagination); - }, - async testLdapConnection() { - const rootStore = useRootStore(); - return await testLdapConnection(rootStore.restApiContext); - }, - async updateLdapConfig(ldapConfig: ILdapConfig) { - const rootStore = useRootStore(); - return await updateLdapConfig(rootStore.restApiContext, ldapConfig); - }, - async runLdapSync(data: IDataObject) { - const rootStore = useRootStore(); - return await runLdapSync(rootStore.restApiContext, data); - }, - setSaveDataErrorExecution(newValue: WorkflowSettings.SaveDataExecution) { - this.saveDataErrorExecution = newValue; - }, - setSaveDataSuccessExecution(newValue: WorkflowSettings.SaveDataExecution) { - this.saveDataSuccessExecution = newValue; - }, - setSaveManualExecutions(saveManualExecutions: boolean) { - this.saveManualExecutions = saveManualExecutions; - }, - setSaveDataProgressExecution(newValue: boolean) { - this.saveDataProgressExecution = newValue; - }, - async getTimezones(): Promise { - const rootStore = useRootStore(); - return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones'); - }, - }, + const getTimezones = async (): Promise => { + const rootStore = useRootStore(); + return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones'); + }; + + const reset = () => { + settings.value = {} as IN8nUISettings; + }; + + return { + settings, + userManagement, + templatesEndpointHealthy, + api, + ldap, + saml, + mfa, + isDocker, + isDevRelease, + isEnterpriseFeatureEnabled, + databaseType, + planName, + consumerId, + binaryDataMode, + pruning, + security, + nodeJsVersion, + concurrency, + isPublicApiEnabled, + isSwaggerUIEnabled, + isPreviewMode, + publicApiLatestVersion, + publicApiPath, + isLdapLoginEnabled, + ldapLoginLabel, + isSamlLoginEnabled, + showSetupPage, + deploymentType, + isDesktopDeployment, + isCloudDeployment, + isSmtpSetup, + isPersonalizationSurveyEnabled, + telemetry, + logLevel, + isTelemetryEnabled, + isMfaFeatureEnabled, + areTagsEnabled, + isHiringBannerEnabled, + isTemplatesEnabled, + isTemplatesEndpointReachable, + templatesHost, + pushBackend, + isCommunityNodesFeatureEnabled, + isNpmAvailable, + allowedModules, + isQueueModeEnabled, + isWorkerViewAvailable, + isDefaultAuthenticationSaml, + workflowCallerPolicyDefaultOption, + permanentlyDismissedBanners, + isBelowUserQuota, + saveDataErrorExecution, + saveDataSuccessExecution, + saveManualExecutions, + saveDataProgressExecution, + isCommunityPlan, + reset, + testLdapConnection, + getLdapConfig, + getLdapSynchronizations, + updateLdapConfig, + runLdapSync, + getTimezones, + createApiKey, + getApiKey, + deleteApiKey, + testTemplatesEndpoint, + submitContactInfo, + disableTemplates, + stopShowingSetupPage, + getSettings, + setSettings, + initialize, + }; }); diff --git a/packages/editor-ui/src/stores/sourceControl.store.ts b/packages/editor-ui/src/stores/sourceControl.store.ts index 25c763b586..958558603f 100644 --- a/packages/editor-ui/src/stores/sourceControl.store.ts +++ b/packages/editor-ui/src/stores/sourceControl.store.ts @@ -11,8 +11,8 @@ export const useSourceControlStore = defineStore('sourceControl', () => { const rootStore = useRootStore(); const settingsStore = useSettingsStore(); - const isEnterpriseSourceControlEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.SourceControl), + const isEnterpriseSourceControlEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.SourceControl], ); const sshKeyTypes: SshKeyTypes = ['ed25519', 'rsa']; diff --git a/packages/editor-ui/src/stores/sso.store.ts b/packages/editor-ui/src/stores/sso.store.ts index e445ed56cf..63c71c3899 100644 --- a/packages/editor-ui/src/stores/sso.store.ts +++ b/packages/editor-ui/src/stores/sso.store.ts @@ -42,8 +42,8 @@ export const useSSOStore = defineStore('sso', () => { void toggleLoginEnabled(value); }, }); - const isEnterpriseSamlEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Saml), + const isEnterpriseSamlEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml], ); const isDefaultAuthenticationSaml = computed(() => settingsStore.isDefaultAuthenticationSaml); const showSsoLoginButton = computed( diff --git a/packages/editor-ui/src/stores/templates.store.ts b/packages/editor-ui/src/stores/templates.store.ts index 42c2c48dfb..36eceb120a 100644 --- a/packages/editor-ui/src/stores/templates.store.ts +++ b/packages/editor-ui/src/stores/templates.store.ts @@ -289,8 +289,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { }, async fetchTemplateById(templateId: string): Promise { const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; const response = await getTemplateById(apiEndpoint, templateId, { 'n8n-version': versionCli, }); @@ -305,8 +306,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { }, async fetchCollectionById(collectionId: string): Promise { const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; const response = await getCollectionById(apiEndpoint, collectionId, { 'n8n-version': versionCli, }); @@ -325,8 +327,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { return cachedCategories; } const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli }); const categories = response.categories; @@ -340,8 +343,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { } const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli }); const collections = response.collections; @@ -361,8 +365,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { } const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; const payload = await getWorkflows( apiEndpoint, @@ -402,8 +407,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, { }, async getWorkflowTemplate(templateId: string): Promise { const settingsStore = useSettingsStore(); + const rootStore = useRootStore(); const apiEndpoint: string = settingsStore.templatesHost; - const versionCli: string = settingsStore.versionCli; + const versionCli: string = rootStore.versionCli; return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli }); }, diff --git a/packages/editor-ui/src/stores/workflows.ee.store.ts b/packages/editor-ui/src/stores/workflows.ee.store.ts index 672a3827db..ae1c1b3a65 100644 --- a/packages/editor-ui/src/stores/workflows.ee.store.ts +++ b/packages/editor-ui/src/stores/workflows.ee.store.ts @@ -50,7 +50,7 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, { const rootStore = useRootStore(); const settingsStore = useSettingsStore(); - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) { await setWorkflowSharedWith(rootStore.restApiContext, payload.workflowId, { shareWithIds: payload.sharedWithProjects.map((p) => p.id), }); diff --git a/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.test.ts b/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.test.ts index c765343ed7..52f123d0fc 100644 --- a/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.test.ts +++ b/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.test.ts @@ -1,23 +1,24 @@ import { useSettingsStore } from '@/stores/settings.store'; import { isEnterpriseFeatureEnabled } from '@/utils/rbac/checks/isEnterpriseFeatureEnabled'; import { EnterpriseEditionFeature } from '@/constants'; - -vi.mock('@/stores/settings.store', () => ({ - useSettingsStore: vi.fn(), -})); +import { createPinia, setActivePinia } from 'pinia'; +import { defaultSettings } from '@/__tests__/defaults'; describe('Checks', () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + describe('isEnterpriseFeatureEnabled()', () => { it('should return true if no feature is provided', () => { expect(isEnterpriseFeatureEnabled({})).toBe(true); }); it('should return true if feature is enabled', () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: vi - .fn() - .mockImplementation((feature) => feature !== EnterpriseEditionFeature.Variables), - } as unknown as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Saml]: true, + }; expect( isEnterpriseFeatureEnabled({ @@ -27,11 +28,11 @@ describe('Checks', () => { }); it('should return true if all features are enabled in allOf mode', () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: vi - .fn() - .mockImplementation((feature) => feature !== EnterpriseEditionFeature.Variables), - } as unknown as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: true, + [EnterpriseEditionFeature.Saml]: true, + }; expect( isEnterpriseFeatureEnabled({ @@ -42,11 +43,11 @@ describe('Checks', () => { }); it('should return false if any feature is not enabled in allOf mode', () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: vi - .fn() - .mockImplementation((feature) => feature !== EnterpriseEditionFeature.Saml), - } as unknown as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: true, + [EnterpriseEditionFeature.Saml]: false, + }; expect( isEnterpriseFeatureEnabled({ @@ -57,11 +58,11 @@ describe('Checks', () => { }); it('should return true if any feature is enabled in oneOf mode', () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: vi - .fn() - .mockImplementation((feature) => feature === EnterpriseEditionFeature.Ldap), - } as unknown as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: true, + [EnterpriseEditionFeature.Saml]: false, + }; expect( isEnterpriseFeatureEnabled({ @@ -72,9 +73,11 @@ describe('Checks', () => { }); it('should return false if no features are enabled in anyOf mode', () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: vi.fn().mockReturnValue(false), - } as unknown as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: false, + [EnterpriseEditionFeature.Saml]: false, + }; expect( isEnterpriseFeatureEnabled({ diff --git a/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.ts b/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.ts index 652e7ec0be..996a04bcd5 100644 --- a/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.ts +++ b/packages/editor-ui/src/utils/rbac/checks/isEnterpriseFeatureEnabled.ts @@ -12,8 +12,8 @@ export const isEnterpriseFeatureEnabled: RBACPermissionCheck settingsStore.isEnterpriseFeatureEnabled[feature]); } else { - return features.some(settingsStore.isEnterpriseFeatureEnabled); + return features.some((feature) => settingsStore.isEnterpriseFeatureEnabled[feature]); } }; diff --git a/packages/editor-ui/src/utils/rbac/middleware/enterprise.test.ts b/packages/editor-ui/src/utils/rbac/middleware/enterprise.test.ts index ee887efb04..f7d556cde7 100644 --- a/packages/editor-ui/src/utils/rbac/middleware/enterprise.test.ts +++ b/packages/editor-ui/src/utils/rbac/middleware/enterprise.test.ts @@ -3,17 +3,21 @@ import { VIEWS, EnterpriseEditionFeature } from '@/constants'; import { enterpriseMiddleware } from '@/utils/rbac/middleware/enterprise'; import { type RouteLocationNormalized } from 'vue-router'; import type { EnterprisePermissionOptions } from '@/types/rbac'; - -vi.mock('@/stores/settings.store', () => ({ - useSettingsStore: vi.fn(), -})); +import { createPinia, setActivePinia } from 'pinia'; +import { defaultSettings } from '@/__tests__/defaults'; describe('Middleware', () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + describe('enterprise', () => { it('should redirect to homepage if none of the required features are enabled in allOf mode', async () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: (_) => false, - } as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: false, + [EnterpriseEditionFeature.Saml]: false, + }; const nextMock = vi.fn(); const options: EnterprisePermissionOptions = { @@ -32,10 +36,11 @@ describe('Middleware', () => { }); it('should allow navigation if all of the required features are enabled in allOf mode', async () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: (feature) => - [EnterpriseEditionFeature.Saml, EnterpriseEditionFeature.Ldap].includes(feature), - } as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: true, + [EnterpriseEditionFeature.Saml]: true, + }; const nextMock = vi.fn(); const options: EnterprisePermissionOptions = { @@ -54,9 +59,10 @@ describe('Middleware', () => { }); it('should redirect to homepage if none of the required features are enabled in oneOf mode', async () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: (_) => false, - } as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Saml]: false, + }; const nextMock = vi.fn(); const options: EnterprisePermissionOptions = { @@ -75,9 +81,11 @@ describe('Middleware', () => { }); it('should allow navigation if at least one of the required features is enabled in oneOf mode', async () => { - vi.mocked(useSettingsStore).mockReturnValue({ - isEnterpriseFeatureEnabled: (feature) => feature === EnterpriseEditionFeature.Saml, - } as ReturnType); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.Ldap]: true, + [EnterpriseEditionFeature.Saml]: false, + }; const nextMock = vi.fn(); const options: EnterprisePermissionOptions = { diff --git a/packages/editor-ui/src/views/CredentialsView.vue b/packages/editor-ui/src/views/CredentialsView.vue index 56bb977dfc..fce0fb3125 100644 --- a/packages/editor-ui/src/views/CredentialsView.vue +++ b/packages/editor-ui/src/views/CredentialsView.vue @@ -159,9 +159,8 @@ export default defineComponent({ }, async initialize() { this.loading = true; - const isVarsEnabled = useSettingsStore().isEnterpriseFeatureEnabled( - EnterpriseEditionFeature.Variables, - ); + const isVarsEnabled = + useSettingsStore().isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]; const loadPromises = [ this.credentialsStore.fetchAllCredentials( diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue index 23e84da26d..b349495a30 100644 --- a/packages/editor-ui/src/views/NodeView.v2.vue +++ b/packages/editor-ui/src/views/NodeView.v2.vue @@ -210,11 +210,11 @@ async function initializeData() { credentialsStore.fetchCredentialTypes(true), ]; - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) { + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]) { promises.push(environmentsStore.fetchAllVariables()); } - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) { + if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets]) { promises.push(externalSecretsStore.fetchAllSecrets()); } diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 3be6022856..65b1487304 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -833,10 +833,10 @@ export default defineComponent({ const loadPromises = (() => { if (this.settingsStore.isPreviewMode && this.isDemo) return []; const promises = [this.loadActiveWorkflows(), this.loadCredentialTypes()]; - if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) { + if (this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]) { promises.push(this.loadVariables()); } - if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) { + if (this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets]) { promises.push(this.loadSecrets()); } return promises; @@ -4208,7 +4208,7 @@ export default defineComponent({ if ( nodeData.credentials && - this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) + this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing] ) { const usedCredentials = this.workflowsStore.usedCredentials; nodeData.credentials = Object.fromEntries( diff --git a/packages/editor-ui/src/views/SettingsLogStreamingView.vue b/packages/editor-ui/src/views/SettingsLogStreamingView.vue index f0695dd795..29fb285e4e 100644 --- a/packages/editor-ui/src/views/SettingsLogStreamingView.vue +++ b/packages/editor-ui/src/views/SettingsLogStreamingView.vue @@ -153,7 +153,7 @@ export default defineComponent({ }, isLicensed(): boolean { if (this.disableLicense) return false; - return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.LogStreaming); + return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.LogStreaming]; }, canManageLogStreaming(): boolean { return hasPermission(['rbac'], { rbac: { scope: 'logStreaming:manage' } }); diff --git a/packages/editor-ui/src/views/SettingsUsersView.vue b/packages/editor-ui/src/views/SettingsUsersView.vue index ed09564896..fe45f63c85 100644 --- a/packages/editor-ui/src/views/SettingsUsersView.vue +++ b/packages/editor-ui/src/views/SettingsUsersView.vue @@ -76,7 +76,7 @@ const usersListActions = computed((): IUserListAction[] => { ]; }); const isAdvancedPermissionsEnabled = computed((): boolean => { - return settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AdvancedPermissions); + return settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedPermissions]; }); const userRoles = computed((): Array<{ value: IRole; label: string; disabled?: boolean }> => { diff --git a/packages/editor-ui/src/views/VariablesView.spec.ts b/packages/editor-ui/src/views/VariablesView.spec.ts index 7bccdbd551..9680a716b2 100644 --- a/packages/editor-ui/src/views/VariablesView.spec.ts +++ b/packages/editor-ui/src/views/VariablesView.spec.ts @@ -7,7 +7,9 @@ import { useSettingsStore } from '@/stores/settings.store'; import { useUsersStore } from '@/stores/users.store'; import { useRBACStore } from '@/stores/rbac.store'; import { createComponentRenderer } from '@/__tests__/render'; -import { EnterpriseEditionFeature } from '@/constants'; +import { EnterpriseEditionFeature, STORES } from '@/constants'; +import { createTestingPinia } from '@pinia/testing'; +import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils'; describe('VariablesView', () => { let server: ReturnType; @@ -16,7 +18,13 @@ describe('VariablesView', () => { let usersStore: ReturnType; let rbacStore: ReturnType; - const renderComponent = createComponentRenderer(VariablesView); + const renderComponent = createComponentRenderer(VariablesView, { + pinia: createTestingPinia({ + initialState: { + [STORES.SETTINGS]: SETTINGS_STORE_DEFAULT_STATE, + }, + }), + }); beforeAll(() => { server = setupServer(); @@ -105,6 +113,7 @@ describe('VariablesView', () => { }); it('should render variable entries', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Variables] = true; server.createList('variable', 3); const wrapper = renderComponent({ pinia }); diff --git a/packages/editor-ui/src/views/VariablesView.vue b/packages/editor-ui/src/views/VariablesView.vue index d1e30c606d..271f9ce600 100644 --- a/packages/editor-ui/src/views/VariablesView.vue +++ b/packages/editor-ui/src/views/VariablesView.vue @@ -41,8 +41,8 @@ const editMode = ref>({}); const loading = ref(false); const permissions = getVariablesPermissions(usersStore.currentUser); -const isFeatureEnabled = computed(() => - settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables), +const isFeatureEnabled = computed( + () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables], ); const variablesToResources = computed((): IResource[] => diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue index fd0da50265..3c874025f0 100644 --- a/packages/editor-ui/src/views/WorkflowsView.vue +++ b/packages/editor-ui/src/views/WorkflowsView.vue @@ -217,7 +217,7 @@ const WorkflowsView = defineComponent({ return this.workflowsStore.allWorkflows; }, isShareable(): boolean { - return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); + return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]; }, statusFilterOptions(): Array<{ label: string; value: string | boolean }> { return [ diff --git a/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts b/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts index d8b9834b86..d67cd3502a 100644 --- a/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts +++ b/packages/editor-ui/src/views/__tests__/SettingsUsersView.test.ts @@ -11,8 +11,10 @@ import { useUsersStore } from '@/stores/users.store'; import { createUser } from '@/__tests__/data/users'; import { createProjectListItem } from '@/__tests__/data/projects'; import { useRBACStore } from '@/stores/rbac.store'; -import { DELETE_USER_MODAL_KEY } from '@/constants'; +import { DELETE_USER_MODAL_KEY, EnterpriseEditionFeature } from '@/constants'; import * as usersApi from '@/api/users'; +import { useSettingsStore } from '@/stores/settings.store'; +import { defaultSettings } from '@/__tests__/defaults'; const wrapperComponentWithModal = { components: { SettingsUsersView, ModalRoot, DeleteUserModal }, @@ -47,6 +49,11 @@ describe('SettingsUsersView', () => { usersStore = useUsersStore(); rbacStore = useRBACStore(); + useSettingsStore().settings.enterprise = { + ...defaultSettings.enterprise, + [EnterpriseEditionFeature.AdvancedExecutionFilters]: true, + }; + vi.spyOn(rbacStore, 'hasScope').mockReturnValue(true); vi.spyOn(usersApi, 'getUsers').mockResolvedValue(users); vi.spyOn(usersStore, 'allUsers', 'get').mockReturnValue(users); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 3433e0c45f..63364aa74f 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -2596,7 +2596,7 @@ export type ExpressionEvaluatorType = 'tmpl' | 'tournament'; export type N8nAIProviderType = 'openai' | 'unknown'; export interface IN8nUISettings { - isDocker: boolean; + isDocker?: boolean; databaseType: 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb'; endpointForm: string; endpointFormTest: string;