feat(editor): Add capability to open NDV and rename node (no-changelog) (#9712)

This commit is contained in:
Alex Grozav
2024-06-17 15:46:55 +03:00
committed by GitHub
parent 87cb199745
commit 12604fe1da
8 changed files with 568 additions and 97 deletions

View File

@@ -12,26 +12,20 @@ import type {
AddedNodesAndConnections,
INodeUi,
ITag,
IUpdateInformation,
IWorkflowDataUpdate,
ToggleNodeCreatorOptions,
XYPosition,
} from '@/Interface';
import useWorkflowsEEStore from '@/stores/workflows.ee.store';
import { useTagsStore } from '@/stores/tags.store';
import type { Connection } from '@vue-flow/core';
import type { CanvasElement } from '@/types';
import {
EnterpriseEditionFeature,
AI_NODE_CREATOR_VIEW,
REGULAR_NODE_CREATOR_VIEW,
TRIGGER_NODE_CREATOR_VIEW,
VIEWS,
} from '@/constants';
import { EnterpriseEditionFeature, VIEWS } from '@/constants';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import { useTelemetry } from '@/composables/useTelemetry';
import { useExternalHooks } from '@/composables/useExternalHooks';
import * as NodeViewUtils from '@/utils/nodeViewUtils';
import type { IConnection, INodeTypeDescription } from 'n8n-workflow';
import type { ExecutionSummary, IConnection, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { useToast } from '@/composables/useToast';
import { v4 as uuid } from 'uuid';
@@ -44,24 +38,27 @@ import { useCollaborationStore } from '@/stores/collaboration.store';
import { getUniqueNodeName } from '@/utils/canvasUtilsV2';
import { historyBus } from '@/models/history';
import { useCanvasOperations } from '@/composables/useCanvasOperations';
import { useExecutionsStore } from '@/stores/executions.store';
const NodeCreation = defineAsyncComponent(
async () => await import('@/components/Node/NodeCreation.vue'),
);
const NodeDetailsView = defineAsyncComponent(
async () => await import('@/components/NodeDetailsView.vue'),
);
const $style = useCssModule();
const router = useRouter();
const route = useRoute();
const i18n = useI18n();
const telemetry = useTelemetry();
const externalHooks = useExternalHooks();
const toast = useToast();
const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const workflowsEEStore = useWorkflowsEEStore();
const tagsStore = useTagsStore();
const sourceControlStore = useSourceControlStore();
const nodeCreatorStore = useNodeCreatorStore();
@@ -71,20 +68,31 @@ const environmentsStore = useEnvironmentsStore();
const externalSecretsStore = useExternalSecretsStore();
const rootStore = useRootStore();
const collaborationStore = useCollaborationStore();
const executionsStore = useExecutionsStore();
const { runWorkflow } = useRunWorkflow({ router });
const {
updateNodePosition,
renameNode,
revertRenameNode,
setNodeActive,
deleteNode,
revertDeleteNode,
createConnection,
deleteConnection,
revertDeleteConnection,
setNodeActiveByName,
} = useCanvasOperations();
const isLoading = ref(true);
const readOnlyNotification = ref<null | { visible: boolean }>(null);
const isProductionExecutionPreview = ref(false);
const isExecutionPreview = ref(false);
const canOpenNDV = ref(true);
const hideNodeIssues = ref(false);
const workflowId = computed<string>(() => route.params.workflowId as string);
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
@@ -151,6 +159,7 @@ async function initialize() {
initializeEditableWorkflow(workflowId.value);
addUndoRedoEventBindings();
addPostMessageEventBindings();
if (window.parent) {
window.parent.postMessage(
@@ -163,6 +172,7 @@ async function initialize() {
}
onBeforeUnmount(() => {
removePostMessageEventBindings();
removeUndoRedoEventBindings();
});
@@ -172,7 +182,7 @@ function addUndoRedoEventBindings() {
historyBus.on('revertRemoveNode', onRevertDeleteNode);
// historyBus.on('revertAddConnection', onRevertAddConnection);
historyBus.on('revertRemoveConnection', onRevertDeleteConnection);
// historyBus.on('revertRenameNode', onRevertNameChange);
historyBus.on('revertRenameNode', onRevertRenameNode);
// historyBus.on('enableNodeToggle', onRevertEnableToggle);
}
@@ -182,10 +192,18 @@ function removeUndoRedoEventBindings() {
historyBus.off('revertRemoveNode', onRevertDeleteNode);
// historyBus.off('revertAddConnection', onRevertAddConnection);
historyBus.off('revertRemoveConnection', onRevertDeleteConnection);
// historyBus.off('revertRenameNode', onRevertNameChange);
historyBus.off('revertRenameNode', onRevertRenameNode);
// historyBus.off('enableNodeToggle', onRevertEnableToggle);
}
function addPostMessageEventBindings() {
window.addEventListener('message', onPostMessageReceived);
}
function removePostMessageEventBindings() {
window.removeEventListener('message', onPostMessageReceived);
}
// @TODO Maybe move this to the store
function initializeEditableWorkflow(id: string) {
const targetWorkflow = workflowsStore.workflowsById[id];
@@ -245,11 +263,16 @@ function onRevertDeleteNode({ node }: { node: INodeUi }) {
revertDeleteNode(node);
}
function onSetNodeActive(id: string) {
setNodeActive(id);
}
/**
* Map new node connection format to the old one and add it to the store
*
* @param connection
*/
function onCreateConnection(connection: Connection) {
createConnection(connection);
}
@@ -262,53 +285,6 @@ function onRevertDeleteConnection({ connection }: { connection: [IConnection, IC
revertDeleteConnection(connection);
}
function onToggleNodeCreator({
source,
createNodeActive,
nodeCreatorView,
}: ToggleNodeCreatorOptions) {
if (createNodeActive === uiStore.isCreateNodeActive) {
return;
}
if (!nodeCreatorView) {
nodeCreatorView =
triggerNodes.value.length > 0 ? REGULAR_NODE_CREATOR_VIEW : TRIGGER_NODE_CREATOR_VIEW;
}
// Default to the trigger tab in node creator if there's no trigger node yet
nodeCreatorStore.setSelectedView(nodeCreatorView);
let mode;
switch (nodeCreatorStore.selectedView) {
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) {
nodeCreatorStore.setOpenSource(source);
}
void externalHooks.run('nodeView.createNodeActiveChanged', {
source,
mode,
createNodeActive,
});
telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', {
source,
mode,
createNodeActive,
workflow_id: workflowId.value,
});
}
async function onAddNodes(
{ nodes, connections }: AddedNodesAndConnections,
dragAndDrop = false,
@@ -355,23 +331,21 @@ async function onAddNodes(
});
}
// @TODO Implement this
// const lastAddedNode = editableWorkflow.value.nodes[editableWorkflow.value.nodes.length - 1];
// const workflow = editableWorkflowObject.value;
// const lastNodeInputs = workflow.getParentNodesByDepth(lastAddedNode.name, 1);
//
// // If the last added node has multiple inputs, move them down
// if (lastNodeInputs.length > 1) {
// lastNodeInputs.slice(1).forEach((node, index) => {
// const nodeUi = workflowsStore.getNodeByName(node.name);
// if (!nodeUi) return;
//
// // onMoveNode({
// // nodeName: nodeUi.name,
// // position: [nodeUi.position[0], nodeUi.position[1] + 100 * (index + 1)],
// // });
// });
// }
const lastAddedNode = editableWorkflow.value.nodes[editableWorkflow.value.nodes.length - 1];
const lastNodeInputs = editableWorkflowObject.value.getParentNodesByDepth(lastAddedNode.name, 1);
// If the last added node has multiple inputs, move them down
if (lastNodeInputs.length > 1) {
lastNodeInputs.slice(1).forEach((node, index) => {
const nodeUi = workflowsStore.getNodeByName(node.name);
if (!nodeUi) return;
updateNodePosition(nodeUi.id, {
x: nodeUi.position[0],
y: nodeUi.position[1] + 100 * (index + 1),
});
});
}
}
type AddNodeData = {
@@ -408,6 +382,7 @@ async function onNodeCreate(node: AddNodeData, _options: AddNodeOptions = {}): P
// @TODO Figure out why this is needed and if we can do better...
// this.matchCredentials(node);
// @TODO Connect added node to last selected node
// const lastSelectedNode = uiStore.getLastSelectedNode;
// const lastSelectedNodeOutputIndex = uiStore.lastSelectedNodeOutputIndex;
// const lastSelectedNodeEndpointUuid = uiStore.lastSelectedNodeEndpointUuid;
@@ -496,7 +471,7 @@ async function createNodeWithDefaultCredentials(node: Partial<INodeUi>) {
) as INodeTypeDescription;
let nodeVersion = nodeTypeDescription.defaultVersion;
if (nodeVersion === undefined) {
if (typeof nodeVersion === 'undefined') {
nodeVersion = Array.isArray(nodeTypeDescription.version)
? nodeTypeDescription.version.slice(-1)[0]
: nodeTypeDescription.version;
@@ -529,7 +504,7 @@ async function createNodeWithDefaultCredentials(node: Partial<INodeUi>) {
// );
// } catch (e) {
// console.error(
// this.$locale.baseText('nodeView.thereWasAProblemLoadingTheNodeParametersOfNode') +
// i18n.baseText('nodeView.thereWasAProblemLoadingTheNodeParametersOfNode') +
// `: "${node.name}"`,
// );
// console.error(e);
@@ -654,8 +629,8 @@ async function injectNode(
//
// if (nodeTypeData === null) {
// this.showMessage({
// title: this.$locale.baseText('nodeView.showMessage.addNodeButton.title'),
// message: this.$locale.baseText('nodeView.showMessage.addNodeButton.message', {
// title: i18n.baseText('nodeView.showMessage.addNodeButton.title'),
// message: i18n.baseText('nodeView.showMessage.addNodeButton.message', {
// interpolate: { nodeTypeName },
// }),
// type: 'error',
@@ -897,6 +872,106 @@ function checkIfEditingIsAllowed(): boolean {
return true;
}
async function onPostMessageReceived(message: MessageEvent) {
if (!message || typeof message.data !== 'string' || !message.data?.includes?.('"command"')) {
return;
}
try {
const json = JSON.parse(message.data);
if (json && json.command === 'openWorkflow') {
try {
await importWorkflowExact(json.data);
canOpenNDV.value = json.canOpenNDV ?? true;
hideNodeIssues.value = json.hideNodeIssues ?? false;
isExecutionPreview.value = false;
} catch (e) {
if (window.top) {
window.top.postMessage(
JSON.stringify({
command: 'error',
message: i18n.baseText('openWorkflow.workflowImportError'),
}),
'*',
);
}
toast.showMessage({
title: i18n.baseText('openWorkflow.workflowImportError'),
message: (e as Error).message,
type: 'error',
});
}
} else if (json && json.command === 'openExecution') {
try {
// If this NodeView is used in preview mode (in iframe) it will not have access to the main app store
// so everything it needs has to be sent using post messages and passed down to child components
isProductionExecutionPreview.value = json.executionMode !== 'manual';
await openExecution(json.executionId);
canOpenNDV.value = json.canOpenNDV ?? true;
hideNodeIssues.value = json.hideNodeIssues ?? false;
isExecutionPreview.value = true;
} catch (e) {
if (window.top) {
window.top.postMessage(
JSON.stringify({
command: 'error',
message: i18n.baseText('nodeView.showError.openExecution.title'),
}),
'*',
);
}
toast.showMessage({
title: i18n.baseText('nodeView.showError.openExecution.title'),
message: (e as Error).message,
type: 'error',
});
}
} else if (json?.command === 'setActiveExecution') {
executionsStore.activeExecution = (await executionsStore.fetchExecution(
json.executionId,
)) as ExecutionSummary;
}
} catch (e) {}
}
async function onSwitchSelectedNode(nodeName: string) {
setNodeActiveByName(nodeName);
}
async function onOpenConnectionNodeCreator(node: string, connectionType: NodeConnectionType) {
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType });
}
function onToggleNodeCreator(options: ToggleNodeCreatorOptions) {
nodeCreatorStore.openNodeCreator(options);
}
async function openExecution(_executionId: string) {
// @TODO
}
async function importWorkflowExact(_workflow: IWorkflowDataUpdate) {
// @TODO
}
async function onRevertRenameNode({
currentName,
newName,
}: {
currentName: string;
newName: string;
}) {
await revertRenameNode(currentName, newName);
}
function onUpdateNodeValue(parameterData: IUpdateInformation) {
if (parameterData.name === 'name' && parameterData.oldValue) {
// The name changed so we have to take care that
// the connections get changed.
void renameNode(parameterData.oldValue as string, parameterData.value as string);
}
}
</script>
<template>
@@ -905,6 +980,7 @@ function checkIfEditingIsAllowed(): boolean {
:workflow="editableWorkflow"
:workflow-object="editableWorkflowObject"
@update:node:position="onUpdateNodePosition"
@update:node:active="onSetNodeActive"
@delete:node="onDeleteNode"
@create:connection="onCreateConnection"
@delete:connection="onDeleteConnection"
@@ -921,6 +997,20 @@ function checkIfEditingIsAllowed(): boolean {
@add-nodes="onAddNodes"
/>
</Suspense>
<Suspense>
<NodeDetailsView
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
:is-production-execution-preview="isProductionExecutionPreview"
@value-changed="onUpdateNodeValue"
@switch-selected-node="onSwitchSelectedNode"
@open-connection-node-creator="onOpenConnectionNodeCreator"
/>
<!--
:renaming="renamingActive"
@stop-execution="stopExecution"
@save-keyboard-shortcut="onSaveKeyboardShortcut"
-->
</Suspense>
</WorkflowCanvas>
</template>