mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
chore(editor): Add telemetry for canvas experiment (#18871)
This commit is contained in:
@@ -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<Element | null>(null);
|
||||
const assistantSidebarWidth = computed(() => assistantStore.chatWidth);
|
||||
const builderSidebarWidth = computed(() => builderStore.chatWidth);
|
||||
|
||||
useTelemetryContext({ ndv_source: computed(() => ndvStore.lastSetActiveNodeSource) });
|
||||
|
||||
onMounted(async () => {
|
||||
setAppZIndexes();
|
||||
logHiringBanner();
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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<InstanceType<typeof N8nInput> | 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<string>('');
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="focusPanelActive" :class="$style.wrapper" @keydown.stop>
|
||||
<div v-if="focusPanelActive" ref="wrapper" :class="$style.wrapper" @keydown.stop>
|
||||
<N8nResizeWrapper
|
||||
:width="focusPanelWidth"
|
||||
:supported-directions="['left']"
|
||||
|
||||
@@ -85,10 +85,13 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
|
||||
function toggleFocusPanel() {
|
||||
focusPanelStore.toggleFocusPanel();
|
||||
|
||||
telemetry.track(`User ${focusPanelStore.focusPanelActive ? 'opened' : 'closed'} focus panel`, {
|
||||
telemetry.track(
|
||||
focusPanelStore.focusPanelActive ? 'User opened focus panel' : 'User closed focus panel',
|
||||
{
|
||||
source: 'canvasButton',
|
||||
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onAskAssistantButtonClick() {
|
||||
|
||||
@@ -48,7 +48,7 @@ async function createPiniaStore(isActiveNode: boolean) {
|
||||
workflowsStore.nodeMetadata[node.name] = { pristine: true };
|
||||
|
||||
if (isActiveNode) {
|
||||
ndvStore.activeNodeName = node.name;
|
||||
ndvStore.setActiveNodeName(node.name, 'other');
|
||||
}
|
||||
|
||||
await useSettingsStore().getSettings();
|
||||
@@ -173,7 +173,7 @@ describe('NodeDetailsView', () => {
|
||||
pinia,
|
||||
});
|
||||
|
||||
ndvStore.activeNodeName = nodeName;
|
||||
ndvStore.setActiveNodeName(nodeName, 'other');
|
||||
|
||||
await waitFor(() => expect(getByTestId('ndv')).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByTestId('ndv-modal')).toBeInTheDocument());
|
||||
|
||||
@@ -39,6 +39,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useStyles } from '@/composables/useStyles';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
const emit = defineEmits<{
|
||||
saveKeyboardShortcut: [event: KeyboardEvent];
|
||||
@@ -75,6 +76,7 @@ const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const deviceSupport = useDeviceSupport();
|
||||
const telemetry = useTelemetry();
|
||||
const telemetryContext = useTelemetryContext({ view_shown: 'ndv' });
|
||||
const i18n = useI18n();
|
||||
const message = useMessage();
|
||||
const { APP_Z_INDEXES } = useStyles();
|
||||
@@ -371,7 +373,7 @@ const onInputTableMounted = (e: { avgRowHeight: number }) => {
|
||||
};
|
||||
|
||||
const onWorkflowActivate = () => {
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
setTimeout(() => {
|
||||
void workflowActivate.activateCurrentWorkflow('ndv');
|
||||
}, 1000);
|
||||
@@ -517,7 +519,7 @@ const close = async () => {
|
||||
workflow_id: workflowsStore.workflowId,
|
||||
});
|
||||
triggerWaitingWarningEnabled.value = false;
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
ndvStore.resetNDVPushRef();
|
||||
};
|
||||
|
||||
@@ -649,6 +651,7 @@ watch(
|
||||
data_pinning_tooltip_presented: pinDataDiscoveryTooltipVisible.value,
|
||||
input_displayed_row_height_avg: avgInputRowHeight.value,
|
||||
output_displayed_row_height_avg: avgOutputRowHeight.value,
|
||||
source: telemetryContext.ndv_source?.value ?? 'other',
|
||||
});
|
||||
}
|
||||
}, 2000); // wait for RunData to mount and present pindata discovery tooltip
|
||||
|
||||
@@ -51,7 +51,11 @@ async function createPiniaStore(
|
||||
{},
|
||||
);
|
||||
|
||||
ndvStore.activeNodeName = activeNodeName;
|
||||
if (activeNodeName) {
|
||||
ndvStore.setActiveNodeName(activeNodeName, 'other');
|
||||
} else {
|
||||
ndvStore.unsetActiveNodeName();
|
||||
}
|
||||
|
||||
await useSettingsStore().getSettings();
|
||||
await useUsersStore().loginWithCookie();
|
||||
@@ -201,7 +205,7 @@ describe('NodeDetailsViewV2', () => {
|
||||
pinia,
|
||||
});
|
||||
|
||||
ndvStore.activeNodeName = 'Manual Trigger';
|
||||
ndvStore.setActiveNodeName('Manual Trigger', 'other');
|
||||
|
||||
await waitFor(() => expect(getByTestId('ndv')).toBeInTheDocument());
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import InputPanel from './InputPanel.vue';
|
||||
import OutputPanel from './OutputPanel.vue';
|
||||
import PanelDragButtonV2 from './PanelDragButtonV2.vue';
|
||||
import TriggerPanel from './TriggerPanel.vue';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
const emit = defineEmits<{
|
||||
saveKeyboardShortcut: [event: KeyboardEvent];
|
||||
@@ -77,6 +78,7 @@ const uiStore = useUIStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const deviceSupport = useDeviceSupport();
|
||||
const telemetry = useTelemetry();
|
||||
const telemetryContext = useTelemetryContext({ view_shown: 'ndv' });
|
||||
const i18n = useI18n();
|
||||
const message = useMessage();
|
||||
const { APP_Z_INDEXES } = useStyles();
|
||||
@@ -370,7 +372,7 @@ const onInputTableMounted = (e: { avgRowHeight: number }) => {
|
||||
};
|
||||
|
||||
const onWorkflowActivate = () => {
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
setTimeout(() => {
|
||||
void workflowActivate.activateCurrentWorkflow('ndv');
|
||||
}, 1000);
|
||||
@@ -491,7 +493,7 @@ const close = async () => {
|
||||
workflow_id: workflowsStore.workflowId,
|
||||
});
|
||||
triggerWaitingWarningEnabled.value = false;
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
ndvStore.resetNDVPushRef();
|
||||
};
|
||||
|
||||
@@ -625,6 +627,7 @@ watch(
|
||||
data_pinning_tooltip_presented: pinDataDiscoveryTooltipVisible.value,
|
||||
input_displayed_row_height_avg: avgInputRowHeight.value,
|
||||
output_displayed_row_height_avg: avgOutputRowHeight.value,
|
||||
source: telemetryContext.ndv_source?.value ?? 'other',
|
||||
});
|
||||
}
|
||||
}, 2000); // wait for RunData to mount and present pindata discovery tooltip
|
||||
|
||||
@@ -316,7 +316,7 @@ describe('NodeExecuteButton', () => {
|
||||
|
||||
await userEvent.click(getByRole('button'));
|
||||
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||
expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
|
||||
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
|
||||
});
|
||||
@@ -330,7 +330,7 @@ describe('NodeExecuteButton', () => {
|
||||
|
||||
await userEvent.click(getByRole('button'));
|
||||
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||
expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
|
||||
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
|
||||
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
|
||||
});
|
||||
|
||||
@@ -332,7 +332,7 @@ async function onClick() {
|
||||
}
|
||||
|
||||
if (isChatNode.value || (isChatChild.value && ndvStore.isInputPanelEmpty)) {
|
||||
ndvStore.setActiveNodeName(null);
|
||||
ndvStore.unsetActiveNodeName();
|
||||
workflowsStore.chatPartialExecutionDestinationNode = props.nodeName;
|
||||
nodeViewEventBus.emit('openChat');
|
||||
} else if (isListeningForEvents.value) {
|
||||
|
||||
@@ -86,6 +86,7 @@ import { completeExpressionSyntax, shouldConvertToExpression } from '@/utils/exp
|
||||
import CssEditor from './CssEditor/CssEditor.vue';
|
||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||
import ExperimentalEmbeddedNdvMapper from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvMapper.vue';
|
||||
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||
|
||||
type Picker = { $emit: (arg0: string, arg1: Date) => void };
|
||||
|
||||
@@ -150,6 +151,7 @@ const settingsStore = useSettingsStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const uiStore = useUIStore();
|
||||
const focusPanelStore = useFocusPanelStore();
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
|
||||
const expressionLocalResolveCtx = inject(ExpressionLocalResolveContextSymbol, undefined);
|
||||
|
||||
@@ -1034,10 +1036,19 @@ async function optionSelected(command: string) {
|
||||
|
||||
case 'focus':
|
||||
nodeSettingsParameters.handleFocus(node.value, props.path, props.parameter);
|
||||
|
||||
if (experimentalNdvStore.isNdvInFocusPanelEnabled) {
|
||||
telemetry.track('User added focused param', {
|
||||
source: 'parameterButton',
|
||||
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
|
||||
});
|
||||
} else {
|
||||
telemetry.track('User opened focus panel', {
|
||||
source: 'parameterButton',
|
||||
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1241,6 +1252,7 @@ onClickOutside(wrapper, onBlur);
|
||||
},
|
||||
]"
|
||||
:style="parameterInputWrapperStyle"
|
||||
:data-parameter-path="path"
|
||||
>
|
||||
<ResourceLocator
|
||||
v-if="parameter.type === 'resourceLocator'"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import TextWithHighlights from './TextWithHighlights.vue';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
const LazyRunDataJsonActions = defineAsyncComponent(
|
||||
async () => await import('@/components/RunDataJsonActions.vue'),
|
||||
@@ -44,6 +45,7 @@ const ndvStore = useNDVStore();
|
||||
|
||||
const externalHooks = useExternalHooks();
|
||||
const telemetry = useTelemetry();
|
||||
const telemetryContext = useTelemetryContext();
|
||||
|
||||
const selectedJsonPath = ref(nonExistingJsonPath);
|
||||
const draggingPath = ref<null | string>(null);
|
||||
@@ -103,6 +105,7 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
src_view: 'json',
|
||||
src_element: el,
|
||||
success: false,
|
||||
view_shown: telemetryContext.view_shown,
|
||||
...mappingTelemetry,
|
||||
};
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ async function createPiniaWithActiveNode() {
|
||||
},
|
||||
};
|
||||
|
||||
ndvStore.activeNodeName = node.name;
|
||||
ndvStore.setActiveNodeName(node.name, 'other');
|
||||
|
||||
return {
|
||||
pinia,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { N8nIconButton, N8nInfoTip, N8nTooltip, N8nTree } from '@n8n/design-syst
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
const MAX_COLUMNS_LIMIT = 40;
|
||||
|
||||
@@ -78,6 +79,7 @@ const workflowsStore = useWorkflowsStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const telemetryContext = useTelemetryContext();
|
||||
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
|
||||
|
||||
const {
|
||||
@@ -323,6 +325,7 @@ function onDragEnd(column: string, src: string, depth = '0') {
|
||||
src_view: 'table',
|
||||
src_element: src,
|
||||
success: false,
|
||||
view_shown: telemetryContext.view_shown,
|
||||
...mappingTelemetry,
|
||||
};
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ function onLabelChange(value: string) {
|
||||
|
||||
function setupNode(options: MessageEventBusDestinationOptions) {
|
||||
workflowsStore.removeNode(node.value);
|
||||
ndvStore.activeNodeName = options.id ?? 'thisshouldnothappen';
|
||||
ndvStore.setActiveNodeName(options.id ?? 'thisshouldnothappen', 'other');
|
||||
workflowsStore.addNode(destinationToFakeINodeUi(options));
|
||||
nodeParameters.value = options as INodeParameters;
|
||||
logStreamingStore.items[destination.id!].destination = options;
|
||||
@@ -294,7 +294,7 @@ function onModalClose() {
|
||||
logStreamingStore.removeDestination(nodeParameters.value.id.toString());
|
||||
}
|
||||
}
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
callEventBus('closing', destination.id);
|
||||
uiStore.stateIsDirty = false;
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ const onLinkClick = (e: MouseEvent) => {
|
||||
pane: 'input',
|
||||
type: 'open-executions-log',
|
||||
});
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
void router.push({
|
||||
name: VIEWS.EXECUTIONS,
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ async function setupStore() {
|
||||
}),
|
||||
]);
|
||||
workflowsStore.workflow = workflow as IWorkflowDb;
|
||||
ndvStore.activeNodeName = 'Test Node Name';
|
||||
ndvStore.setActiveNodeName('Test Node Name', 'other');
|
||||
|
||||
return pinia;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import pick from 'lodash/pick';
|
||||
import { DateTime } from 'luxon';
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
type Props = {
|
||||
nodes?: IConnectedNode[];
|
||||
@@ -72,6 +73,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const telemetryContext = useTelemetryContext();
|
||||
const i18n = useI18n();
|
||||
const ndvStore = useNDVStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
@@ -416,6 +418,7 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
src_has_credential: hasCredential,
|
||||
src_element: el,
|
||||
success: false,
|
||||
view_shown: telemetryContext.view_shown,
|
||||
...mappingTelemetry,
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ const { nodeId, isReadOnly, subTitle, isEmbeddedInCanvas } = defineProps<{
|
||||
|
||||
defineSlots<{ actions?: {} }>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
dblclickHeader: [MouseEvent];
|
||||
}>();
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const uiStore = useUIStore();
|
||||
const { renameNode } = useCanvasOperations();
|
||||
@@ -37,12 +41,6 @@ function handleValueChanged(parameterData: IUpdateInformation) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleDoubleClickHeader() {
|
||||
if (activeNode.value) {
|
||||
ndvStore.setActiveNodeName(activeNode.value.name);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCaptureWheelEvent(event: WheelEvent) {
|
||||
if (event.ctrlKey) {
|
||||
// If the event is pinch, let it propagate and zoom canvas
|
||||
@@ -86,7 +84,7 @@ function handleCaptureWheelEvent(event: WheelEvent) {
|
||||
hide-sub-connections
|
||||
@value-changed="handleValueChanged"
|
||||
@capture-wheel-body="handleCaptureWheelEvent"
|
||||
@dblclick-header="handleDoubleClickHeader"
|
||||
@dblclick-header="emit('dblclickHeader', $event)"
|
||||
>
|
||||
<template #actions>
|
||||
<slot name="actions" />
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getNodeSubTitleText } from '@/components/canvas/experimental/experiment
|
||||
import ExperimentalEmbeddedNdvActions from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvActions.vue';
|
||||
import { useCanvas } from '@/composables/useCanvas';
|
||||
import { useExpressionResolveCtx } from '@/components/canvas/experimental/composables/useExpressionResolveCtx';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
|
||||
const { nodeId, isReadOnly } = defineProps<{
|
||||
nodeId: string;
|
||||
@@ -27,6 +28,9 @@ const experimentalNdvStore = useExperimentalNdvStore();
|
||||
const isExpanded = computed(() => !experimentalNdvStore.collapsedNodes[nodeId]);
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
useTelemetryContext({ view_shown: 'zoomed_view' });
|
||||
|
||||
const node = computed(() => workflowsStore.getNodeById(nodeId) ?? null);
|
||||
const nodeType = computed(() => {
|
||||
if (node.value) {
|
||||
@@ -65,7 +69,7 @@ function handleToggleExpand() {
|
||||
|
||||
function handleOpenNdv() {
|
||||
if (node.value) {
|
||||
ndvStore.setActiveNodeName(node.value.name);
|
||||
ndvStore.setActiveNodeName(node.value.name, 'canvas_zoomed_view');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +102,7 @@ watchOnce(isVisible, (visible) => {
|
||||
:sub-title="subTitle"
|
||||
:input-node-name="expressionResolveCtx?.inputNode?.name"
|
||||
is-embedded-in-canvas
|
||||
@dblclick-header="handleOpenNdv"
|
||||
>
|
||||
<template #actions>
|
||||
<ExperimentalEmbeddedNdvActions
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('useCalloutHelpers()', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||
expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
|
||||
expect(nodeCreatorStore.setNodeCreatorState).toHaveBeenCalledWith({
|
||||
source: NODE_CREATOR_OPEN_SOURCES.TEMPLATES_CALLOUT,
|
||||
createNodeActive: true,
|
||||
|
||||
@@ -135,7 +135,7 @@ export function useCalloutHelpers() {
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
const items: INodeCreateElement[] = getPreBuiltAgentNodeCreatorItems();
|
||||
|
||||
ndvStore.setActiveNodeName(null);
|
||||
ndvStore.unsetActiveNodeName();
|
||||
nodeCreatorStore.setNodeCreatorState({
|
||||
source: NODE_CREATOR_OPEN_SOURCES.TEMPLATES_CALLOUT,
|
||||
createNodeActive: true,
|
||||
|
||||
@@ -257,7 +257,9 @@ describe('useCanvasOperations', () => {
|
||||
{ openNDV: true },
|
||||
);
|
||||
|
||||
await waitFor(() => expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith('Test Name'));
|
||||
await waitFor(() =>
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith('Test Name', 'added_new_node'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set sticky node type as active node', async () => {
|
||||
@@ -1227,7 +1229,7 @@ describe('useCanvasOperations', () => {
|
||||
await renameNode(oldName, newName);
|
||||
|
||||
expect(workflowObject.renameNode).toHaveBeenCalledWith(oldName, newName);
|
||||
expect(ndvStore.activeNodeName).toBe(newName);
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(newName, expect.any(String));
|
||||
});
|
||||
|
||||
it('should not rename node when new name is same as old name', async () => {
|
||||
@@ -1291,7 +1293,7 @@ describe('useCanvasOperations', () => {
|
||||
const { revertRenameNode } = useCanvasOperations();
|
||||
await revertRenameNode(currentName, oldName);
|
||||
|
||||
expect(ndvStore.activeNodeName).toBe(oldName);
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(oldName, expect.any(String));
|
||||
});
|
||||
|
||||
it('should not revert node renaming when old name is same as new name', async () => {
|
||||
@@ -1318,9 +1320,9 @@ describe('useCanvasOperations', () => {
|
||||
ndvStore.activeNodeName = '';
|
||||
|
||||
const { setNodeActive } = useCanvasOperations();
|
||||
setNodeActive(nodeId);
|
||||
setNodeActive(nodeId, 'other');
|
||||
|
||||
expect(ndvStore.activeNodeName).toBe(nodeName);
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(nodeName, expect.any(String));
|
||||
});
|
||||
|
||||
it('should not change active node name when node does not exist', () => {
|
||||
@@ -1331,7 +1333,7 @@ describe('useCanvasOperations', () => {
|
||||
ndvStore.activeNodeName = 'Existing Node';
|
||||
|
||||
const { setNodeActive } = useCanvasOperations();
|
||||
setNodeActive(nodeId);
|
||||
setNodeActive(nodeId, 'other');
|
||||
|
||||
expect(ndvStore.activeNodeName).toBe('Existing Node');
|
||||
});
|
||||
@@ -1343,7 +1345,7 @@ describe('useCanvasOperations', () => {
|
||||
workflowsStore.getNodeById.mockImplementation(() => node);
|
||||
|
||||
const { setNodeActive } = useCanvasOperations();
|
||||
setNodeActive(node.id);
|
||||
setNodeActive(node.id, 'other');
|
||||
|
||||
expect(workflowsStore.setNodePristine).toHaveBeenCalledWith(node.name, false);
|
||||
});
|
||||
@@ -1356,9 +1358,9 @@ describe('useCanvasOperations', () => {
|
||||
ndvStore.activeNodeName = '';
|
||||
|
||||
const { setNodeActiveByName } = useCanvasOperations();
|
||||
setNodeActiveByName(nodeName);
|
||||
setNodeActiveByName(nodeName, 'other');
|
||||
|
||||
expect(ndvStore.activeNodeName).toBe(nodeName);
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(nodeName, expect.any(String));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3165,7 +3167,7 @@ describe('useCanvasOperations', () => {
|
||||
await openExecution(executionId, nodeId);
|
||||
|
||||
expect(workflowsStore.getNodeById).toHaveBeenCalledWith(nodeId);
|
||||
expect(ndvStore.activeNodeName).toBe(mockNode.name);
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(mockNode.name, expect.any(String));
|
||||
});
|
||||
|
||||
it('should show error when nodeId is provided but node does not exist', async () => {
|
||||
|
||||
@@ -114,6 +114,7 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||
import type { TelemetryNdvSource, TelemetryNdvType } from '@/types/telemetry';
|
||||
|
||||
type AddNodeData = Partial<INodeUi> & {
|
||||
type: string;
|
||||
@@ -325,7 +326,7 @@ export function useCanvasOperations() {
|
||||
|
||||
const isRenamingActiveNode = ndvStore.activeNodeName === currentName;
|
||||
if (isRenamingActiveNode) {
|
||||
ndvStore.activeNodeName = newName;
|
||||
ndvStore.setActiveNodeName(newName, 'other');
|
||||
}
|
||||
|
||||
if (trackHistory && trackBulk) {
|
||||
@@ -529,22 +530,22 @@ export function useCanvasOperations() {
|
||||
}
|
||||
}
|
||||
|
||||
function setNodeActive(id: string) {
|
||||
function setNodeActive(id: string, source: TelemetryNdvSource) {
|
||||
const node = workflowsStore.getNodeById(id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
workflowsStore.setNodePristine(node.name, false);
|
||||
setNodeActiveByName(node.name);
|
||||
setNodeActiveByName(node.name, source);
|
||||
}
|
||||
|
||||
function setNodeActiveByName(name: string) {
|
||||
ndvStore.activeNodeName = name;
|
||||
function setNodeActiveByName(name: string, source: TelemetryNdvSource) {
|
||||
ndvStore.setActiveNodeName(name, source);
|
||||
}
|
||||
|
||||
function clearNodeActive() {
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
}
|
||||
|
||||
function setNodeParameters(id: string, parameters: Record<string, unknown>) {
|
||||
@@ -787,25 +788,31 @@ export function useCanvasOperations() {
|
||||
nodeHelpers.updateNodeCredentialIssues(nodeData);
|
||||
nodeHelpers.updateNodeInputIssues(nodeData);
|
||||
|
||||
const isStickyNode = nodeData.type === STICKY_NODE_TYPE;
|
||||
const nextView =
|
||||
isStickyNode || !options.openNDV || preventOpeningNDV
|
||||
? undefined
|
||||
: experimentalNdvStore.isNdvInFocusPanelEnabled &&
|
||||
focusPanelStore.focusPanelActive &&
|
||||
focusPanelStore.resolvedParameter === undefined
|
||||
? 'focus_panel'
|
||||
: experimentalNdvStore.isZoomedViewEnabled
|
||||
? 'zoomed_view'
|
||||
: 'ndv';
|
||||
|
||||
if (options.telemetry) {
|
||||
trackAddNode(nodeData, options);
|
||||
trackAddNode(nodeData, options, nextView);
|
||||
}
|
||||
|
||||
if (nodeData.type !== STICKY_NODE_TYPE) {
|
||||
if (!isStickyNode) {
|
||||
void externalHooks.run('nodeView.addNodeButton', { nodeTypeName: nodeData.type });
|
||||
|
||||
if (options.openNDV && !preventOpeningNDV) {
|
||||
if (
|
||||
experimentalNdvStore.isNdvInFocusPanelEnabled &&
|
||||
focusPanelStore.focusPanelActive &&
|
||||
focusPanelStore.focusedNodeParameters.length === 0
|
||||
) {
|
||||
if (nextView === 'focus_panel') {
|
||||
// Do nothing. The added node get selected and the details are shown in the focus panel
|
||||
} else if (experimentalNdvStore.isZoomedViewEnabled) {
|
||||
} else if (nextView === 'zoomed_view') {
|
||||
experimentalNdvStore.setNodeNameToBeFocused(nodeData.name);
|
||||
} else {
|
||||
ndvStore.setActiveNodeName(nodeData.name);
|
||||
}
|
||||
} else if (nextView === 'ndv') {
|
||||
ndvStore.setActiveNodeName(nodeData.name, 'added_new_node');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -897,13 +904,13 @@ export function useCanvasOperations() {
|
||||
}
|
||||
}
|
||||
|
||||
function trackAddNode(nodeData: INodeUi, options: AddNodeOptions) {
|
||||
function trackAddNode(nodeData: INodeUi, options: AddNodeOptions, nextView?: TelemetryNdvType) {
|
||||
switch (nodeData.type) {
|
||||
case STICKY_NODE_TYPE:
|
||||
trackAddStickyNoteNode();
|
||||
break;
|
||||
default:
|
||||
trackAddDefaultNode(nodeData, options);
|
||||
trackAddDefaultNode(nodeData, options, nextView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,7 +920,11 @@ export function useCanvasOperations() {
|
||||
});
|
||||
}
|
||||
|
||||
function trackAddDefaultNode(nodeData: INodeUi, options: AddNodeOptions) {
|
||||
function trackAddDefaultNode(
|
||||
nodeData: INodeUi,
|
||||
options: AddNodeOptions,
|
||||
nextView?: TelemetryNdvType,
|
||||
) {
|
||||
// Extract action-related parameters from node parameters if available
|
||||
const nodeParameters = nodeData.parameters;
|
||||
const resource =
|
||||
@@ -934,6 +945,7 @@ export function useCanvasOperations() {
|
||||
resource,
|
||||
operation,
|
||||
action: options.actionName,
|
||||
next_view_shown: nextView,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2192,7 +2204,7 @@ export function useCanvasOperations() {
|
||||
if (nodeId) {
|
||||
const node = workflowsStore.getNodeById(nodeId);
|
||||
if (node) {
|
||||
ndvStore.activeNodeName = node.name;
|
||||
ndvStore.setActiveNodeName(node.name, 'other');
|
||||
} else {
|
||||
toast.showError(
|
||||
new Error(`Node with id "${nodeId}" could not be found!`),
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('useNodeSettingsParameters', () => {
|
||||
};
|
||||
ndvStore.activeNodeName = 'Node1';
|
||||
ndvStore.setActiveNodeName = vi.fn();
|
||||
ndvStore.unsetActiveNodeName = vi.fn();
|
||||
ndvStore.resetNDVPushRef = vi.fn();
|
||||
focusPanelStore.openWithFocusedNodeParameter = vi.fn();
|
||||
focusPanelStore.focusPanelActive = false;
|
||||
@@ -73,7 +74,7 @@ describe('useNodeSettingsParameters', () => {
|
||||
parameter,
|
||||
});
|
||||
|
||||
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||
expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
|
||||
expect(ndvStore.resetNDVPushRef).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ export function useNodeSettingsParameters() {
|
||||
});
|
||||
|
||||
if (ndvStore.activeNode) {
|
||||
ndvStore.setActiveNodeName(null);
|
||||
ndvStore.unsetActiveNodeName();
|
||||
ndvStore.resetNDVPushRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { computed, defineComponent, h } from 'vue';
|
||||
import { useTelemetryContext } from './useTelemetryContext';
|
||||
|
||||
describe(useTelemetryContext, () => {
|
||||
it('should return empty context when no context is provided', () => {
|
||||
const TestComponent = defineComponent({
|
||||
setup() {
|
||||
const context = useTelemetryContext();
|
||||
return () => h('div', JSON.stringify([context.view_shown, context.ndv_source?.value]));
|
||||
},
|
||||
});
|
||||
|
||||
expect(mount(TestComponent).text()).toBe('[null,null]');
|
||||
});
|
||||
|
||||
it('should return context with overrides when overrides are provided', () => {
|
||||
const TestComponent = defineComponent({
|
||||
setup() {
|
||||
const context = useTelemetryContext({ view_shown: 'ndv' });
|
||||
return () => h('div', JSON.stringify([context.view_shown, context.ndv_source?.value]));
|
||||
},
|
||||
});
|
||||
|
||||
expect(mount(TestComponent).text()).toBe('["ndv",null]');
|
||||
});
|
||||
|
||||
it('should inherit context from parent and merge with overrides', () => {
|
||||
const ChildComponent = defineComponent({
|
||||
setup() {
|
||||
const childCtx = useTelemetryContext({ view_shown: 'ndv' });
|
||||
return () => h('div', JSON.stringify([childCtx.view_shown, childCtx.ndv_source?.value]));
|
||||
},
|
||||
});
|
||||
const ParentComponent = defineComponent({
|
||||
setup() {
|
||||
useTelemetryContext({
|
||||
view_shown: 'focus_panel',
|
||||
ndv_source: computed(() => 'added_new_node'),
|
||||
});
|
||||
return () => h('div', [h(ChildComponent)]);
|
||||
},
|
||||
});
|
||||
|
||||
expect(mount(ParentComponent).text()).toBe('["ndv","added_new_node"]');
|
||||
});
|
||||
|
||||
it('should handle multiple nested contexts correctly', () => {
|
||||
const Level4Component = defineComponent({
|
||||
setup() {
|
||||
const ctx = useTelemetryContext();
|
||||
return () => h('div', JSON.stringify([ctx.view_shown, ctx.ndv_source?.value]));
|
||||
},
|
||||
});
|
||||
const Level3Component = defineComponent({
|
||||
setup() {
|
||||
const ctx = useTelemetryContext({ view_shown: 'ndv' });
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', JSON.stringify([ctx.view_shown, ctx.ndv_source?.value])),
|
||||
h(Level4Component),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const Level2Component = defineComponent({
|
||||
setup() {
|
||||
const ctx = useTelemetryContext({ ndv_source: computed(() => 'other') });
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', JSON.stringify([ctx.view_shown, ctx.ndv_source?.value])),
|
||||
h(Level3Component),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const Level1Component = defineComponent({
|
||||
setup() {
|
||||
const ctx = useTelemetryContext({ view_shown: 'focus_panel' });
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', JSON.stringify([ctx.view_shown, ctx.ndv_source?.value])),
|
||||
h(Level2Component),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
expect(mount(Level1Component).text()).toBe(
|
||||
[
|
||||
'["focus_panel",null]',
|
||||
'["focus_panel","other"]',
|
||||
'["ndv","other"]',
|
||||
'["ndv","other"]',
|
||||
].join(''),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { TelemetryContextSymbol } from '@/constants';
|
||||
import type { TelemetryContext } from '@/types/telemetry';
|
||||
import { inject, provide } from 'vue';
|
||||
|
||||
/**
|
||||
* Composable that injects/provides data for telemetry payload.
|
||||
*
|
||||
* Intended for populating telemetry payload in reusable components to include
|
||||
* contextual information that depends on which part of UI it is used.
|
||||
*/
|
||||
export function useTelemetryContext(overrides: TelemetryContext = {}): TelemetryContext {
|
||||
const ctx = inject(TelemetryContextSymbol, {});
|
||||
const merged = { ...ctx, ...overrides };
|
||||
|
||||
provide(TelemetryContextSymbol, merged);
|
||||
|
||||
return merged;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
import type { ComputedRef, InjectionKey, Ref } from 'vue';
|
||||
import type { ExpressionLocalResolveContext } from './types/expressions';
|
||||
import { DATA_STORE_MODULE_NAME } from './features/dataStore/constants';
|
||||
import type { TelemetryContext } from './types/telemetry';
|
||||
|
||||
export const MAX_WORKFLOW_SIZE = 1024 * 1024 * 16; // Workflow size limit in bytes
|
||||
export const MAX_EXPECTED_REQUEST_SIZE = 2048; // Expected maximum workflow request metadata (i.e. headers) size in bytes
|
||||
@@ -982,6 +983,7 @@ export const PopOutWindowKey: InjectionKey<Ref<Window | undefined>> = Symbol('Po
|
||||
export const ExpressionLocalResolveContextSymbol: InjectionKey<
|
||||
ComputedRef<ExpressionLocalResolveContext | undefined>
|
||||
> = Symbol('ExpressionLocalResolveContext');
|
||||
export const TelemetryContextSymbol: InjectionKey<TelemetryContext> = Symbol('TelemetryContext');
|
||||
|
||||
export const APP_MODALS_ELEMENT_ID = 'app-modals';
|
||||
export const CODEMIRROR_TOOLTIP_CONTAINER_ELEMENT_ID = 'cm-tooltip-container';
|
||||
|
||||
@@ -115,7 +115,7 @@ function handleResizeOverviewPanelEnd() {
|
||||
}
|
||||
|
||||
function handleOpenNdv(treeNode: LogEntry) {
|
||||
ndvStore.setActiveNodeName(treeNode.node.name);
|
||||
ndvStore.setActiveNodeName(treeNode.node.name, 'logs_view');
|
||||
|
||||
void nextTick(() => {
|
||||
const source = treeNode.runData?.source[0];
|
||||
|
||||
@@ -62,7 +62,7 @@ const isExecuting = computed(
|
||||
);
|
||||
|
||||
function handleClickOpenNdv() {
|
||||
ndvStore.setActiveNodeName(logEntry.node.name);
|
||||
ndvStore.setActiveNodeName(logEntry.node.name, 'logs_view');
|
||||
}
|
||||
|
||||
function handleChangeDisplayMode(value: IRunDataDisplayMode) {
|
||||
|
||||
@@ -87,6 +87,12 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const resolvedParameter = computed(() =>
|
||||
focusedNodeParameters.value[0] && isRichParameter(focusedNodeParameters.value[0])
|
||||
? focusedNodeParameters.value[0]
|
||||
: undefined,
|
||||
);
|
||||
|
||||
function _setOptions({
|
||||
parameters,
|
||||
isActive,
|
||||
@@ -191,6 +197,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
||||
focusedNodeParametersInTelemetryFormat,
|
||||
lastFocusTimestamp,
|
||||
focusPanelWidth,
|
||||
resolvedParameter,
|
||||
openWithFocusedNodeParameter,
|
||||
isRichParameter,
|
||||
closeFocusPanel,
|
||||
|
||||
@@ -25,6 +25,7 @@ import { defineStore } from 'pinia';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useWorkflowsStore } from './workflows.store';
|
||||
import { computed, ref } from 'vue';
|
||||
import type { TelemetryNdvSource } from '@/types/telemetry';
|
||||
|
||||
const DEFAULT_MAIN_PANEL_DIMENSIONS = {
|
||||
relativeLeft: 1,
|
||||
@@ -91,6 +92,7 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
|
||||
const isAutocompleteOnboarded = ref(localStorageAutoCompleteIsOnboarded.value === 'true');
|
||||
|
||||
const highlightDraggables = ref(false);
|
||||
const lastSetActiveNodeSource = ref<TelemetryNdvSource>();
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
@@ -215,8 +217,18 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
|
||||
|
||||
const isNDVOpen = computed(() => activeNodeName.value !== null);
|
||||
|
||||
const setActiveNodeName = (nodeName: string | null): void => {
|
||||
const unsetActiveNodeName = (): void => {
|
||||
activeNodeName.value = null;
|
||||
lastSetActiveNodeSource.value = undefined;
|
||||
};
|
||||
|
||||
const setActiveNodeName = (nodeName: string, source: TelemetryNdvSource): void => {
|
||||
if (activeNodeName.value === nodeName) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeNodeName.value = nodeName;
|
||||
lastSetActiveNodeSource.value = source;
|
||||
};
|
||||
|
||||
const setInputNodeName = (nodeName: string | undefined): void => {
|
||||
@@ -411,7 +423,9 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
|
||||
expressionOutputItemIndex,
|
||||
isTableHoverOnboarded,
|
||||
mainPanelDimensions,
|
||||
lastSetActiveNodeSource,
|
||||
setActiveNodeName,
|
||||
unsetActiveNodeName,
|
||||
setInputNodeName,
|
||||
setInputRunIndex,
|
||||
setOutputRunIndex,
|
||||
|
||||
@@ -43,6 +43,7 @@ import { CanvasConnectionMode } from '@/types';
|
||||
import { isVueFlowConnection } from '@/utils/typeGuards';
|
||||
import type { PartialBy } from '@/utils/typeHelpers';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import type { TelemetryNdvType } from '@/types/telemetry';
|
||||
|
||||
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
@@ -107,7 +108,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
const nodeName = node ?? ndvStore.activeNodeName;
|
||||
const nodeData = nodeName ? workflowsStore.getNodeByName(nodeName) : null;
|
||||
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
|
||||
setTimeout(() => {
|
||||
if (creatorView) {
|
||||
@@ -215,7 +216,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
}
|
||||
|
||||
function openNodeCreatorForTriggerNodes(source: NodeCreatorOpenSource) {
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
setSelectedView(TRIGGER_NODE_CREATOR_VIEW);
|
||||
setShowScrim(true);
|
||||
setNodeCreatorState({
|
||||
@@ -238,7 +239,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
transformNodeType(a, actionNode.properties.displayName, 'action'),
|
||||
);
|
||||
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
setSelectedView(REGULAR_NODE_CREATOR_VIEW);
|
||||
setNodeCreatorState({
|
||||
source: eventSource,
|
||||
@@ -417,6 +418,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
resource?: string;
|
||||
operation?: string;
|
||||
action?: string;
|
||||
next_view_shown?: TelemetryNdvType;
|
||||
}) {
|
||||
trackNodeCreatorEvent('User added node to workflow canvas', properties);
|
||||
}
|
||||
|
||||
16
packages/frontend/editor-ui/src/types/telemetry.ts
Normal file
16
packages/frontend/editor-ui/src/types/telemetry.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { ComputedRef } from 'vue';
|
||||
|
||||
export type TelemetryNdvType = 'ndv' | 'focus_panel' | 'zoomed_view';
|
||||
|
||||
export type TelemetryNdvSource =
|
||||
| 'added_new_node'
|
||||
| 'canvas_default_view'
|
||||
| 'canvas_zoomed_view'
|
||||
| 'focus_panel'
|
||||
| 'logs_view'
|
||||
| 'other';
|
||||
|
||||
export type TelemetryContext = Partial<{
|
||||
view_shown: TelemetryNdvType;
|
||||
ndv_source: ComputedRef<TelemetryNdvSource | undefined>;
|
||||
}>;
|
||||
@@ -769,7 +769,7 @@ function onSetNodeActivated(id: string, event?: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
setNodeActive(id);
|
||||
setNodeActive(id, 'canvas_default_view');
|
||||
}
|
||||
|
||||
function onOpenSubWorkflow(id: string) {
|
||||
@@ -1212,7 +1212,7 @@ function onSwitchActiveNode(nodeName: string) {
|
||||
const node = workflowsStore.getNodeByName(nodeName);
|
||||
if (!node) return;
|
||||
|
||||
setNodeActiveByName(nodeName);
|
||||
setNodeActiveByName(nodeName, 'other');
|
||||
selectNodes([node.id]);
|
||||
}
|
||||
|
||||
@@ -1773,7 +1773,7 @@ function registerCustomActions() {
|
||||
registerCustomAction({
|
||||
key: 'openNodeDetail',
|
||||
action: ({ node }: { node: string }) => {
|
||||
setNodeActiveByName(node);
|
||||
setNodeActiveByName(node, 'other');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1795,7 +1795,7 @@ function registerCustomActions() {
|
||||
registerCustomAction({
|
||||
key: 'showNodeCreator',
|
||||
action: () => {
|
||||
ndvStore.activeNodeName = null;
|
||||
ndvStore.unsetActiveNodeName();
|
||||
|
||||
void nextTick(() => {
|
||||
void onOpenNodeCreatorForTriggerNodes(NODE_CREATOR_OPEN_SOURCES.TAB);
|
||||
@@ -1824,7 +1824,7 @@ function showAddFirstStepIfEnabled() {
|
||||
function updateNodeRoute(nodeId: string) {
|
||||
const nodeUi = workflowsStore.findNodeByPartialId(nodeId);
|
||||
if (nodeUi) {
|
||||
setNodeActive(nodeUi.id);
|
||||
setNodeActive(nodeUi.id, 'other');
|
||||
} else {
|
||||
toast.showToast({
|
||||
title: i18n.baseText('nodeView.showMessage.ndvUrl.missingNodes.title'),
|
||||
@@ -1889,7 +1889,7 @@ watch(
|
||||
watch(
|
||||
() => route.params.nodeId,
|
||||
async (newId) => {
|
||||
if (typeof newId !== 'string' || newId === '') ndvStore.activeNodeName = null;
|
||||
if (typeof newId !== 'string' || newId === '') ndvStore.unsetActiveNodeName();
|
||||
else {
|
||||
updateNodeRoute(newId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user