mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): Node creator actions (#4696)
* WIP: Node Actions List UI * WIP: Recommended Actions and preseting of fields * WIP: Resource category * 🎨 Moved actions categorisation to the server * 🏷️ Add missing INodeAction type * ✨ Improve SSR categorisation, fix adding of mixed actions * ♻️ Refactor CategorizedItems to composition api, style fixes * WIP: Adding multiple nodes * ♻️ Refactor rest of the NodeCreator component to composition API, conver globalLinkActions to composable * ✨ Allow actions dragging, fix search and refactor passing of actions to categorized items * 💄 Fix node actions title * Migrate to the pinia store, add posthog feature and various fixes * 🐛 Fix filtering of trigger actions when not merged * fix: N8N-5439 — Do not use simple node item when at NodeHelperPanel root * 🐛 Design review fixes * 🐛 Fix disabling of merged actions * Fix trigger root filtering * ✨ Allow for custom node actions parser, introduce hubspot parser * 🐛 Fix initial node params validation, fix position of second added node * 🐛 Introduce operations category, removed canvas node names overrride, fix API actions display and prevent dragging of action nodes * ✨ Prevent NDV auto-open feature flag * 🐛 Inject recommened action for trigger nodes without actions * Refactored NodeCreatorNode to Storybook, change filtering of merged nodes for the trigger helper panel, minor fixes * Improve rendering of app nodes and animation * Cleanup, any only enable accordion transition on triggerhelperpanel * Hide node creator scrollbars in Firefox * Minor styles fixes * Do not copy the array in rendering method * Removed unused props * Fix memory leak * Fix categorisation of regular nodes with a single resource * Implement telemetry calls for node actions * Move categorization to FE * Fix client side actions categorisation * Skip custom action show * Only load tooltip for NodeIcon if necessary * Fix lodash startCase import * Remove lodash.startcase * Cleanup * Fix node creator autofocus on "tab" * Prevent posthog getFeatureFlag from crashing * Debugging preview env search issues * Remove logs * Make sure the pre-filled params are update not overwritten * Get rid of transition in itemiterator * WIP: Rough version of NodeActions keyboard navigation, replace nodeCreator composable with Pinia store module * Rewrite to add support for ActionItem to ItemIterator and make CategorizedItems accept items props * Fix category item counter & cleanup * Add APIHint to actions search no-result, clean up NodeCreatorNode * Improve node actions no results message * Remove logging, fix filtering of recommended placeholder category * Remove unused NodeActions component and node merging feature falg * Do not show regular nodes without actions * Make sure to add manual trigger when adding http node via actions hint * Fixed api hint footer line height * Prevent pointer-events od NodeIcon img and remove "this" from template * Address PR points * Fix e2e specs * Make sure canvas ia loaded * Make sure canvas ia loaded before opening nodeCreator in e2e spec * Fix flaky workflows tags e2e getter * Imrpove node creator click outside UX, add manual node to regular nodes added from trigger panel * Add manual trigger node if dragging regular from trigger panel
This commit is contained in:
@@ -160,7 +160,7 @@ import { genericHelpers } from '@/mixins/genericHelpers';
|
||||
import { mouseSelect } from '@/mixins/mouseSelect';
|
||||
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
||||
import { restApi } from '@/mixins/restApi';
|
||||
import { globalLinkActions } from '@/mixins/globalLinkActions';
|
||||
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
|
||||
import { showMessage } from '@/mixins/showMessage';
|
||||
import { titleChange } from '@/mixins/titleChange';
|
||||
import { newVersions } from '@/mixins/newVersions';
|
||||
@@ -256,7 +256,6 @@ export default mixins(
|
||||
workflowHelpers,
|
||||
workflowRun,
|
||||
newVersions,
|
||||
globalLinkActions,
|
||||
debounceHelper,
|
||||
)
|
||||
.extend({
|
||||
@@ -271,6 +270,14 @@ export default mixins(
|
||||
NodeCreation,
|
||||
CanvasControls,
|
||||
},
|
||||
setup() {
|
||||
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
|
||||
|
||||
return {
|
||||
registerCustomAction,
|
||||
unregisterCustomAction,
|
||||
};
|
||||
},
|
||||
errorCaptured: (err, vm, info) => {
|
||||
console.error('errorCaptured'); // eslint-disable-line no-console
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
@@ -326,7 +333,6 @@ export default mixins(
|
||||
},
|
||||
nodeViewScale(newScale) {
|
||||
const element = this.$refs.nodeView as HTMLDivElement;
|
||||
|
||||
if(element) {
|
||||
element.style.transform = `scale(${newScale})`;
|
||||
}
|
||||
@@ -397,8 +403,9 @@ export default mixins(
|
||||
useSettingsStore,
|
||||
useTemplatesStore,
|
||||
useUIStore,
|
||||
useUsersStore,
|
||||
useWorkflowsStore,
|
||||
useUsersStore,
|
||||
useNodeCreatorStore,
|
||||
useWorkflowsEEStore,
|
||||
),
|
||||
nativelyNumberSuffixedDefaults(): string[] {
|
||||
@@ -675,12 +682,10 @@ export default mixins(
|
||||
},
|
||||
showTriggerCreator(source: string) {
|
||||
if(this.createNodeActive) return;
|
||||
this.nodeCreatorStore.selectedType = TRIGGER_NODE_FILTER;
|
||||
this.nodeCreatorStore.showScrim = true;
|
||||
this.nodeCreatorStore.setSelectedType(TRIGGER_NODE_FILTER);
|
||||
this.nodeCreatorStore.setShowScrim(true);
|
||||
this.onToggleNodeCreator({ source, createNodeActive: true });
|
||||
this.$nextTick(() => {
|
||||
this.nodeCreatorStore.showTabs = false;
|
||||
});
|
||||
this.$nextTick(() => this.nodeCreatorStore.setShowTabs(false));
|
||||
},
|
||||
async openExecution(executionId: string) {
|
||||
this.startLoading();
|
||||
@@ -1486,17 +1491,25 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeTypeName = event.dataTransfer.getData('nodeTypeName');
|
||||
if (nodeTypeName) {
|
||||
const nodeTypeNames = event.dataTransfer.getData('nodeTypeName').split(',');
|
||||
|
||||
if (nodeTypeNames) {
|
||||
const mousePosition = this.getMousePositionWithinNodeView(event);
|
||||
|
||||
this.addNode(nodeTypeName, {
|
||||
position: [
|
||||
mousePosition[0] - NodeViewUtils.NODE_SIZE / 2,
|
||||
mousePosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||
],
|
||||
dragAndDrop: true,
|
||||
const nodesToAdd = nodeTypeNames.map((nodeTypeName: string, index: number) => {
|
||||
|
||||
return {
|
||||
nodeTypeName,
|
||||
position: [
|
||||
// If adding more than one node, offset the X position
|
||||
(mousePosition[0] - NodeViewUtils.NODE_SIZE / 2) + (NodeViewUtils.NODE_SIZE * (index * 2)),
|
||||
mousePosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||
] as XYPosition,
|
||||
dragAndDrop: true,
|
||||
};
|
||||
});
|
||||
|
||||
this.onAddNode(nodesToAdd, true);
|
||||
this.createNodeActive = false;
|
||||
}
|
||||
},
|
||||
@@ -1608,7 +1621,7 @@ export default mixins(
|
||||
return newNodeData;
|
||||
},
|
||||
|
||||
async injectNode (nodeTypeName: string, options: AddNodeOptions = {}) {
|
||||
async injectNode (nodeTypeName: string, options: AddNodeOptions = {}, showDetail = true) {
|
||||
const nodeTypeData: INodeTypeDescription | null = this.nodeTypesStore.getNodeType(nodeTypeName);
|
||||
|
||||
if (nodeTypeData === null) {
|
||||
@@ -1665,7 +1678,6 @@ export default mixins(
|
||||
}
|
||||
|
||||
// If a node is active then add the new node directly after the current one
|
||||
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
||||
newNodeData.position = NodeViewUtils.getNewNodePosition(
|
||||
this.nodes,
|
||||
[lastSelectedNode.position[0] + NodeViewUtils.PUSH_NODES_OFFSET, lastSelectedNode.position[1] + yOffset],
|
||||
@@ -1718,9 +1730,12 @@ export default mixins(
|
||||
// Automatically deselect all nodes and select the current one and also active
|
||||
// current node
|
||||
this.deselectAllNodes();
|
||||
setTimeout(() => {
|
||||
this.nodeSelectedByName(newNodeData.name, nodeTypeName !== STICKY_NODE_TYPE);
|
||||
});
|
||||
const preventDetailOpen = window.posthog?.getFeatureFlag && window.posthog?.getFeatureFlag('prevent-ndv-auto-open') === 'prevent';
|
||||
if(showDetail && !preventDetailOpen) {
|
||||
setTimeout(() => {
|
||||
this.nodeSelectedByName(newNodeData.name, nodeTypeName !== STICKY_NODE_TYPE);
|
||||
});
|
||||
}
|
||||
|
||||
return newNodeData;
|
||||
},
|
||||
@@ -1756,7 +1771,7 @@ export default mixins(
|
||||
|
||||
this.__addConnection(connectionData, true);
|
||||
},
|
||||
async addNode(nodeTypeName: string, options: AddNodeOptions = {}) {
|
||||
async addNode(nodeTypeName: string, options: AddNodeOptions = {}, showDetail = true) {
|
||||
if (!this.editAllowedCheck()) {
|
||||
return;
|
||||
}
|
||||
@@ -1765,7 +1780,7 @@ export default mixins(
|
||||
const lastSelectedNode = this.lastSelectedNode;
|
||||
const lastSelectedNodeOutputIndex = this.uiStore.lastSelectedNodeOutputIndex;
|
||||
|
||||
const newNodeData = await this.injectNode(nodeTypeName, options);
|
||||
const newNodeData = await this.injectNode(nodeTypeName, options, showDetail);
|
||||
if (!newNodeData) {
|
||||
return;
|
||||
}
|
||||
@@ -3234,16 +3249,39 @@ export default mixins(
|
||||
if (createNodeActive === this.createNodeActive) return;
|
||||
|
||||
// Default to the trigger tab in node creator if there's no trigger node yet
|
||||
if (!this.containsTrigger) {
|
||||
this.nodeCreatorStore.selectedType = TRIGGER_NODE_FILTER;
|
||||
}
|
||||
if (!this.containsTrigger) this.nodeCreatorStore.setSelectedType(TRIGGER_NODE_FILTER);
|
||||
|
||||
this.createNodeActive = createNodeActive;
|
||||
this.$externalHooks().run('nodeView.createNodeActiveChanged', { source, createNodeActive });
|
||||
this.$telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', { source, createNodeActive, workflow_id: this.workflowsStore.workflowId });
|
||||
|
||||
const mode = this.nodeCreatorStore.selectedType === TRIGGER_NODE_FILTER ? 'trigger' : 'default';
|
||||
this.$externalHooks().run('nodeView.createNodeActiveChanged', { source, mode, createNodeActive });
|
||||
this.$telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', { source, mode, createNodeActive, workflow_id: this.workflowsStore.workflowId });
|
||||
},
|
||||
onAddNode({ nodeTypeName, position }: { nodeTypeName: string; position?: [number, number] }) {
|
||||
this.addNode(nodeTypeName, { position });
|
||||
onAddNode(nodeTypes: Array<{ nodeTypeName: string; position: XYPosition }>, dragAndDrop: boolean) {
|
||||
nodeTypes.forEach(({ nodeTypeName, position }, index) => {
|
||||
this.addNode(nodeTypeName, { position, dragAndDrop }, nodeTypes.length === 1 || index > 0);
|
||||
if(index === 0) return;
|
||||
// If there's more than one node, we want to connect them
|
||||
// this has to be done in mutation subscriber to make sure both nodes already
|
||||
// exist
|
||||
const actionWatcher = this.workflowsStore.$onAction(({ name, after, args }) => {
|
||||
if(name === 'addNode' && args[0].type === nodeTypeName) {
|
||||
after(() => {
|
||||
const lastAddedNode = this.nodes[this.nodes.length - 1];
|
||||
const previouslyAddedNode = this.nodes[this.nodes.length - 2];
|
||||
|
||||
this.$nextTick(() => this.connectTwoNodes(previouslyAddedNode.name, 0, lastAddedNode.name, 0));
|
||||
|
||||
// Position the added node to the right side of the previsouly added one
|
||||
lastAddedNode.position = [
|
||||
previouslyAddedNode.position[0] + (NodeViewUtils.NODE_SIZE * 2),
|
||||
previouslyAddedNode.position[1],
|
||||
];
|
||||
actionWatcher();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
async saveCurrentWorkflowExternal(callback: () => void) {
|
||||
await this.saveCurrentWorkflow();
|
||||
|
||||
Reference in New Issue
Block a user