refactor: Add node IDs (#3788)
* update type * add id to new nodes * update paste/import behavior * update duplicate/copy * update duplicate workflow * update import functions + templates * add instance id on copy * on download add instance id * simplify for testing * update telemetry events * add ids to nodegraph * not if same instance * update spacing * fix tests * update tests * add uuid * fix tests update tests add uuid fix ts issue * fix telemetry event * update workflow import * update public api * add sqlit migration * on workflow update * add psql migration * add mysql migration * revert to title * fix telemetry bug * remove console log * remove migration logs * fix copy/paste bug * replace node index with node id * remove console log * address PR feedback * address comment * fix type issue * fix select * update schema * fix ts issue * update tel helpers * fix eslint issues
This commit is contained in:
@@ -117,7 +117,7 @@ export default mixins(showMessage, workflowHelpers).extend({
|
||||
|
||||
this.$data.isSaving = true;
|
||||
|
||||
const saved = await this.saveAsNewWorkflow({name, tags: this.currentTagIds, resetWebhookUrls: true, openInNewWindow: true});
|
||||
const saved = await this.saveAsNewWorkflow({name, tags: this.currentTagIds, resetWebhookUrls: true, openInNewWindow: true, resetNodeIds: true});
|
||||
|
||||
if (saved) {
|
||||
this.closeDialog();
|
||||
|
||||
@@ -183,7 +183,7 @@ import {
|
||||
IExecutionResponse,
|
||||
IWorkflowDataUpdate,
|
||||
IMenuItem,
|
||||
IUser,
|
||||
IWorkflowToShare,
|
||||
} from '../Interface';
|
||||
|
||||
import ExecutionsList from '@/components/ExecutionsList.vue';
|
||||
@@ -442,7 +442,6 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
this.$telemetry.track('User imported workflow', { source: 'file', workflow_id: this.$store.getters.workflowId });
|
||||
this.$root.$emit('importWorkflowData', { data: worflowData });
|
||||
};
|
||||
|
||||
@@ -513,8 +512,11 @@ export default mixins(
|
||||
data.id = parseInt(data.id, 10);
|
||||
}
|
||||
|
||||
const exportData: IWorkflowDataUpdate = {
|
||||
const exportData: IWorkflowToShare = {
|
||||
...data,
|
||||
meta: {
|
||||
instanceId: this.$store.getters.instanceId,
|
||||
},
|
||||
tags: (tags || []).map(tagId => {
|
||||
const {usageCount, ...tag} = this.$store.getters["tags/getTagById"](tagId);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="node-wrapper" :style="nodePosition">
|
||||
<div class="node-wrapper" :style="nodePosition" :id="nodeId">
|
||||
<div class="select-background" v-show="isSelected"></div>
|
||||
<div :class="{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name="data.name" :ref="data.name">
|
||||
<div :class="nodeClass" :style="nodeStyle" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="sticky-wrapper" :style="stickyPosition">
|
||||
<div class="sticky-wrapper" :style="stickyPosition" :id="nodeId">
|
||||
<div
|
||||
:class="{'sticky-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}"
|
||||
:style="stickySize"
|
||||
@@ -18,7 +18,7 @@
|
||||
:height="node.parameters.height"
|
||||
:width="node.parameters.width"
|
||||
:scale="nodeViewScale"
|
||||
:id="nodeIndex"
|
||||
:id="node.id"
|
||||
:readOnly="isReadOnly"
|
||||
:defaultText="defaultText"
|
||||
:editMode="isActive && !isReadOnly"
|
||||
@@ -165,9 +165,9 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||
if (!this.isSelected && this.node) {
|
||||
this.$emit('nodeSelected', this.node.name, false, true);
|
||||
}
|
||||
const nodeIndex = this.$store.getters.getNodeIndex(this.data.name);
|
||||
const nodeIdName = `node-${nodeIndex}`;
|
||||
this.instance.destroyDraggable(nodeIdName); // todo
|
||||
if (this.node) {
|
||||
this.instance.destroyDraggable(this.node.id); // todo avoid destroying if possible
|
||||
}
|
||||
},
|
||||
onResize({height, width, dX, dY}: { width: number, height: number, dX: number, dY: number }) {
|
||||
if (!this.node) {
|
||||
|
||||
@@ -3,12 +3,10 @@ import { INodeUi, XYPosition } from '@/Interface';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { getMousePosition, getRelativePosition } from '@/views/canvasHelpers';
|
||||
|
||||
export const mouseSelect = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
@@ -171,18 +169,15 @@ export const mouseSelect = mixins(
|
||||
|
||||
this.updateSelectBox(e);
|
||||
},
|
||||
|
||||
nodeDeselected (node: INodeUi) {
|
||||
this.$store.commit('removeNodeFromSelection', node);
|
||||
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
|
||||
// @ts-ignore
|
||||
this.instance.removeFromDragSelection(nodeElement);
|
||||
this.instance.removeFromDragSelection(node.id);
|
||||
},
|
||||
nodeSelected (node: INodeUi) {
|
||||
this.$store.commit('addSelectedNode', node);
|
||||
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
|
||||
// @ts-ignore
|
||||
this.instance.addToDragSelection(nodeElement);
|
||||
this.instance.addToDragSelection(node.id);
|
||||
},
|
||||
deselectAllNodes () {
|
||||
// @ts-ignore
|
||||
|
||||
@@ -2,12 +2,10 @@ import mixins from 'vue-typed-mixins';
|
||||
// @ts-ignore
|
||||
import normalizeWheel from 'normalize-wheel';
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { getMousePosition } from '@/views/canvasHelpers';
|
||||
|
||||
export const moveNodeWorkflow = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
|
||||
@@ -3,8 +3,7 @@ import { IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { NODE_NAME_PREFIX, NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
import * as CanvasHelpers from '@/views/canvasHelpers';
|
||||
import { Endpoint } from 'jsplumb';
|
||||
|
||||
@@ -15,7 +14,6 @@ import { getStyleTokenValue } from '../helpers';
|
||||
|
||||
export const nodeBase = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
mounted () {
|
||||
// Initialize the node
|
||||
@@ -28,10 +26,7 @@ export const nodeBase = mixins(
|
||||
return this.$store.getters.getNodeByName(this.name);
|
||||
},
|
||||
nodeId (): string {
|
||||
return NODE_NAME_PREFIX + this.nodeIndex;
|
||||
},
|
||||
nodeIndex (): string {
|
||||
return this.$store.getters.getNodeIndex(this.data.name).toString();
|
||||
return this.data.id;
|
||||
},
|
||||
},
|
||||
props: [
|
||||
@@ -62,7 +57,7 @@ export const nodeBase = mixins(
|
||||
const anchorPosition = CanvasHelpers.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
||||
|
||||
const newEndpointData: IEndpointOptions = {
|
||||
uuid: CanvasHelpers.getInputEndpointUUID(this.nodeIndex, index),
|
||||
uuid: CanvasHelpers.getInputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'Rectangle',
|
||||
@@ -71,7 +66,7 @@ export const nodeBase = mixins(
|
||||
isSource: false,
|
||||
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||
parameters: {
|
||||
nodeIndex: this.nodeIndex,
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
@@ -130,7 +125,7 @@ export const nodeBase = mixins(
|
||||
const anchorPosition = CanvasHelpers.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
||||
|
||||
const newEndpointData: IEndpointOptions = {
|
||||
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
|
||||
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'Dot',
|
||||
@@ -140,7 +135,7 @@ export const nodeBase = mixins(
|
||||
isTarget: false,
|
||||
enabled: !this.isReadOnly,
|
||||
parameters: {
|
||||
nodeIndex: this.nodeIndex,
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
@@ -166,7 +161,7 @@ export const nodeBase = mixins(
|
||||
|
||||
if (!this.isReadOnly) {
|
||||
const plusEndpointData: IEndpointOptions = {
|
||||
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
|
||||
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'N8nPlus',
|
||||
@@ -187,7 +182,7 @@ export const nodeBase = mixins(
|
||||
hover: true, // hack to distinguish hover state
|
||||
},
|
||||
parameters: {
|
||||
nodeIndex: this.nodeIndex,
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
@@ -258,8 +253,7 @@ export const nodeBase = mixins(
|
||||
// create a proper solution
|
||||
let newNodePositon: XYPosition;
|
||||
moveNodes.forEach((node: INodeUi) => {
|
||||
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
|
||||
const element = document.getElementById(nodeElement);
|
||||
const element = document.getElementById(node.id);
|
||||
if (element === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export const nodeIndex = Vue.extend({
|
||||
methods: {
|
||||
getNodeIndex (nodeName: string): string {
|
||||
let uniqueId = this.$store.getters.getNodeIndex(nodeName);
|
||||
|
||||
if (uniqueId === -1) {
|
||||
this.$store.commit('addToNodeIndex', nodeName);
|
||||
uniqueId = this.$store.getters.getNodeIndex(nodeName);
|
||||
}
|
||||
|
||||
// We return as string as draggable and jsplumb seems to make problems
|
||||
// when numbers are given
|
||||
return uniqueId.toString();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -53,7 +53,7 @@ import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const workflowHelpers = mixins(
|
||||
externalHooks,
|
||||
@@ -666,7 +666,7 @@ export const workflowHelpers = mixins(
|
||||
}
|
||||
},
|
||||
|
||||
async saveAsNewWorkflow ({name, tags, resetWebhookUrls, openInNewWindow}: {name?: string, tags?: string[], resetWebhookUrls?: boolean, openInNewWindow?: boolean} = {}, redirect = true): Promise<boolean> {
|
||||
async saveAsNewWorkflow ({name, tags, resetWebhookUrls, resetNodeIds, openInNewWindow}: {name?: string, tags?: string[], resetWebhookUrls?: boolean, openInNewWindow?: boolean, resetNodeIds?: boolean} = {}, redirect = true): Promise<boolean> {
|
||||
try {
|
||||
this.$store.commit('addActiveAction', 'workflowSaving');
|
||||
|
||||
@@ -674,10 +674,19 @@ export const workflowHelpers = mixins(
|
||||
// make sure that the new ones are not active
|
||||
workflowDataRequest.active = false;
|
||||
const changedNodes = {} as IDataObject;
|
||||
|
||||
if (resetNodeIds) {
|
||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map(node => {
|
||||
node.id = uuid();
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
if (resetWebhookUrls) {
|
||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map(node => {
|
||||
if (node.webhookId) {
|
||||
node.webhookId = uuidv4();
|
||||
node.webhookId = uuid();
|
||||
changedNodes[node.name] = node.webhookId;
|
||||
}
|
||||
return node;
|
||||
|
||||
Reference in New Issue
Block a user