mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix: Bring back nodes panel telemetry events (#11456)
This commit is contained in:
parent
529d4fc3ef
commit
130c942f63
|
@ -29,6 +29,7 @@ import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue'
|
||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
nodeTypeSelected: [value: [actionKey: string, nodeName: string] | [nodeName: string]];
|
nodeTypeSelected: [value: [actionKey: string, nodeName: string] | [nodeName: string]];
|
||||||
|
@ -47,6 +48,8 @@ const {
|
||||||
actionsCategoryLocales,
|
actionsCategoryLocales,
|
||||||
} = useActions();
|
} = useActions();
|
||||||
|
|
||||||
|
const nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
|
||||||
// We only inject labels if search is empty
|
// We only inject labels if search is empty
|
||||||
const parsedTriggerActions = computed(() =>
|
const parsedTriggerActions = computed(() =>
|
||||||
parseActions(actions.value, actionsCategoryLocales.value.triggers, false),
|
parseActions(actions.value, actionsCategoryLocales.value.triggers, false),
|
||||||
|
@ -182,7 +185,7 @@ function trackActionsView() {
|
||||||
};
|
};
|
||||||
|
|
||||||
void useExternalHooks().run('nodeCreateList.onViewActions', trackingPayload);
|
void useExternalHooks().run('nodeCreateList.onViewActions', trackingPayload);
|
||||||
telemetry?.trackNodesPanel('nodeCreateList.onViewActions', trackingPayload);
|
nodeCreatorStore.onViewActions(trackingPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSearch() {
|
function resetSearch() {
|
||||||
|
@ -206,7 +209,7 @@ function addHttpNode() {
|
||||||
void useExternalHooks().run('nodeCreateList.onActionsCustmAPIClicked', {
|
void useExternalHooks().run('nodeCreateList.onActionsCustmAPIClicked', {
|
||||||
app_identifier,
|
app_identifier,
|
||||||
});
|
});
|
||||||
telemetry?.trackNodesPanel('nodeCreateList.onActionsCustmAPIClicked', { app_identifier });
|
nodeCreatorStore.onActionsCustomAPIClicked({ app_identifier });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anonymous component to handle triggers and actions rendering order
|
// Anonymous component to handle triggers and actions rendering order
|
||||||
|
|
|
@ -23,7 +23,6 @@ import ItemsRenderer from '../Renderers/ItemsRenderer.vue';
|
||||||
import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue';
|
import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue';
|
||||||
import NoResults from '../Panel/NoResults.vue';
|
import NoResults from '../Panel/NoResults.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
|
||||||
import { getNodeIcon, getNodeIconColor, getNodeIconUrl } from '@/utils/nodeTypesUtils';
|
import { getNodeIcon, getNodeIconColor, getNodeIconUrl } from '@/utils/nodeTypesUtils';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
|
||||||
|
@ -36,11 +35,10 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const telemetry = useTelemetry();
|
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
|
||||||
const { mergedNodes, actions } = useNodeCreatorStore();
|
const { mergedNodes, actions, onSubcategorySelected } = useNodeCreatorStore();
|
||||||
const { pushViewStack, popViewStack } = useViewStacks();
|
const { pushViewStack, popViewStack } = useViewStacks();
|
||||||
|
|
||||||
const { registerKeyHook } = useKeyboardNavigation();
|
const { registerKeyHook } = useKeyboardNavigation();
|
||||||
|
@ -83,7 +81,7 @@ function onSelected(item: INodeCreateElement) {
|
||||||
sections: item.properties.sections,
|
sections: item.properties.sections,
|
||||||
});
|
});
|
||||||
|
|
||||||
telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', {
|
onSubcategorySelected({
|
||||||
subcategory: item.key,
|
subcategory: item.key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -153,9 +151,6 @@ function onSelected(item: INodeCreateElement) {
|
||||||
|
|
||||||
if (item.type === 'link') {
|
if (item.type === 'link') {
|
||||||
window.open(item.properties.url, '_blank');
|
window.open(item.properties.url, '_blank');
|
||||||
telemetry.trackNodesPanel('nodeCreateList.onLinkSelected', {
|
|
||||||
link: item.properties.url,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,15 @@ import SearchBar from './SearchBar.vue';
|
||||||
import ActionsRenderer from '../Modes/ActionsMode.vue';
|
import ActionsRenderer from '../Modes/ActionsMode.vue';
|
||||||
import NodesRenderer from '../Modes/NodesMode.vue';
|
import NodesRenderer from '../Modes/NodesMode.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const { callDebounced } = useDebounce();
|
||||||
|
|
||||||
const { mergedNodes } = useNodeCreatorStore();
|
const { mergedNodes } = useNodeCreatorStore();
|
||||||
const { pushViewStack, popViewStack, updateCurrentViewStack } = useViewStacks();
|
const { pushViewStack, popViewStack, updateCurrentViewStack } = useViewStacks();
|
||||||
const { setActiveItemIndex, attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation();
|
const { setActiveItemIndex, attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation();
|
||||||
|
const nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
|
||||||
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
||||||
|
|
||||||
|
@ -55,6 +58,19 @@ function onSearch(value: string) {
|
||||||
if (activeViewStack.value.uuid) {
|
if (activeViewStack.value.uuid) {
|
||||||
updateCurrentViewStack({ search: value });
|
updateCurrentViewStack({ search: value });
|
||||||
void setActiveItemIndex(getDefaultActiveIndex(value));
|
void setActiveItemIndex(getDefaultActiveIndex(value));
|
||||||
|
if (value.length) {
|
||||||
|
callDebounced(
|
||||||
|
nodeCreatorStore.onNodeFilterChanged,
|
||||||
|
{ trailing: true, debounceTime: 2000 },
|
||||||
|
{
|
||||||
|
newValue: value,
|
||||||
|
filteredNodes: activeViewStack.value.items ?? [],
|
||||||
|
filterMode: activeViewStack.value.rootView ?? 'Regular',
|
||||||
|
subcategory: activeViewStack.value.subcategory,
|
||||||
|
title: activeViewStack.value.title,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +315,7 @@ function onBackButton() {
|
||||||
margin-top: var(--spacing-4xs);
|
margin-top: var(--spacing-4xs);
|
||||||
font-size: var(--font-size-s);
|
font-size: var(--font-size-s);
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
|
|
||||||
color: var(--color-text-base);
|
color: var(--color-text-base);
|
||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useKeyboardNavigation } from '../composables/useKeyboardNavigation';
|
||||||
import { useViewStacks } from '../composables/useViewStacks';
|
import { useViewStacks } from '../composables/useViewStacks';
|
||||||
import ItemsRenderer from './ItemsRenderer.vue';
|
import ItemsRenderer from './ItemsRenderer.vue';
|
||||||
import CategoryItem from '../ItemTypes/CategoryItem.vue';
|
import CategoryItem from '../ItemTypes/CategoryItem.vue';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
elements: INodeCreateElement[];
|
elements: INodeCreateElement[];
|
||||||
|
@ -24,10 +24,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
elements: () => [],
|
elements: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const telemetry = useTelemetry();
|
|
||||||
const { popViewStack } = useViewStacks();
|
const { popViewStack } = useViewStacks();
|
||||||
const { registerKeyHook } = useKeyboardNavigation();
|
const { registerKeyHook } = useKeyboardNavigation();
|
||||||
const { workflowId } = useWorkflowsStore();
|
const { workflowId } = useWorkflowsStore();
|
||||||
|
const nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
|
||||||
const activeItemId = computed(() => useKeyboardNavigation()?.activeItemId);
|
const activeItemId = computed(() => useKeyboardNavigation()?.activeItemId);
|
||||||
const actionCount = computed(() => props.elements.filter(({ type }) => type === 'action').length);
|
const actionCount = computed(() => props.elements.filter(({ type }) => type === 'action').length);
|
||||||
|
@ -38,10 +38,11 @@ function toggleExpanded() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setExpanded(isExpanded: boolean) {
|
function setExpanded(isExpanded: boolean) {
|
||||||
|
const prev = expanded.value;
|
||||||
expanded.value = isExpanded;
|
expanded.value = isExpanded;
|
||||||
|
|
||||||
if (expanded.value) {
|
if (expanded.value && !prev) {
|
||||||
telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', {
|
nodeCreatorStore.onCategoryExpanded({
|
||||||
category_name: props.category,
|
category_name: props.category,
|
||||||
workflow_id: workflowId,
|
workflow_id: workflowId,
|
||||||
});
|
});
|
||||||
|
|
|
@ -332,7 +332,11 @@ export const useActions = () => {
|
||||||
return storeWatcher;
|
return storeWatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackActionSelected(action: IUpdateInformation, telemetry: Telemetry, rootView: string) {
|
function trackActionSelected(
|
||||||
|
action: IUpdateInformation,
|
||||||
|
_telemetry: Telemetry,
|
||||||
|
rootView: string,
|
||||||
|
) {
|
||||||
const payload = {
|
const payload = {
|
||||||
node_type: action.key,
|
node_type: action.key,
|
||||||
action: action.name,
|
action: action.name,
|
||||||
|
@ -340,7 +344,7 @@ export const useActions = () => {
|
||||||
resource: (action.value as INodeParameters).resource || '',
|
resource: (action.value as INodeParameters).resource || '',
|
||||||
};
|
};
|
||||||
void useExternalHooks().run('nodeCreateList.addAction', payload);
|
void useExternalHooks().run('nodeCreateList.addAction', payload);
|
||||||
telemetry?.trackNodesPanel('nodeCreateList.addAction', payload);
|
useNodeCreatorStore().onAddActions(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -84,7 +84,6 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
IPinData,
|
IPinData,
|
||||||
ITelemetryTrackProperties,
|
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
NodeInputConnections,
|
NodeInputConnections,
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
|
@ -733,25 +732,22 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackAddStickyNoteNode() {
|
function trackAddStickyNoteNode() {
|
||||||
telemetry.trackNodesPanel('nodeView.addSticky', {
|
telemetry.track('User inserted workflow note', {
|
||||||
workflow_id: workflowsStore.workflowId,
|
workflow_id: workflowsStore.workflowId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackAddDefaultNode(nodeData: INodeUi, options: AddNodeOptions) {
|
function trackAddDefaultNode(nodeData: INodeUi, options: AddNodeOptions) {
|
||||||
const trackProperties: ITelemetryTrackProperties = {
|
nodeCreatorStore.onNodeAddedToCanvas({
|
||||||
node_type: nodeData.type,
|
node_type: nodeData.type,
|
||||||
node_version: nodeData.typeVersion,
|
node_version: nodeData.typeVersion,
|
||||||
is_auto_add: options.isAutoAdd,
|
is_auto_add: options.isAutoAdd,
|
||||||
workflow_id: workflowsStore.workflowId,
|
workflow_id: workflowsStore.workflowId,
|
||||||
drag_and_drop: options.dragAndDrop,
|
drag_and_drop: options.dragAndDrop,
|
||||||
};
|
input_node_type: uiStore.lastInteractedWithNode
|
||||||
|
? uiStore.lastInteractedWithNode.type
|
||||||
if (uiStore.lastInteractedWithNode) {
|
: undefined,
|
||||||
trackProperties.input_node_type = uiStore.lastInteractedWithNode.type;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
telemetry.trackNodesPanel('nodeView.addNodeButton', trackProperties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
export const nodesPanelSession = {
|
|
||||||
pushRef: '',
|
|
||||||
data: {
|
|
||||||
nodeFilter: '',
|
|
||||||
resultsNodes: [] as string[],
|
|
||||||
filterMode: 'Regular',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hooksGenerateNodesPanelEvent = () => {
|
|
||||||
return {
|
|
||||||
eventName: 'User entered nodes panel search term',
|
|
||||||
properties: {
|
|
||||||
search_string: nodesPanelSession.data.nodeFilter,
|
|
||||||
results_count: nodesPanelSession.data.resultsNodes.length,
|
|
||||||
results_nodes: nodesPanelSession.data.resultsNodes,
|
|
||||||
filter_mode: nodesPanelSession.data.filterMode,
|
|
||||||
nodes_panel_session_id: nodesPanelSession.pushRef,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hooksResetNodesPanelSession = () => {
|
|
||||||
nodesPanelSession.pushRef = `nodes_panel_session_${new Date().valueOf()}`;
|
|
||||||
nodesPanelSession.data = {
|
|
||||||
nodeFilter: '',
|
|
||||||
resultsNodes: [],
|
|
||||||
filterMode: 'Regular',
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,2 +1 @@
|
||||||
export * from './hooksAddFakeDoorFeatures';
|
export * from './hooksAddFakeDoorFeatures';
|
||||||
export * from './hooksNodesPanel';
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import type { ITelemetrySettings } from '@n8n/api-types';
|
||||||
import type { ITelemetryTrackProperties, IDataObject } from 'n8n-workflow';
|
import type { ITelemetryTrackProperties, IDataObject } from 'n8n-workflow';
|
||||||
import type { RouteLocation } from 'vue-router';
|
import type { RouteLocation } from 'vue-router';
|
||||||
|
|
||||||
import type { INodeCreateElement, IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
import type { IUserNodesPanelSession, RudderStack } from './telemetry.types';
|
import type { RudderStack } from './telemetry.types';
|
||||||
import {
|
import {
|
||||||
APPEND_ATTRIBUTION_DEFAULT_PATH,
|
APPEND_ATTRIBUTION_DEFAULT_PATH,
|
||||||
MICROSOFT_TEAMS_NODE_TYPE,
|
MICROSOFT_TEAMS_NODE_TYPE,
|
||||||
|
@ -26,15 +26,6 @@ export class Telemetry {
|
||||||
return window.rudderanalytics;
|
return window.rudderanalytics;
|
||||||
}
|
}
|
||||||
|
|
||||||
private userNodesPanelSession: IUserNodesPanelSession = {
|
|
||||||
pushRef: '',
|
|
||||||
data: {
|
|
||||||
nodeFilter: '',
|
|
||||||
resultsNodes: [],
|
|
||||||
filterMode: 'Regular',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pageEventQueue = [];
|
this.pageEventQueue = [];
|
||||||
this.previousPath = '';
|
this.previousPath = '';
|
||||||
|
@ -200,78 +191,6 @@ export class Telemetry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackNodesPanel(event: string, properties: IDataObject = {}) {
|
|
||||||
if (this.rudderStack) {
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
switch (event) {
|
|
||||||
case 'nodeView.createNodeActiveChanged':
|
|
||||||
if (properties.createNodeActive !== false) {
|
|
||||||
this.resetNodesPanelSession();
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
this.track('User opened nodes panel', properties);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.destroyed':
|
|
||||||
if (
|
|
||||||
this.userNodesPanelSession.data.nodeFilter.length > 0 &&
|
|
||||||
this.userNodesPanelSession.data.nodeFilter !== ''
|
|
||||||
) {
|
|
||||||
this.track('User entered nodes panel search term', this.generateNodesPanelEvent());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.nodeFilterChanged':
|
|
||||||
if (
|
|
||||||
(properties.newValue as string).length === 0 &&
|
|
||||||
this.userNodesPanelSession.data.nodeFilter.length > 0
|
|
||||||
) {
|
|
||||||
this.track('User entered nodes panel search term', this.generateNodesPanelEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(properties.newValue as string).length > ((properties.oldValue as string) || '').length
|
|
||||||
) {
|
|
||||||
this.userNodesPanelSession.data.nodeFilter = properties.newValue as string;
|
|
||||||
this.userNodesPanelSession.data.resultsNodes = (
|
|
||||||
(properties.filteredNodes || []) as INodeCreateElement[]
|
|
||||||
).map((node: INodeCreateElement) => node.key);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.onCategoryExpanded':
|
|
||||||
properties.is_subcategory = false;
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
this.track('User viewed node category', properties);
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.onViewActions':
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
this.track('User viewed node actions', properties);
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.onActionsCustmAPIClicked':
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
this.track('User clicked custom API from node actions', properties);
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.addAction':
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
this.track('User added action', properties);
|
|
||||||
break;
|
|
||||||
case 'nodeCreateList.onSubcategorySelected':
|
|
||||||
properties.category_name = properties.subcategory;
|
|
||||||
properties.is_subcategory = true;
|
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.pushRef;
|
|
||||||
delete properties.selected;
|
|
||||||
this.track('User viewed node category', properties);
|
|
||||||
break;
|
|
||||||
case 'nodeView.addNodeButton':
|
|
||||||
this.track('User added node to workflow canvas', properties, { withPostHog: true });
|
|
||||||
break;
|
|
||||||
case 'nodeView.addSticky':
|
|
||||||
this.track('User inserted workflow note', properties);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We currently do not support tracking directly from within node implementation
|
// We currently do not support tracking directly from within node implementation
|
||||||
// so we are using this method as centralized way to track node parameters changes
|
// so we are using this method as centralized way to track node parameters changes
|
||||||
trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) {
|
trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) {
|
||||||
|
@ -295,24 +214,6 @@ export class Telemetry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetNodesPanelSession() {
|
|
||||||
this.userNodesPanelSession.pushRef = `nodes_panel_session_${new Date().valueOf()}`;
|
|
||||||
this.userNodesPanelSession.data = {
|
|
||||||
nodeFilter: '',
|
|
||||||
resultsNodes: [],
|
|
||||||
filterMode: 'All',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateNodesPanelEvent() {
|
|
||||||
return {
|
|
||||||
search_string: this.userNodesPanelSession.data.nodeFilter,
|
|
||||||
results_count: this.userNodesPanelSession.data.resultsNodes.length,
|
|
||||||
filter_mode: this.userNodesPanelSession.data.filterMode,
|
|
||||||
nodes_panel_session_id: this.userNodesPanelSession.pushRef,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private initRudderStack(key: string, url: string, options: IDataObject) {
|
private initRudderStack(key: string, url: string, options: IDataObject) {
|
||||||
window.rudderanalytics = window.rudderanalytics || [];
|
window.rudderanalytics = window.rudderanalytics || [];
|
||||||
if (!this.rudderStack) {
|
if (!this.rudderStack) {
|
||||||
|
|
297
packages/editor-ui/src/stores/nodeCreator.store.test.ts
Normal file
297
packages/editor-ui/src/stores/nodeCreator.store.test.ts
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
import { useNodeCreatorStore } from './nodeCreator.store';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { CUSTOM_API_CALL_KEY, REGULAR_NODE_CREATOR_VIEW } from '@/constants';
|
||||||
|
import type { INodeCreateElement } from '@/Interface';
|
||||||
|
|
||||||
|
const workflow_id = 'workflow-id';
|
||||||
|
const category_name = 'category-name';
|
||||||
|
const source = 'source';
|
||||||
|
const mode = 'mode';
|
||||||
|
const now = 1717602004819;
|
||||||
|
const now1 = 1718602004819;
|
||||||
|
const node_type = 'node-type';
|
||||||
|
const node_version = 1;
|
||||||
|
const input_node_type = 'input-node-type';
|
||||||
|
const action = 'action';
|
||||||
|
const source_mode = 'source-mode';
|
||||||
|
const resource = 'resource';
|
||||||
|
const actions = ['action1'];
|
||||||
|
|
||||||
|
vi.mock('@/composables/useTelemetry', () => {
|
||||||
|
const track = vi.fn();
|
||||||
|
return {
|
||||||
|
useTelemetry: () => {
|
||||||
|
return {
|
||||||
|
track,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useNodeCreatorStore', () => {
|
||||||
|
let nodeCreatorStore: ReturnType<typeof useNodeCreatorStore>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
vi.setSystemTime(now);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks when node creator is opened', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User opened nodes panel',
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
source,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
workflow_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets session id every time node creator is opened', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User opened nodes panel',
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
source,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
workflow_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.setSystemTime(now1);
|
||||||
|
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User opened nodes panel',
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
source,
|
||||||
|
nodes_panel_session_id: getSessionId(now1),
|
||||||
|
workflow_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks event on category expanded', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onCategoryExpanded({ workflow_id, category_name });
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User viewed node category',
|
||||||
|
{
|
||||||
|
category_name,
|
||||||
|
is_subcategory: false,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
workflow_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks event when node is added to canvas', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onNodeAddedToCanvas({
|
||||||
|
node_type,
|
||||||
|
node_version,
|
||||||
|
is_auto_add: true,
|
||||||
|
workflow_id,
|
||||||
|
drag_and_drop: true,
|
||||||
|
input_node_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User added node to workflow canvas',
|
||||||
|
{
|
||||||
|
node_type,
|
||||||
|
node_version,
|
||||||
|
is_auto_add: true,
|
||||||
|
drag_and_drop: true,
|
||||||
|
input_node_type,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
workflow_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks event when action is added', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onAddActions({
|
||||||
|
node_type,
|
||||||
|
action,
|
||||||
|
source_mode,
|
||||||
|
resource,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User added action',
|
||||||
|
{
|
||||||
|
node_type,
|
||||||
|
action,
|
||||||
|
source_mode,
|
||||||
|
resource,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks when custom api action is clicked', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onActionsCustomAPIClicked({
|
||||||
|
app_identifier: node_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User clicked custom API from node actions',
|
||||||
|
{
|
||||||
|
app_identifier: node_type,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks when action is viewed', () => {
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onViewActions({
|
||||||
|
app_identifier: node_type,
|
||||||
|
actions,
|
||||||
|
regular_action_count: 1,
|
||||||
|
trigger_action_count: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User viewed node actions',
|
||||||
|
{
|
||||||
|
app_identifier: node_type,
|
||||||
|
actions,
|
||||||
|
regular_action_count: 1,
|
||||||
|
trigger_action_count: 2,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks when search filter is updated, ignoring custom actions in count', () => {
|
||||||
|
const newValue = 'new-value';
|
||||||
|
const subcategory = 'subcategory';
|
||||||
|
const title = 'title';
|
||||||
|
|
||||||
|
const mockTrigger = {
|
||||||
|
key: 'n8n-node.exampleTrigger',
|
||||||
|
properties: {
|
||||||
|
name: 'n8n-node.exampleTrigge',
|
||||||
|
displayName: 'Example Trigger',
|
||||||
|
},
|
||||||
|
} as INodeCreateElement;
|
||||||
|
|
||||||
|
const mockCustom = {
|
||||||
|
key: 'action',
|
||||||
|
properties: {
|
||||||
|
actionKey: CUSTOM_API_CALL_KEY,
|
||||||
|
},
|
||||||
|
} as INodeCreateElement;
|
||||||
|
|
||||||
|
const mockRegular = {
|
||||||
|
key: 'n8n-node.example',
|
||||||
|
properties: {},
|
||||||
|
} as INodeCreateElement;
|
||||||
|
|
||||||
|
nodeCreatorStore.onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
nodeCreatorStore.onNodeFilterChanged({
|
||||||
|
newValue,
|
||||||
|
filteredNodes: [mockCustom, mockRegular, mockTrigger],
|
||||||
|
filterMode: REGULAR_NODE_CREATOR_VIEW,
|
||||||
|
subcategory,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useTelemetry().track).toHaveBeenCalledWith(
|
||||||
|
'User entered nodes panel search term',
|
||||||
|
{
|
||||||
|
search_string: newValue,
|
||||||
|
filter_mode: 'regular',
|
||||||
|
category_name: subcategory,
|
||||||
|
results_count: 2,
|
||||||
|
trigger_count: 1,
|
||||||
|
regular_count: 1,
|
||||||
|
nodes_panel_session_id: getSessionId(now),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getSessionId(time: number) {
|
||||||
|
return `nodes_panel_session_${time}`;
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import {
|
import {
|
||||||
AI_NODE_CREATOR_VIEW,
|
AI_NODE_CREATOR_VIEW,
|
||||||
|
AI_OTHERS_NODE_CREATOR_VIEW,
|
||||||
|
CUSTOM_API_CALL_KEY,
|
||||||
NODE_CREATOR_OPEN_SOURCES,
|
NODE_CREATOR_OPEN_SOURCES,
|
||||||
REGULAR_NODE_CREATOR_VIEW,
|
REGULAR_NODE_CREATOR_VIEW,
|
||||||
STORES,
|
STORES,
|
||||||
|
@ -12,17 +14,17 @@ import type {
|
||||||
SimplifiedNodeType,
|
SimplifiedNodeType,
|
||||||
ActionsRecord,
|
ActionsRecord,
|
||||||
ToggleNodeCreatorOptions,
|
ToggleNodeCreatorOptions,
|
||||||
|
INodeCreateElement,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { transformNodeType } from '@/components/Node/NodeCreator/utils';
|
import { transformNodeType } from '@/components/Node/NodeCreator/utils';
|
||||||
import type { INodeInputConfiguration } from 'n8n-workflow';
|
import type { IDataObject, INodeInputConfiguration, NodeParameterValueType } from 'n8n-workflow';
|
||||||
import { NodeConnectionType, nodeConnectionTypes, NodeHelpers } from 'n8n-workflow';
|
import { NodeConnectionType, nodeConnectionTypes, NodeHelpers } from 'n8n-workflow';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
|
||||||
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import {
|
import {
|
||||||
|
@ -33,15 +35,15 @@ import type { Connection } from '@vue-flow/core';
|
||||||
import { CanvasConnectionMode } from '@/types';
|
import { CanvasConnectionMode } from '@/types';
|
||||||
import { isVueFlowConnection } from '@/utils/typeGuards';
|
import { isVueFlowConnection } from '@/utils/typeGuards';
|
||||||
import type { PartialBy } from '@/utils/typeHelpers';
|
import type { PartialBy } from '@/utils/typeHelpers';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
|
||||||
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
|
|
||||||
const externalHooks = useExternalHooks();
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
const externalHooks = useExternalHooks();
|
||||||
|
|
||||||
const selectedView = ref<NodeFilterType>(TRIGGER_NODE_CREATOR_VIEW);
|
const selectedView = ref<NodeFilterType>(TRIGGER_NODE_CREATOR_VIEW);
|
||||||
const mergedNodes = ref<SimplifiedNodeType[]>([]);
|
const mergedNodes = ref<SimplifiedNodeType[]>([]);
|
||||||
|
@ -50,6 +52,10 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
const showScrim = ref(false);
|
const showScrim = ref(false);
|
||||||
const openSource = ref<NodeCreatorOpenSource>('');
|
const openSource = ref<NodeCreatorOpenSource>('');
|
||||||
|
|
||||||
|
const isCreateNodeActive = ref<boolean>(false);
|
||||||
|
|
||||||
|
const nodePanelSessionId = ref<string>('');
|
||||||
|
|
||||||
const allNodeCreatorNodes = computed(() =>
|
const allNodeCreatorNodes = computed(() =>
|
||||||
Object.values(mergedNodes.value).map((i) => transformNodeType(i)),
|
Object.values(mergedNodes.value).map((i) => transformNodeType(i)),
|
||||||
);
|
);
|
||||||
|
@ -115,7 +121,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
createNodeActive,
|
createNodeActive,
|
||||||
nodeCreatorView,
|
nodeCreatorView,
|
||||||
}: ToggleNodeCreatorOptions) {
|
}: ToggleNodeCreatorOptions) {
|
||||||
if (createNodeActive === uiStore.isCreateNodeActive) {
|
if (createNodeActive === isCreateNodeActive.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,54 +134,24 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
// Default to the trigger tab in node creator if there's no trigger node yet
|
// Default to the trigger tab in node creator if there's no trigger node yet
|
||||||
setSelectedView(nodeCreatorView);
|
setSelectedView(nodeCreatorView);
|
||||||
|
|
||||||
let mode;
|
isCreateNodeActive.value = createNodeActive;
|
||||||
switch (selectedView.value) {
|
|
||||||
case AI_NODE_CREATOR_VIEW:
|
|
||||||
mode = 'ai';
|
|
||||||
break;
|
|
||||||
case REGULAR_NODE_CREATOR_VIEW:
|
|
||||||
mode = 'regular';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mode = 'regular';
|
|
||||||
}
|
|
||||||
|
|
||||||
uiStore.isCreateNodeActive = createNodeActive;
|
|
||||||
if (createNodeActive && source) {
|
if (createNodeActive && source) {
|
||||||
setOpenSource(source);
|
setOpenSource(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
void externalHooks.run('nodeView.createNodeActiveChanged', {
|
void externalHooks.run('nodeView.createNodeActiveChanged', {
|
||||||
source,
|
source,
|
||||||
mode,
|
mode: getMode(nodeCreatorView),
|
||||||
createNodeActive,
|
createNodeActive,
|
||||||
});
|
});
|
||||||
|
|
||||||
trackNodesPanelActiveChanged({
|
if (createNodeActive) {
|
||||||
source,
|
onCreatorOpened({
|
||||||
mode,
|
source,
|
||||||
createNodeActive,
|
mode: getMode(nodeCreatorView),
|
||||||
workflowId: workflowsStore.workflowId,
|
workflow_id: workflowsStore.workflowId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackNodesPanelActiveChanged({
|
|
||||||
source,
|
|
||||||
mode,
|
|
||||||
createNodeActive,
|
|
||||||
workflowId,
|
|
||||||
}: {
|
|
||||||
source?: string;
|
|
||||||
mode?: string;
|
|
||||||
createNodeActive?: boolean;
|
|
||||||
workflowId?: string;
|
|
||||||
}) {
|
|
||||||
telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', {
|
|
||||||
source,
|
|
||||||
mode,
|
|
||||||
createNodeActive,
|
|
||||||
workflow_id: workflowId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openNodeCreatorForConnectingNode({
|
function openNodeCreatorForConnectingNode({
|
||||||
|
@ -264,7 +240,150 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetNodesPanelSession() {
|
||||||
|
nodePanelSessionId.value = `nodes_panel_session_${new Date().valueOf()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackNodeCreatorEvent(event: string, properties: IDataObject = {}, withPostHog = false) {
|
||||||
|
telemetry.track(
|
||||||
|
event,
|
||||||
|
{
|
||||||
|
...properties,
|
||||||
|
nodes_panel_session_id: nodePanelSessionId.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withPostHog,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreatorOpened({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
}: {
|
||||||
|
source?: string;
|
||||||
|
mode: string;
|
||||||
|
workflow_id?: string;
|
||||||
|
}) {
|
||||||
|
resetNodesPanelSession();
|
||||||
|
trackNodeCreatorEvent('User opened nodes panel', {
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
workflow_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNodeFilterChanged({
|
||||||
|
newValue,
|
||||||
|
filteredNodes,
|
||||||
|
filterMode,
|
||||||
|
subcategory,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
newValue: string;
|
||||||
|
filteredNodes: INodeCreateElement[];
|
||||||
|
filterMode: NodeFilterType;
|
||||||
|
subcategory?: string;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
|
if (!newValue.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { results_count, trigger_count, regular_count } = filteredNodes.reduce(
|
||||||
|
(accu, node) => {
|
||||||
|
if (!('properties' in node)) {
|
||||||
|
return accu;
|
||||||
|
}
|
||||||
|
const isCustomAction =
|
||||||
|
'actionKey' in node.properties && node.properties.actionKey === CUSTOM_API_CALL_KEY;
|
||||||
|
if (isCustomAction) {
|
||||||
|
return accu;
|
||||||
|
}
|
||||||
|
const isTrigger = node.key.includes('Trigger');
|
||||||
|
return {
|
||||||
|
results_count: accu.results_count + 1,
|
||||||
|
trigger_count: accu.trigger_count + (isTrigger ? 1 : 0),
|
||||||
|
regular_count: accu.regular_count + (isTrigger ? 0 : 1),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
results_count: 0,
|
||||||
|
trigger_count: 0,
|
||||||
|
regular_count: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
trackNodeCreatorEvent('User entered nodes panel search term', {
|
||||||
|
search_string: newValue,
|
||||||
|
filter_mode: getMode(filterMode),
|
||||||
|
category_name: subcategory,
|
||||||
|
results_count,
|
||||||
|
trigger_count,
|
||||||
|
regular_count,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCategoryExpanded(properties: { category_name: string; workflow_id: string }) {
|
||||||
|
trackNodeCreatorEvent('User viewed node category', { ...properties, is_subcategory: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onViewActions(properties: {
|
||||||
|
app_identifier: string;
|
||||||
|
actions: string[];
|
||||||
|
regular_action_count: number;
|
||||||
|
trigger_action_count: number;
|
||||||
|
}) {
|
||||||
|
trackNodeCreatorEvent('User viewed node actions', properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onActionsCustomAPIClicked(properties: { app_identifier: string }) {
|
||||||
|
trackNodeCreatorEvent('User clicked custom API from node actions', properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAddActions(properties: {
|
||||||
|
node_type?: string;
|
||||||
|
action: string;
|
||||||
|
source_mode: string;
|
||||||
|
resource: NodeParameterValueType;
|
||||||
|
}) {
|
||||||
|
trackNodeCreatorEvent('User added action', properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSubcategorySelected(properties: { subcategory: string }) {
|
||||||
|
trackNodeCreatorEvent('User viewed node category', {
|
||||||
|
category_name: properties.subcategory,
|
||||||
|
is_subcategory: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNodeAddedToCanvas(properties: {
|
||||||
|
node_type: string;
|
||||||
|
node_version: number;
|
||||||
|
is_auto_add?: boolean;
|
||||||
|
workflow_id: string;
|
||||||
|
drag_and_drop?: boolean;
|
||||||
|
input_node_type?: string;
|
||||||
|
}) {
|
||||||
|
trackNodeCreatorEvent('User added node to workflow canvas', properties, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMode(mode: NodeFilterType): string {
|
||||||
|
if (mode === AI_NODE_CREATOR_VIEW || mode === AI_OTHERS_NODE_CREATOR_VIEW) {
|
||||||
|
return 'ai';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === TRIGGER_NODE_CREATOR_VIEW) {
|
||||||
|
return 'trigger';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'regular';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isCreateNodeActive,
|
||||||
openSource,
|
openSource,
|
||||||
selectedView,
|
selectedView,
|
||||||
showScrim,
|
showScrim,
|
||||||
|
@ -280,5 +399,13 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
openNodeCreatorForConnectingNode,
|
openNodeCreatorForConnectingNode,
|
||||||
openNodeCreatorForTriggerNodes,
|
openNodeCreatorForTriggerNodes,
|
||||||
allNodeCreatorNodes,
|
allNodeCreatorNodes,
|
||||||
|
onCreatorOpened,
|
||||||
|
onNodeFilterChanged,
|
||||||
|
onCategoryExpanded,
|
||||||
|
onActionsCustomAPIClicked,
|
||||||
|
onViewActions,
|
||||||
|
onAddActions,
|
||||||
|
onSubcategorySelected,
|
||||||
|
onNodeAddedToCanvas,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -192,7 +192,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
const bannersHeight = ref<number>(0);
|
const bannersHeight = ref<number>(0);
|
||||||
const bannerStack = ref<BannerName[]>([]);
|
const bannerStack = ref<BannerName[]>([]);
|
||||||
const pendingNotificationsForViews = ref<{ [key in VIEWS]?: NotificationOptions[] }>({});
|
const pendingNotificationsForViews = ref<{ [key in VIEWS]?: NotificationOptions[] }>({});
|
||||||
const isCreateNodeActive = ref<boolean>(false);
|
|
||||||
|
|
||||||
const appGridWidth = ref<number>(0);
|
const appGridWidth = ref<number>(0);
|
||||||
|
|
||||||
|
@ -659,7 +658,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
nodeViewMoveInProgress,
|
nodeViewMoveInProgress,
|
||||||
nodeViewInitialized,
|
nodeViewInitialized,
|
||||||
addFirstStepOnLoad,
|
addFirstStepOnLoad,
|
||||||
isCreateNodeActive,
|
|
||||||
sidebarMenuCollapsed,
|
sidebarMenuCollapsed,
|
||||||
fakeDoorFeatures,
|
fakeDoorFeatures,
|
||||||
bannerStack,
|
bannerStack,
|
||||||
|
|
|
@ -1450,7 +1450,7 @@ function selectNodes(ids: string[]) {
|
||||||
|
|
||||||
function onClickPane(position: CanvasNode['position']) {
|
function onClickPane(position: CanvasNode['position']) {
|
||||||
lastClickPosition.value = [position.x, position.y];
|
lastClickPosition.value = [position.x, position.y];
|
||||||
uiStore.isCreateNodeActive = false;
|
nodeCreatorStore.isCreateNodeActive = false;
|
||||||
setNodeSelected();
|
setNodeSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1643,7 +1643,7 @@ onBeforeUnmount(() => {
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<LazyNodeCreation
|
<LazyNodeCreation
|
||||||
v-if="!isCanvasReadOnly"
|
v-if="!isCanvasReadOnly"
|
||||||
:create-node-active="uiStore.isCreateNodeActive"
|
:create-node-active="nodeCreatorStore.isCreateNodeActive"
|
||||||
:node-view-scale="viewportTransform.zoom"
|
:node-view-scale="viewportTransform.zoom"
|
||||||
@toggle-node-creator="onToggleNodeCreator"
|
@toggle-node-creator="onToggleNodeCreator"
|
||||||
@add-nodes="onAddNodesAndConnections"
|
@add-nodes="onAddNodesAndConnections"
|
||||||
|
|
|
@ -71,7 +71,6 @@ import type {
|
||||||
INodeInputConfiguration,
|
INodeInputConfiguration,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
ITelemetryTrackProperties,
|
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
Workflow,
|
Workflow,
|
||||||
INodeOutputConfiguration,
|
INodeOutputConfiguration,
|
||||||
|
@ -2398,24 +2397,19 @@ export default defineComponent({
|
||||||
this.uiStore.stateIsDirty = true;
|
this.uiStore.stateIsDirty = true;
|
||||||
|
|
||||||
if (nodeTypeName === STICKY_NODE_TYPE) {
|
if (nodeTypeName === STICKY_NODE_TYPE) {
|
||||||
this.$telemetry.trackNodesPanel('nodeView.addSticky', {
|
this.$telemetry.track('User inserted workflow note', {
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
void this.externalHooks.run('nodeView.addNodeButton', { nodeTypeName });
|
void this.externalHooks.run('nodeView.addNodeButton', { nodeTypeName });
|
||||||
const trackProperties: ITelemetryTrackProperties = {
|
this.nodeCreatorStore.onNodeAddedToCanvas({
|
||||||
node_type: nodeTypeName,
|
node_type: nodeTypeName,
|
||||||
node_version: newNodeData.typeVersion,
|
node_version: newNodeData.typeVersion,
|
||||||
is_auto_add: isAutoAdd,
|
is_auto_add: isAutoAdd,
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
drag_and_drop: options.dragAndDrop,
|
drag_and_drop: options.dragAndDrop,
|
||||||
};
|
input_node_type: lastSelectedNode ? lastSelectedNode.type : undefined,
|
||||||
|
});
|
||||||
if (lastSelectedNode) {
|
|
||||||
trackProperties.input_node_type = lastSelectedNode.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$telemetry.trackNodesPanel('nodeView.addNodeButton', trackProperties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically deselect all nodes and select the current one and also active
|
// Automatically deselect all nodes and select the current one and also active
|
||||||
|
@ -4242,12 +4236,13 @@ export default defineComponent({
|
||||||
mode,
|
mode,
|
||||||
createNodeActive,
|
createNodeActive,
|
||||||
});
|
});
|
||||||
this.$telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', {
|
if (createNodeActive) {
|
||||||
source,
|
this.nodeCreatorStore.onCreatorOpened({
|
||||||
mode,
|
source,
|
||||||
createNodeActive,
|
mode,
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async onAddNodes(
|
async onAddNodes(
|
||||||
{ nodes, connections }: AddedNodesAndConnections,
|
{ nodes, connections }: AddedNodesAndConnections,
|
||||||
|
|
Loading…
Reference in a new issue