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:
OlegIvaniv
2022-12-09 10:56:36 +01:00
committed by GitHub
parent b7c1359090
commit 79fe57dad8
78 changed files with 2498 additions and 1515 deletions

View File

@@ -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();