chore: Remove self-install telemetry (#21988)

This commit is contained in:
Tomi Turtiainen 2025-11-20 14:09:07 +02:00 committed by GitHub
parent 3d3e8ccf1d
commit bf1511ae57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 214 additions and 273 deletions

View File

@ -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"
/>
<Telemetry />
<AskAssistantFloatingButton v-if="assistantStore.isFloatingButtonShown" />
</div>
<AssistantsHub />

View File

@ -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: '<a><slot /></a>',
},
};
});
vi.mock('@/app/composables/useTelemetry', () => {
const init = vi.fn();
return {
useTelemetry: () => ({
init,
}),
};
});
const renderComponent = createComponentRenderer(Telemetry, {
pinia: createTestingPinia(),
});
let route: ReturnType<typeof useRoute>;
let rootStore: MockedStore<typeof useRootStore>;
let settingsStore: MockedStore<typeof useSettingsStore>;
let usersStore: MockedStore<typeof useUsersStore>;
let telemetryPlugin: ReturnType<typeof useTelemetry>;
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();
});
});

View File

@ -1,73 +0,0 @@
<script lang="ts" setup>
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';
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;
});
const selfInstallSrc = computed((): string => {
return `https://n8n.io/self-install?instanceId=${rootStore.instanceId}&userId=${currentUserId.value}`;
});
watch(telemetry, () => {
init();
});
watch(isTelemetryEnabledOnRoute, (enabled) => {
if (enabled) {
init();
}
});
onMounted(() => {
init();
});
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;
}
</script>
<template>
<iframe v-if="isTelemetryEnabled && currentUserId" v-show="false" :src="selfInstallSrc" />
<span v-else v-show="false" />
</template>

View File

@ -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<string, unknown> = {};
vi.mock('vue-router', () => ({
useRouter: vi.fn(),
useRoute: () => ({
meta: mockRouteMeta,
}),
RouterLink: {
template: '<a><slot /></a>',
},
}));
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<Project>({ 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();
});
});

View File

@ -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();
});
}

View File

@ -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: '<html><body>Test iframe content</body></html>',
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);
});
});
});