diff --git a/packages/cli/src/services/__tests__/url.service.test.ts b/packages/cli/src/services/__tests__/url.service.test.ts new file mode 100644 index 0000000000000..c89d62789c149 --- /dev/null +++ b/packages/cli/src/services/__tests__/url.service.test.ts @@ -0,0 +1,43 @@ +import type { GlobalConfig } from '@n8n/config'; +import { mock } from 'jest-mock-extended'; + +import config from '@/config'; + +import { UrlService } from '../url.service'; + +describe('UrlService', () => { + beforeEach(() => { + process.env.WEBHOOK_URL = undefined; + config.load(config.default); + }); + + describe('getInstanceBaseUrl', () => { + it('should set URL from N8N_EDITOR_BASE_URL', () => { + config.set('editorBaseUrl', 'https://example.com/'); + process.env.WEBHOOK_URL = undefined; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should set URL from WEBHOOK_URL', () => { + config.set('editorBaseUrl', ''); + process.env.WEBHOOK_URL = 'https://example.com/'; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should trim quotes when setting URL from N8N_EDITOR_BASE_URL', () => { + config.set('editorBaseUrl', '"https://example.com"'); + process.env.WEBHOOK_URL = undefined; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should trim quotes when setting URL from WEBHOOK_URL', () => { + config.set('editorBaseUrl', ''); + process.env.WEBHOOK_URL = '"https://example.com/"'; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + }); +}); diff --git a/packages/cli/src/services/url.service.ts b/packages/cli/src/services/url.service.ts index 43b53f28ad054..f9d3fcdbbdeb6 100644 --- a/packages/cli/src/services/url.service.ts +++ b/packages/cli/src/services/url.service.ts @@ -14,7 +14,7 @@ export class UrlService { /** Returns the base URL of the webhooks */ getWebhookBaseUrl() { - let urlBaseWebhook = process.env.WEBHOOK_URL ?? this.baseUrl; + let urlBaseWebhook = this.trimQuotes(process.env.WEBHOOK_URL) || this.baseUrl; if (!urlBaseWebhook.endsWith('/')) { urlBaseWebhook += '/'; } @@ -23,7 +23,7 @@ export class UrlService { /** Return the n8n instance base URL without trailing slash */ getInstanceBaseUrl(): string { - const n8nBaseUrl = config.getEnv('editorBaseUrl') || this.getWebhookBaseUrl(); + const n8nBaseUrl = this.trimQuotes(config.getEnv('editorBaseUrl')) || this.getWebhookBaseUrl(); return n8nBaseUrl.endsWith('/') ? n8nBaseUrl.slice(0, n8nBaseUrl.length - 1) : n8nBaseUrl; } @@ -36,4 +36,9 @@ export class UrlService { } return `${protocol}://${host}:${port}${path}`; } + + /** Remove leading and trailing double quotes from a URL. */ + private trimQuotes(url?: string) { + return url?.replace(/^["]+|["]+$/g, '') ?? ''; + } } diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index e639f537dfa0e..b6691ac76b556 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -52,7 +52,6 @@ import type { AI_NODE_CREATOR_VIEW, CREDENTIAL_EDIT_MODAL_KEY, SignInType, - FAKE_DOOR_FEATURES, TRIGGER_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW, @@ -62,7 +61,6 @@ import type { BulkCommand, Undoable } from '@/models/history'; import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers'; import type { ProjectSharingData } from '@/types/projects.types'; -import type { BaseTextKey } from './plugins/i18n'; export * from 'n8n-design-system/types'; @@ -1036,24 +1034,6 @@ export interface NotificationOptions extends Partial message: string | ElementNotificationOptions['message']; } -export type IFakeDoor = { - id: FAKE_DOOR_FEATURES; - featureName: BaseTextKey; - icon?: string; - infoText?: BaseTextKey; - actionBoxTitle: BaseTextKey; - actionBoxDescription: BaseTextKey; - actionBoxButtonLabel?: BaseTextKey; - linkURL: string; - uiLocations: IFakeDoorLocation[]; -}; - -export type IFakeDoorLocation = - | 'settings' - | 'settings/users' - | 'credentialsModal' - | 'workflowShareModal'; - export type NodeFilterType = | typeof REGULAR_NODE_CREATOR_VIEW | typeof TRIGGER_NODE_CREATOR_VIEW diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index efbde8d1fbf10..c6d5ee07ecee7 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -22,7 +22,6 @@ import { NodeHelpers } from 'n8n-workflow'; import CredentialConfig from '@/components/CredentialEdit/CredentialConfig.vue'; import CredentialInfo from '@/components/CredentialEdit/CredentialInfo.vue'; import CredentialSharing from '@/components/CredentialEdit/CredentialSharing.ee.vue'; -import FeatureComingSoon from '@/components/FeatureComingSoon.vue'; import InlineNameEdit from '@/components/InlineNameEdit.vue'; import Modal from '@/components/Modal.vue'; import SaveButton from '@/components/SaveButton.vue'; @@ -518,14 +517,13 @@ async function loadCurrentCredential() { function onTabSelect(tab: string) { activeTab.value = tab; - const tabName: string = tab.replaceAll('coming-soon/', ''); const credType: string = credentialType.value ? credentialType.value.name : ''; const activeNode: INode | null = ndvStore.activeNode; telemetry.track('User viewed credential tab', { credential_type: credType, node_type: activeNode ? activeNode.type : null, - tab: tabName, + tab, workflow_id: workflowsStore.workflowId, credential_id: credentialId.value, sharing_enabled: EnterpriseEditionFeature.Sharing, @@ -1130,9 +1128,6 @@ function resetCredentialData(): void {
-
- -
diff --git a/packages/editor-ui/src/components/FeatureComingSoon.vue b/packages/editor-ui/src/components/FeatureComingSoon.vue deleted file mode 100644 index fd24a35196b49..0000000000000 --- a/packages/editor-ui/src/components/FeatureComingSoon.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/SettingsSidebar.vue b/packages/editor-ui/src/components/SettingsSidebar.vue index 5409620e4586e..41a7de813ea33 100644 --- a/packages/editor-ui/src/components/SettingsSidebar.vue +++ b/packages/editor-ui/src/components/SettingsSidebar.vue @@ -2,9 +2,7 @@ import { computed } from 'vue'; import { ABOUT_MODAL_KEY, VIEWS } from '@/constants'; import { useUserHelpers } from '@/composables/useUserHelpers'; -import type { IFakeDoor } from '@/Interface'; import type { IMenuItem } from 'n8n-design-system'; -import type { BaseTextKey } from '@/plugins/i18n'; import { useUIStore } from '@/stores/ui.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useRootStore } from '@/stores/root.store'; @@ -26,23 +24,6 @@ const rootStore = useRootStore(); const settingsStore = useSettingsStore(); const uiStore = useUIStore(); -const settingsFakeDoorFeatures = computed(() => - Object.keys(uiStore.fakeDoorsByLocation) - .filter((location: string) => location.includes('settings')) - .map((location) => uiStore.fakeDoorsByLocation[location]), -); - -const handleSelect = (key: string) => { - switch (key) { - case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud - case 'logging': - router.push({ name: VIEWS.FAKE_DOOR, params: { featureId: key } }).catch(() => {}); - break; - default: - break; - } -}; - const sidebarMenuItems = computed(() => { const menuItems: IMenuItem[] = [ { @@ -122,19 +103,6 @@ const sidebarMenuItems = computed(() => { }, ]; - for (const item of settingsFakeDoorFeatures.value) { - if (item.uiLocations.includes('settings')) { - menuItems.push({ - id: item.id, - icon: item.icon ?? 'question', - label: i18n.baseText(item.featureName as BaseTextKey), - position: 'top', - available: true, - activateOnRoutePaths: [`/settings/coming-soon/${item.id}`], - }); - } - } - menuItems.push({ id: 'settings-log-streaming', icon: 'sign-in-alt', @@ -159,7 +127,7 @@ const sidebarMenuItems = computed(() => {