diff --git a/packages/frontend/editor-ui/src/App.vue b/packages/frontend/editor-ui/src/App.vue index 032ec07940..4c10bc6ae1 100644 --- a/packages/frontend/editor-ui/src/App.vue +++ b/packages/frontend/editor-ui/src/App.vue @@ -27,6 +27,8 @@ import { useWorkflowDiffRouting } from '@/composables/useWorkflowDiffRouting'; import { useStyles } from './composables/useStyles'; import { locale } from '@n8n/design-system'; import axios from 'axios'; +import { useTelemetryContext } from '@/composables/useTelemetryContext'; +import { useNDVStore } from '@/stores/ndv.store'; const route = useRoute(); const rootStore = useRootStore(); @@ -35,6 +37,7 @@ const builderStore = useBuilderStore(); const uiStore = useUIStore(); const usersStore = useUsersStore(); const settingsStore = useSettingsStore(); +const ndvStore = useNDVStore(); const { setAppZIndexes } = useStyles(); @@ -57,6 +60,8 @@ const appGrid = ref(null); const assistantSidebarWidth = computed(() => assistantStore.chatWidth); const builderSidebarWidth = computed(() => builderStore.chatWidth); +useTelemetryContext({ ndv_source: computed(() => ndvStore.lastSetActiveNodeSource) }); + onMounted(async () => { setAppZIndexes(); logHiringBanner(); diff --git a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts index 1e15f545cf..30df0ff672 100644 --- a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts +++ b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts @@ -273,7 +273,7 @@ describe('NodeErrorView.vue', () => { await userEvent.click(button); expect(window.open).not.toHaveBeenCalled(); - expect(mockNDVStore.activeNodeName).toBe('ErrorCode'); + expect(mockNDVStore.setActiveNodeName).toHaveBeenCalledWith('ErrorCode', expect.any(String)); }); it('sets active node name when error has no workflow/execution IDs', async () => { @@ -293,7 +293,7 @@ describe('NodeErrorView.vue', () => { await userEvent.click(button); expect(window.open).not.toHaveBeenCalled(); - expect(mockNDVStore.activeNodeName).toBe('ErrorCode'); + expect(mockNDVStore.setActiveNodeName).toHaveBeenCalledWith('ErrorCode', expect.any(String)); }); }); }); diff --git a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue index 9eb85080b3..1943537e57 100644 --- a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue @@ -416,7 +416,7 @@ const onOpenErrorNodeDetailClick = () => { }); window.open(link.href, '_blank'); } else { - ndvStore.activeNodeName = props.error.node.name; + ndvStore.setActiveNodeName(props.error.node.name, 'other'); } }; diff --git a/packages/frontend/editor-ui/src/components/FocusPanel.vue b/packages/frontend/editor-ui/src/components/FocusPanel.vue index b16e04d82b..b46f729896 100644 --- a/packages/frontend/editor-ui/src/components/FocusPanel.vue +++ b/packages/frontend/editor-ui/src/components/FocusPanel.vue @@ -2,7 +2,7 @@ import { useFocusPanelStore } from '@/stores/focusPanel.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { N8nText, N8nInput, N8nResizeWrapper, N8nInfoTip } from '@n8n/design-system'; -import { computed, nextTick, ref, watch, toRef } from 'vue'; +import { computed, nextTick, ref, watch, toRef, useTemplateRef } from 'vue'; import { useI18n } from '@n8n/i18n'; import { formatAsExpression, @@ -28,7 +28,7 @@ import { htmlEditorEventBus } from '@/event-bus'; import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils'; import type { INodeUi, ResizeData, TargetNodeParameterContext } from '@/Interface'; import { useTelemetry } from '@/composables/useTelemetry'; -import { useThrottleFn } from '@vueuse/core'; +import { useActiveElement, useThrottleFn } from '@vueuse/core'; import { useExecutionData } from '@/composables/useExecutionData'; import { useWorkflowsStore } from '@/stores/workflows.store'; import ExperimentalNodeDetailsDrawer from '@/components/canvas/experimental/components/ExperimentalNodeDetailsDrawer.vue'; @@ -36,6 +36,7 @@ import { useExperimentalNdvStore } from '@/components/canvas/experimental/experi import { useNDVStore } from '@/stores/ndv.store'; import { useVueFlow } from '@vue-flow/core'; import ExperimentalFocusPanelHeader from '@/components/canvas/experimental/components/ExperimentalFocusPanelHeader.vue'; +import { useTelemetryContext } from '@/composables/useTelemetryContext'; defineOptions({ name: 'FocusPanel' }); @@ -51,6 +52,7 @@ const emit = defineEmits<{ // ESLint: false positive // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents const inputField = ref | HTMLElement>(); +const wrapperRef = useTemplateRef('wrapper'); const locale = useI18n(); const nodeHelpers = useNodeHelpers(); @@ -64,13 +66,11 @@ const experimentalNdvStore = useExperimentalNdvStore(); const ndvStore = useNDVStore(); const deviceSupport = useDeviceSupport(); const vueFlow = useVueFlow(workflowsStore.workflowId); +const activeElement = useActiveElement(); -const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]); -const resolvedParameter = computed(() => - focusedNodeParameter.value && focusPanelStore.isRichParameter(focusedNodeParameter.value) - ? focusedNodeParameter.value - : undefined, -); +useTelemetryContext({ view_shown: 'focus_panel' }); + +const resolvedParameter = computed(() => focusPanelStore.resolvedParameter); const inputValue = ref(''); @@ -288,6 +288,12 @@ function optionSelected(command: string) { function closeFocusPanel() { if (experimentalNdvStore.isNdvInFocusPanelEnabled && resolvedParameter.value) { focusPanelStore.unsetParameters(); + + telemetry.track('User removed focused param', { + source: 'closeIcon', + parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat, + }); + return; } @@ -365,6 +371,24 @@ watch( { immediate: true }, ); +watch(activeElement, (active) => { + if (!node.value || !active || !wrapperRef.value?.contains(active)) { + return; + } + + const path = active.closest('.parameter-input')?.getAttribute('data-parameter-path'); + + if (!path) { + return; + } + + telemetry.track('User focused focus panel', { + node_id: node.value.id, + node_type: node.value.type, + parameter_path: path, + }); +}); + function onResize(event: ResizeData) { focusPanelStore.updateWidth(event.width); } @@ -373,13 +397,13 @@ const onResizeThrottle = useThrottleFn(onResize, 10); function onOpenNdv() { if (node.value) { - ndvStore.setActiveNodeName(node.value.name); + ndvStore.setActiveNodeName(node.value.name, 'focus_panel'); } }