feat(editor): Add telemetry for focus panel (no-changelog) (#17159)

Co-authored-by: Daria Staferova <daria.staferova@n8n.io>
This commit is contained in:
Charlie Kolb
2025-07-24 13:44:39 +02:00
committed by GitHub
parent 49dec19196
commit 424c79c14b
5 changed files with 55 additions and 8 deletions

View File

@@ -28,6 +28,7 @@ import { useDebounce } from '@/composables/useDebounce';
import { htmlEditorEventBus } from '@/event-bus'; import { htmlEditorEventBus } from '@/event-bus';
import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils'; import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils';
import type { ResizeData, TargetNodeParameterContext } from '@/Interface'; import type { ResizeData, TargetNodeParameterContext } from '@/Interface';
import { useTelemetry } from '@/composables/useTelemetry';
import { useThrottleFn } from '@vueuse/core'; import { useThrottleFn } from '@vueuse/core';
import { useStyles } from '@/composables/useStyles'; import { useStyles } from '@/composables/useStyles';
import { useExecutionData } from '@/composables/useExecutionData'; import { useExecutionData } from '@/composables/useExecutionData';
@@ -53,6 +54,7 @@ const nodeHelpers = useNodeHelpers();
const focusPanelStore = useFocusPanelStore(); const focusPanelStore = useFocusPanelStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore(); const nodeTypesStore = useNodeTypesStore();
const telemetry = useTelemetry();
const nodeSettingsParameters = useNodeSettingsParameters(); const nodeSettingsParameters = useNodeSettingsParameters();
const environmentsStore = useEnvironmentsStore(); const environmentsStore = useEnvironmentsStore();
const deviceSupport = useDeviceSupport(); const deviceSupport = useDeviceSupport();
@@ -262,6 +264,22 @@ function optionSelected(command: string) {
} }
} }
function closeFocusPanel() {
telemetry.track('User closed focus panel', {
source: 'closeIcon',
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
});
focusPanelStore.closeFocusPanel();
}
function onExecute() {
telemetry.track(
'User executed node from focus panel',
focusPanelStore.focusedNodeParametersInTelemetryFormat[0],
);
}
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 }); const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 });
// Wait for editor to mount before focusing // Wait for editor to mount before focusing
@@ -343,13 +361,14 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
:square="true" :square="true"
:hide-label="true" :hide-label="true"
telemetry-source="focus" telemetry-source="focus"
></NodeExecuteButton> @execute="onExecute"
/>
<N8nIcon <N8nIcon
:class="$style.closeButton" :class="$style.closeButton"
icon="x" icon="x"
color="text-base" color="text-base"
size="xlarge" size="xlarge"
@click="focusPanelStore.closeFocusPanel" @click="closeFocusPanel"
/> />
</div> </div>
</div> </div>

View File

@@ -21,6 +21,7 @@ import { useActions } from './NodeCreator/composables/useActions';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue'; import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import AssistantIcon from '@n8n/design-system/components/AskAssistantIcon/AssistantIcon.vue'; import AssistantIcon from '@n8n/design-system/components/AskAssistantIcon/AssistantIcon.vue';
import { useI18n } from '@n8n/i18n'; import { useI18n } from '@n8n/i18n';
import { useTelemetry } from '@/composables/useTelemetry';
import { useAssistantStore } from '@/stores/assistant.store'; import { useAssistantStore } from '@/stores/assistant.store';
type Props = { type Props = {
@@ -46,6 +47,7 @@ const uiStore = useUIStore();
const focusPanelStore = useFocusPanelStore(); const focusPanelStore = useFocusPanelStore();
const posthogStore = usePostHog(); const posthogStore = usePostHog();
const i18n = useI18n(); const i18n = useI18n();
const telemetry = useTelemetry();
const assistantStore = useAssistantStore(); const assistantStore = useAssistantStore();
const { getAddedNodesAndConnections } = useActions(); const { getAddedNodesAndConnections } = useActions();
@@ -86,6 +88,15 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
closeNodeCreator(true); closeNodeCreator(true);
} }
function toggleFocusPanel() {
focusPanelStore.toggleFocusPanel();
telemetry.track(`User ${focusPanelStore.focusPanelActive ? 'opened' : 'closed'} focus panel`, {
source: 'canvasButton',
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
});
}
function onAskAssistantButtonClick() { function onAskAssistantButtonClick() {
if (!assistantStore.chatWindowOpen) if (!assistantStore.chatWindowOpen)
assistantStore.trackUserOpenedAssistant({ assistantStore.trackUserOpenedAssistant({
@@ -132,12 +143,7 @@ function onAskAssistantButtonClick() {
:shortcut="{ keys: ['f'], shiftKey: true }" :shortcut="{ keys: ['f'], shiftKey: true }"
placement="left" placement="left"
> >
<n8n-icon-button <n8n-icon-button type="tertiary" size="large" icon="panel-right" @click="toggleFocusPanel" />
type="tertiary"
size="large"
icon="panel-right"
@click="focusPanelStore.toggleFocusPanel"
/>
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<n8n-tooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left"> <n8n-tooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left">
<template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template> <template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template>

View File

@@ -78,6 +78,7 @@ import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
import { hasFocusOnInput, isBlurrableEl, isFocusableEl, isSelectableEl } from '@/utils/typesUtils'; import { hasFocusOnInput, isBlurrableEl, isFocusableEl, isSelectableEl } from '@/utils/typesUtils';
import { completeExpressionSyntax, shouldConvertToExpression } from '@/utils/expressions'; import { completeExpressionSyntax, shouldConvertToExpression } from '@/utils/expressions';
import CssEditor from './CssEditor/CssEditor.vue'; import CssEditor from './CssEditor/CssEditor.vue';
import { useFocusPanelStore } from '@/stores/focusPanel.store';
type Picker = { $emit: (arg0: string, arg1: Date) => void }; type Picker = { $emit: (arg0: string, arg1: Date) => void };
@@ -141,6 +142,7 @@ const workflowsStore = useWorkflowsStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const nodeTypesStore = useNodeTypesStore(); const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore(); const uiStore = useUIStore();
const focusPanelStore = useFocusPanelStore();
// ESLint: false positive // ESLint: false positive
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
@@ -1000,6 +1002,10 @@ async function optionSelected(command: string) {
case 'focus': case 'focus':
nodeSettingsParameters.handleFocus(node.value, props.path, props.parameter); nodeSettingsParameters.handleFocus(node.value, props.path, props.parameter);
telemetry.track('User opened focus panel', {
source: 'parameterButton',
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
});
return; return;
} }

View File

@@ -152,6 +152,16 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
return 'value' in p && 'node' in p; return 'value' in p && 'node' in p;
} }
const focusedNodeParametersInTelemetryFormat = computed<
Array<{ parameterPath: string; nodeType: string; nodeId: string }>
>(() =>
focusedNodeParameters.value.map((x) => ({
parameterPath: x.parameterPath,
nodeType: isRichParameter(x) ? x.node.type : 'unresolved',
nodeId: x.nodeId,
})),
);
// Ensure lastFocusTimestamp is set on initial load if panel is already active (e.g. after reload) // Ensure lastFocusTimestamp is set on initial load if panel is already active (e.g. after reload)
watchOnce( watchOnce(
() => currentFocusPanelData.value, () => currentFocusPanelData.value,
@@ -165,6 +175,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
return { return {
focusPanelActive, focusPanelActive,
focusedNodeParameters, focusedNodeParameters,
focusedNodeParametersInTelemetryFormat,
lastFocusTimestamp, lastFocusTimestamp,
focusPanelWidth, focusPanelWidth,
openWithFocusedNodeParameter, openWithFocusedNodeParameter,

View File

@@ -1233,6 +1233,11 @@ function onToggleFocusPanel() {
} }
focusPanelStore.toggleFocusPanel(); focusPanelStore.toggleFocusPanel();
telemetry.track(`User ${focusPanelStore.focusPanelActive ? 'opened' : 'closed'} focus panel`, {
source: 'canvasKeyboardShortcut',
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
parameterCount: focusPanelStore.focusedNodeParametersInTelemetryFormat.length,
});
} }
function closeNodeCreator() { function closeNodeCreator() {