From bf1511ae5789642f1db524585e3eae80cfac7029 Mon Sep 17 00:00:00 2001
From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
Date: Thu, 20 Nov 2025 14:09:07 +0200
Subject: [PATCH] chore: Remove self-install telemetry (#21988)
---
packages/frontend/editor-ui/src/app/App.vue | 5 +-
.../src/app/components/Telemetry.test.ts | 127 ----------------
.../src/app/components/Telemetry.vue | 73 ---------
.../useTelemetryInitializer.test.ts | 140 ++++++++++++++++++
.../composables/useTelemetryInitializer.ts | 71 +++++++++
.../tests/ui/46-n8n-io-iframe.spec.ts | 71 ---------
6 files changed, 214 insertions(+), 273 deletions(-)
delete mode 100644 packages/frontend/editor-ui/src/app/components/Telemetry.test.ts
delete mode 100644 packages/frontend/editor-ui/src/app/components/Telemetry.vue
create mode 100644 packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.test.ts
create mode 100644 packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.ts
delete mode 100644 packages/testing/playwright/tests/ui/46-n8n-io-iframe.spec.ts
diff --git a/packages/frontend/editor-ui/src/app/App.vue b/packages/frontend/editor-ui/src/app/App.vue
index 02ae96b0619..3a421fe0b5a 100644
--- a/packages/frontend/editor-ui/src/app/App.vue
+++ b/packages/frontend/editor-ui/src/app/App.vue
@@ -3,9 +3,9 @@ import AssistantsHub from '@/features/ai/assistant/components/AssistantsHub.vue'
import AskAssistantFloatingButton from '@/features/ai/assistant/components/Chat/AskAssistantFloatingButton.vue';
import BannerStack from '@/features/shared/banners/components/BannerStack.vue';
import Modals from '@/app/components/Modals.vue';
-import Telemetry from '@/app/components/Telemetry.vue';
import { useHistoryHelper } from '@/app/composables/useHistoryHelper';
import { useTelemetryContext } from '@/app/composables/useTelemetryContext';
+import { useTelemetryInitializer } from '@/app/composables/useTelemetryInitializer';
import { useWorkflowDiffRouting } from '@/app/composables/useWorkflowDiffRouting';
import {
APP_MODALS_ELEMENT_ID,
@@ -63,6 +63,8 @@ useHistoryHelper(route);
// Initialize workflow diff routing management
useWorkflowDiffRouting();
+useTelemetryInitializer();
+
const loading = ref(true);
const defaultLocale = computed(() => rootStore.defaultLocale);
const isDemoMode = computed(() => route.name === VIEWS.DEMO);
@@ -181,7 +183,6 @@ useExposeCssVar('--ask-assistant--floating-button--margin-bottom', askAiFloating
@input-change="onCommandBarChange"
@navigate-to="onCommandBarNavigateTo"
/>
-
diff --git a/packages/frontend/editor-ui/src/app/components/Telemetry.test.ts b/packages/frontend/editor-ui/src/app/components/Telemetry.test.ts
deleted file mode 100644
index 3e05be43679..00000000000
--- a/packages/frontend/editor-ui/src/app/components/Telemetry.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { useRoute } from 'vue-router';
-import { createTestingPinia } from '@pinia/testing';
-import { createComponentRenderer } from '@/__tests__/render';
-import type { MockedStore } from '@/__tests__/utils';
-import { mockedStore } from '@/__tests__/utils';
-import Telemetry from './Telemetry.vue';
-import { useRootStore } from '@n8n/stores/useRootStore';
-import { useSettingsStore } from '@/app/stores/settings.store';
-import { useUsersStore } from '@/features/settings/users/users.store';
-import { useTelemetry } from '@/app/composables/useTelemetry';
-
-vi.mock('vue-router', () => {
- const meta = {};
- return {
- useRouter: vi.fn(),
- useRoute: () => ({
- meta,
- }),
- RouterLink: {
- template: '',
- },
- };
-});
-
-vi.mock('@/app/composables/useTelemetry', () => {
- const init = vi.fn();
- return {
- useTelemetry: () => ({
- init,
- }),
- };
-});
-
-const renderComponent = createComponentRenderer(Telemetry, {
- pinia: createTestingPinia(),
-});
-
-let route: ReturnType;
-let rootStore: MockedStore;
-let settingsStore: MockedStore;
-let usersStore: MockedStore;
-let telemetryPlugin: ReturnType;
-
-describe('Telemetry', () => {
- beforeEach(() => {
- vi.clearAllMocks();
-
- route = useRoute();
- rootStore = mockedStore(useRootStore);
- settingsStore = mockedStore(useSettingsStore);
- usersStore = mockedStore(useUsersStore);
- telemetryPlugin = useTelemetry();
- });
-
- it('should not throw error when opened', async () => {
- expect(() => renderComponent()).not.toThrow();
- });
-
- it('should initialize if telemetry is enabled in settings and not disabled on the route', async () => {
- settingsStore.telemetry = {
- enabled: true,
- };
- usersStore.currentUserId = '123';
- rootStore.instanceId = '456';
- renderComponent();
-
- expect(telemetryPlugin.init).toHaveBeenCalledWith(
- {
- enabled: true,
- },
- expect.objectContaining({
- userId: '123',
- instanceId: '456',
- }),
- );
- });
-
- it('should not initialize if telemetry is disabled in settings', async () => {
- settingsStore.telemetry = {
- enabled: false,
- };
- renderComponent();
-
- expect(telemetryPlugin.init).not.toHaveBeenCalled();
- });
-
- it('should not initialize if telemetry is disabled on the route', async () => {
- settingsStore.telemetry = {
- enabled: true,
- };
- route.meta.telemetry = {
- disabled: true,
- };
- renderComponent();
-
- expect(telemetryPlugin.init).not.toHaveBeenCalled();
- });
-
- it('should render the iframe with correct src', async () => {
- settingsStore.telemetry = {
- enabled: true,
- };
- usersStore.currentUserId = '123';
- rootStore.instanceId = '456';
- const { container } = renderComponent();
-
- const iframe = container.querySelector('iframe');
-
- expect(iframe).toBeInTheDocument();
- expect(iframe).not.toBeVisible();
- expect(iframe).toHaveAttribute('src', expect.stringContaining('userId=123'));
- expect(iframe).toHaveAttribute('src', expect.stringContaining('instanceId=456'));
- });
-
- it('should not render the iframe if telemetry disabled', async () => {
- settingsStore.telemetry = {
- enabled: false,
- };
- usersStore.currentUserId = '123';
- rootStore.instanceId = '456';
- const { container } = renderComponent();
-
- const iframe = container.querySelector('iframe');
-
- expect(iframe).not.toBeInTheDocument();
- });
-});
diff --git a/packages/frontend/editor-ui/src/app/components/Telemetry.vue b/packages/frontend/editor-ui/src/app/components/Telemetry.vue
deleted file mode 100644
index bb4abd790a1..00000000000
--- a/packages/frontend/editor-ui/src/app/components/Telemetry.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
diff --git a/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.test.ts b/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.test.ts
new file mode 100644
index 00000000000..e9da7a7cd09
--- /dev/null
+++ b/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.test.ts
@@ -0,0 +1,140 @@
+import { createTestingPinia } from '@pinia/testing';
+import { mount } from '@vue/test-utils';
+import { defineComponent, h, nextTick } from 'vue';
+import { mock } from 'vitest-mock-extended';
+import { useTelemetryInitializer } from './useTelemetryInitializer';
+import { useTelemetry } from '@/app/composables/useTelemetry';
+import type { Project } from '@/features/collaboration/projects/projects.types';
+
+const mockRouteMeta: Record = {};
+
+vi.mock('vue-router', () => ({
+ useRouter: vi.fn(),
+ useRoute: () => ({
+ meta: mockRouteMeta,
+ }),
+ RouterLink: {
+ template: '',
+ },
+}));
+
+vi.mock('@/app/composables/useTelemetry', () => {
+ const init = vi.fn();
+ return {
+ useTelemetry: () => ({
+ init,
+ }),
+ };
+});
+
+const TestComponent = defineComponent({
+ setup() {
+ useTelemetryInitializer();
+ return () => h('div');
+ },
+});
+
+describe('useTelemetryInitializer', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ // Clear route meta
+ Object.keys(mockRouteMeta).forEach((key) => delete mockRouteMeta[key]);
+ });
+
+ it('should not throw error when called', () => {
+ expect(() =>
+ mount(TestComponent, { global: { plugins: [createTestingPinia()] } }),
+ ).not.toThrow();
+ });
+
+ it('should initialize if telemetry is enabled in settings and not disabled on the route', async () => {
+ const pinia = createTestingPinia({
+ stubActions: false,
+ initialState: {
+ users: {
+ currentUserId: '123',
+ },
+ projects: {
+ personalProject: mock({ id: '789' }),
+ },
+ settings: {
+ settings: {
+ telemetry: {
+ enabled: true,
+ },
+ },
+ },
+ },
+ });
+
+ const telemetryPlugin = useTelemetry();
+ const wrapper = mount(TestComponent, { global: { plugins: [pinia] } });
+
+ await nextTick();
+
+ expect(telemetryPlugin.init).toHaveBeenCalledWith(
+ {
+ enabled: true,
+ },
+ expect.objectContaining({
+ userId: '123',
+ projectId: '789',
+ }),
+ );
+
+ wrapper.unmount();
+ });
+
+ it('should not initialize if telemetry is disabled in settings', async () => {
+ const pinia = createTestingPinia({
+ stubActions: false,
+ initialState: {
+ settings: {
+ settings: {
+ telemetry: {
+ enabled: false,
+ },
+ },
+ },
+ },
+ });
+
+ const telemetryPlugin = useTelemetry();
+ const wrapper = mount(TestComponent, { global: { plugins: [pinia] } });
+
+ await nextTick();
+
+ expect(telemetryPlugin.init).not.toHaveBeenCalled();
+
+ wrapper.unmount();
+ });
+
+ it('should not initialize if telemetry is disabled on the route', async () => {
+ // Set route meta before mounting
+ mockRouteMeta.telemetry = {
+ disabled: true,
+ };
+
+ const pinia = createTestingPinia({
+ stubActions: false,
+ initialState: {
+ settings: {
+ settings: {
+ telemetry: {
+ enabled: true,
+ },
+ },
+ },
+ },
+ });
+
+ const telemetryPlugin = useTelemetry();
+ const wrapper = mount(TestComponent, { global: { plugins: [pinia] } });
+
+ await nextTick();
+
+ expect(telemetryPlugin.init).not.toHaveBeenCalled();
+
+ wrapper.unmount();
+ });
+});
diff --git a/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.ts b/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.ts
new file mode 100644
index 00000000000..6106880b75a
--- /dev/null
+++ b/packages/frontend/editor-ui/src/app/composables/useTelemetryInitializer.ts
@@ -0,0 +1,71 @@
+import type { ITelemetrySettings } from '@n8n/api-types';
+import { useRootStore } from '@n8n/stores/useRootStore';
+import { useSettingsStore } from '@/app/stores/settings.store';
+import { useUsersStore } from '@/features/settings/users/users.store';
+import { useProjectsStore } from '@/features/collaboration/projects/projects.store';
+import { computed, onMounted, watch, ref } from 'vue';
+import { useTelemetry } from '@/app/composables/useTelemetry';
+import { useRoute } from 'vue-router';
+
+/**
+ * Initializes the telemetry for the application
+ */
+export function useTelemetryInitializer() {
+ const isTelemetryInitialized = ref(false);
+
+ const rootStore = useRootStore();
+ const settingsStore = useSettingsStore();
+ const usersStore = useUsersStore();
+ const projectsStore = useProjectsStore();
+ const telemetryPlugin = useTelemetry();
+ const route = useRoute();
+
+ const currentUserId = computed((): string => {
+ return usersStore.currentUserId ?? '';
+ });
+
+ const isTelemetryEnabledOnRoute = computed((): boolean => {
+ const routeMeta = route.meta as { telemetry?: { disabled?: boolean } } | undefined;
+ return routeMeta?.telemetry ? !routeMeta.telemetry.disabled : true;
+ });
+
+ const telemetry = computed((): ITelemetrySettings => {
+ return settingsStore.telemetry;
+ });
+
+ const isTelemetryEnabled = computed((): boolean => {
+ return !!telemetry.value?.enabled;
+ });
+
+ function init() {
+ if (
+ isTelemetryInitialized.value ||
+ !isTelemetryEnabledOnRoute.value ||
+ !isTelemetryEnabled.value
+ )
+ return;
+
+ telemetryPlugin.init(telemetry.value, {
+ instanceId: rootStore.instanceId,
+ userId: currentUserId.value,
+ projectId: projectsStore.personalProject?.id,
+ versionCli: rootStore.versionCli,
+ });
+
+ isTelemetryInitialized.value = true;
+ }
+
+ watch(telemetry, () => {
+ init();
+ });
+
+ watch(isTelemetryEnabledOnRoute, (enabled) => {
+ if (enabled) {
+ init();
+ }
+ });
+
+ onMounted(() => {
+ init();
+ });
+}
diff --git a/packages/testing/playwright/tests/ui/46-n8n-io-iframe.spec.ts b/packages/testing/playwright/tests/ui/46-n8n-io-iframe.spec.ts
deleted file mode 100644
index df1cb71908d..00000000000
--- a/packages/testing/playwright/tests/ui/46-n8n-io-iframe.spec.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { test, expect } from '../../fixtures/base';
-import type { TestRequirements } from '../../Types';
-
-const telemetryDisabledRequirements: TestRequirements = {
- config: {
- settings: {
- telemetry: { enabled: false },
- },
- },
- storage: {
- 'n8n-telemetry': JSON.stringify({ enabled: false }),
- },
-};
-
-const telemetryEnabledRequirements: TestRequirements = {
- config: {
- settings: {
- telemetry: { enabled: true },
- instanceId: 'test-instance-id',
- },
- },
- storage: {
- 'n8n-telemetry': JSON.stringify({ enabled: true }),
- 'n8n-instance-id': 'test-instance-id',
- },
- intercepts: {
- iframeRequest: {
- url: 'https://n8n.io/self-install*',
- response: 'Test iframe content',
- contentType: 'text/html',
- },
- },
-};
-
-test.describe('n8n.io iframe', () => {
- test.describe('when telemetry is disabled', () => {
- test('should not load the iframe when visiting /home/workflows', async ({
- n8n,
- setupRequirements,
- }) => {
- await setupRequirements(telemetryDisabledRequirements);
-
- await n8n.page.goto('/');
- await n8n.page.waitForLoadState();
- await expect(n8n.iframe.getIframe()).not.toBeAttached();
- });
- });
-
- test.describe('when telemetry is enabled', () => {
- test('should load the iframe when visiting /home/workflows @auth:owner', async ({
- n8n,
- setupRequirements,
- }) => {
- await setupRequirements(telemetryEnabledRequirements);
-
- // Get current user ID from the API
- const currentUser = await n8n.api.get('/rest/login');
- const testInstanceId = 'test-instance-id';
- const testUserId = currentUser.id;
- const iframeUrl = `https://n8n.io/self-install?instanceId=${testInstanceId}&userId=${testUserId}`;
-
- await n8n.page.goto('/');
- await n8n.page.waitForLoadState();
-
- const iframeElement = n8n.iframe.getIframeBySrc(iframeUrl);
- await expect(iframeElement).toBeAttached();
-
- await expect(iframeElement).toHaveAttribute('src', iframeUrl);
- });
- });
-});