From 79fe57dad8093b27651ce82164d6e7a0f08f9e43 Mon Sep 17 00:00:00 2001 From: OlegIvaniv Date: Fri, 9 Dec 2022 10:56:36 +0100 Subject: [PATCH] feat(editor): Node creator actions (#4696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP: Node Actions List UI * WIP: Recommended Actions and preseting of fields * WIP: Resource category * :art: Moved actions categorisation to the server * :label: Add missing INodeAction type * :sparkles: Improve SSR categorisation, fix adding of mixed actions * :recycle: Refactor CategorizedItems to composition api, style fixes * WIP: Adding multiple nodes * :recycle: Refactor rest of the NodeCreator component to composition API, conver globalLinkActions to composable * :sparkles: Allow actions dragging, fix search and refactor passing of actions to categorized items * :lipstick: Fix node actions title * Migrate to the pinia store, add posthog feature and various fixes * :bug: Fix filtering of trigger actions when not merged * fix: N8N-5439 — Do not use simple node item when at NodeHelperPanel root * :bug: Design review fixes * :bug: Fix disabling of merged actions * Fix trigger root filtering * :sparkles: Allow for custom node actions parser, introduce hubspot parser * :bug: Fix initial node params validation, fix position of second added node * :bug: Introduce operations category, removed canvas node names overrride, fix API actions display and prevent dragging of action nodes * :sparkles: Prevent NDV auto-open feature flag * :bug: 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 --- cypress/e2e/4-node-creator.cy.ts | 16 +- cypress/e2e/7-workflow-actions.cy.ts | 6 +- cypress/pages/features/node-creator.ts | 3 +- cypress/pages/workflow.ts | 7 +- .../NodeCreatorNode.stories.ts | 58 ++ .../N8nNodeCreatorNode/NodeCreatorNode.vue | 125 +++ .../N8nNodeCreatorNode/TriggerIcon.vue | 65 ++ .../components/N8nNodeCreatorNode/index.ts | 3 + .../src/components/N8nNodeIcon/NodeIcon.vue | 32 +- .../src/plugins/n8nComponents.ts | 2 + packages/editor-ui/package.json | 1 + packages/editor-ui/src/App.vue | 10 +- packages/editor-ui/src/Interface.ts | 33 +- .../src/components/Node/NodeCreation.vue | 8 +- .../Node/NodeCreator/ActionItem.vue | 162 +++ .../Node/NodeCreator/CategorizedItems.vue | 960 ++++++++++-------- .../Node/NodeCreator/CategoryItem.vue | 84 +- .../Node/NodeCreator/CreatorItem.vue | 74 -- .../Node/NodeCreator/ItemIterator.vue | 276 +++-- .../components/Node/NodeCreator/MainPanel.vue | 109 +- .../components/Node/NodeCreator/NoResults.vue | 34 +- .../Node/NodeCreator/NodeCreator.vue | 173 ++-- .../components/Node/NodeCreator/NodeItem.vue | 359 +++---- .../components/Node/NodeCreator/SearchBar.vue | 96 +- .../Node/NodeCreator/SubcategoryItem.vue | 1 + .../Node/NodeCreator/TriggerHelperPanel.vue | 477 ++++++--- .../Node/NodeCreator/TypeSelector.vue | 38 +- .../editor-ui/src/components/NodeSettings.vue | 2 + .../editor-ui/src/components/TriggerIcon.vue | 42 - .../src/composables/useGlobalLinkActions.ts | 59 ++ packages/editor-ui/src/constants.ts | 4 + .../editor-ui/src/mixins/globalLinkActions.ts | 55 - .../src/plugins/i18n/locales/en.json | 10 +- .../editor-ui/src/plugins/telemetry/index.ts | 15 +- packages/editor-ui/src/shims.d.ts | 7 + packages/editor-ui/src/stores/nodeCreator.ts | 298 +++++- packages/editor-ui/src/stores/nodeTypes.ts | 18 +- packages/editor-ui/src/stores/workflows.ts | 20 +- .../editor-ui/src/utils/nodeTypesUtils.ts | 36 +- .../editor-ui/src/views/CanvasAddButton.vue | 47 +- packages/editor-ui/src/views/NodeView.vue | 100 +- .../nodes/Affinity/AffinityTrigger.node.ts | 2 +- .../nodes/AgileCrm/AgileCrm.node.ts | 2 +- .../nodes/Asana/AsanaTrigger.node.ts | 2 +- .../nodes/Aws/AwsSnsTrigger.node.ts | 2 +- .../nodes/Cisco/Webex/CiscoWebex.node.ts | 2 +- .../Cisco/Webex/CiscoWebexTrigger.node.ts | 2 +- .../nodes/CustomerIo/CustomerIo.node.ts | 2 +- .../EmailReadImap/v1/EmailReadImapV1.node.ts | 2 +- .../EmailReadImap/v2/EmailReadImapV2.node.ts | 2 +- .../ExecuteWorkflowTrigger.node.ts | 2 +- .../nodes/FunctionItem/FunctionItem.node.ts | 2 +- .../nodes-base/nodes/Gitlab/Gitlab.node.ts | 2 +- .../nodes/Gitlab/GitlabTrigger.node.ts | 2 +- .../nodes/Hubspot/HubspotTrigger.node.ts | 2 +- packages/nodes-base/nodes/Jira/Jira.node.ts | 2 +- .../nodes/KoBoToolbox/KoBoToolbox.node.ts | 1 - .../nodes/ManualTrigger/ManualTrigger.node.ts | 2 +- packages/nodes-base/nodes/Msg91/Msg91.node.ts | 2 +- .../N8nTrainingCustomerDatastore.node.ts | 2 +- .../N8nTrainingCustomerMessenger.node.ts | 2 +- packages/nodes-base/nodes/NoOp/NoOp.node.ts | 2 +- .../nodes/Notion/NotionTrigger.node.ts | 2 +- .../nodes/Notion/v1/VersionDescription.ts | 2 +- .../nodes/Notion/v2/VersionDescription.ts | 2 +- .../nodes/RssFeedRead/RssFeedRead.node.ts | 2 +- .../SendInBlue/SendInBlueTrigger.node.ts | 2 +- .../SplitInBatches/SplitInBatches.node.ts | 2 +- .../nodes/StickyNote/StickyNote.node.ts | 2 +- .../nodes/StopAndError/StopAndError.node.ts | 2 +- .../nodes/Toggl/TogglTrigger.node.ts | 2 +- .../VenafiTlsProtectDatacenter.node.ts | 4 +- .../VenafiTlsProtectCloud.node.ts | 4 +- .../VenafiTlsProtectCloudTrigger.node.ts | 2 +- .../nodes/WhatsApp/WhatsApp.node.ts | 2 +- .../nodes-base/nodes/Zoho/ZohoCrm.node.ts | 2 +- packages/workflow/src/Interfaces.ts | 7 + pnpm-lock.yaml | 8 + 78 files changed, 2498 insertions(+), 1515 deletions(-) create mode 100644 packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.stories.ts create mode 100644 packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue create mode 100644 packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue create mode 100644 packages/design-system/src/components/N8nNodeCreatorNode/index.ts create mode 100644 packages/editor-ui/src/components/Node/NodeCreator/ActionItem.vue delete mode 100644 packages/editor-ui/src/components/Node/NodeCreator/CreatorItem.vue delete mode 100644 packages/editor-ui/src/components/TriggerIcon.vue create mode 100644 packages/editor-ui/src/composables/useGlobalLinkActions.ts delete mode 100644 packages/editor-ui/src/mixins/globalLinkActions.ts diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts index e11dc9b3f2..f3efd655b2 100644 --- a/cypress/e2e/4-node-creator.cy.ts +++ b/cypress/e2e/4-node-creator.cy.ts @@ -70,11 +70,20 @@ describe('Node Creator', () => { .should('exist') .should('contain.text', 'We didn\'t make that... yet'); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image'); + nodeCreatorFeature.getters.creatorItem().should('have.length', 1); + + nodeCreatorFeature.getters.searchBar().find('input').clear().type('this node totally does not exist'); + nodeCreatorFeature.getters.creatorItem().should('have.length', 0); + + nodeCreatorFeature.getters.searchBar().find('input').clear() + nodeCreatorFeature.getters.getCreatorItem('On App Event').click(); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image'); nodeCreatorFeature.getters.creatorItem().should('have.length', 0); nodeCreatorFeature.getters.noResults() .should('exist') - .should('contain.text', 'To see results, click here'); + .should('contain.text', 'To see all results, click here'); nodeCreatorFeature.getters.noResults().contains('click here').click(); nodeCreatorFeature.getters.nodeCreatorTabs().should('exist'); @@ -85,6 +94,7 @@ describe('Node Creator', () => { }) it('should add manual trigger node', () => { + cy.get('.el-loading-mask').should('not.exist'); nodeCreatorFeature.getters.canvasAddButton().click(); nodeCreatorFeature.getters.getCreatorItem('Manually').click(); @@ -95,7 +105,7 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.nodeCreator().should('not.exist'); // TODO: Replace once we have canvas feature utils - cy.get('div').contains("On clicking 'execute'").should('exist'); + cy.get('div').contains("Add first step").should('exist'); }) it('check if non-core nodes are rendered', () => { cy.wait('@nodesIntercept').then((interception) => { @@ -144,7 +154,7 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCreatorItem(customCategory).should('exist'); nodeCreatorFeature.actions.toggleCategory(customCategory); - nodeCreatorFeature.getters.getCreatorItem(customNode).findChildByTestId('node-item-community-tooltip').should('exist'); + nodeCreatorFeature.getters.getCreatorItem(customNode).findChildByTestId('node-creator-item-tooltip').should('exist'); nodeCreatorFeature.getters.getCreatorItem(customNode).contains(customNodeDescription).should('exist'); nodeCreatorFeature.actions.selectNode(customNode); diff --git a/cypress/e2e/7-workflow-actions.cy.ts b/cypress/e2e/7-workflow-actions.cy.ts index 495614d569..b7d903b9ba 100644 --- a/cypress/e2e/7-workflow-actions.cy.ts +++ b/cypress/e2e/7-workflow-actions.cy.ts @@ -66,7 +66,7 @@ describe('Workflow Actions', () => { it('should add more tags', () => { WorkflowPage.getters.newTagLink().click(); WorkflowPage.actions.addTags(TEST_WF_TAGS); - WorkflowPage.getters.workflowTagElements().first().click(); + WorkflowPage.getters.firstWorkflowTagElement().click(); WorkflowPage.actions.addTags(['Another one']); WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length + 1); }); @@ -74,7 +74,7 @@ describe('Workflow Actions', () => { it('should remove tags by clicking X in tag', () => { WorkflowPage.getters.newTagLink().click(); WorkflowPage.actions.addTags(TEST_WF_TAGS); - WorkflowPage.getters.workflowTagElements().first().click(); + WorkflowPage.getters.firstWorkflowTagElement().click(); WorkflowPage.getters.workflowTagsContainer().find('.el-tag__close').first().click(); cy.get('body').type('{enter}'); WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length - 1); @@ -83,7 +83,7 @@ describe('Workflow Actions', () => { it('should remove tags from dropdown', () => { WorkflowPage.getters.newTagLink().click(); WorkflowPage.actions.addTags(TEST_WF_TAGS); - WorkflowPage.getters.workflowTagElements().first().click(); + WorkflowPage.getters.firstWorkflowTagElement().click(); WorkflowPage.getters.workflowTagsDropdown().find('li').first().click(); cy.get('body').type('{enter}'); WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length - 1); diff --git a/cypress/pages/features/node-creator.ts b/cypress/pages/features/node-creator.ts index 54dc9790ba..dad6f6ebe5 100644 --- a/cypress/pages/features/node-creator.ts +++ b/cypress/pages/features/node-creator.ts @@ -16,12 +16,13 @@ export class NodeCreator extends BasePage { creatorItem: () => cy.getByTestId('item-iterator-item'), communityNodeTooltip: () => cy.getByTestId('node-item-community-tooltip'), noResults: () => cy.getByTestId('categorized-no-results'), - nodeItemName: () => cy.getByTestId('node-item-name'), + nodeItemName: () => cy.getByTestId('node-creator-item-name'), activeSubcategory: () => cy.getByTestId('categorized-items-subcategory'), expandedCategories: () => this.getters.creatorItem().find('>div').filter('.active').invoke('text'), }; actions = { openNodeCreator: () => { + cy.get('.el-loading-mask').should('not.exist'); this.getters.plusButton().click(); this.getters.nodeCreator().should('be.visible') }, diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 77001f2fc0..3e0369ec7b 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -10,6 +10,7 @@ export class WorkflowPage extends BasePage { workflowTagsContainer: () => cy.getByTestId('workflow-tags-container'), workflowTagsInput: () => this.getters.workflowTagsContainer().then(($el) => cy.wrap($el.find('input').first())), workflowTagElements: () => cy.get('[data-test-id="workflow-tags-container"] span.tags > span'), + firstWorkflowTagElement: () => cy.get('[data-test-id="workflow-tags-container"] span.tags > span:nth-child(1)'), workflowTagsDropdown: () => cy.getByTestId('workflow-tags-dropdown'), newTagLink: () => cy.getByTestId('new-tag-link'), saveButton: () => cy.getByTestId('workflow-save-button'), @@ -43,12 +44,14 @@ export class WorkflowPage extends BasePage { addInitialNodeToCanvas: (nodeDisplayName: string) => { this.getters.canvasPlusButton().click(); this.getters.nodeCreatorSearchBar().type(nodeDisplayName); - this.getters.nodeCreatorSearchBar().type('{enter}{esc}'); + this.getters.nodeCreatorSearchBar().type('{enter}'); + cy.get('body').type('{esc}'); }, addNodeToCanvas: (nodeDisplayName: string) => { this.getters.nodeCreatorPlusButton().click(); this.getters.nodeCreatorSearchBar().type(nodeDisplayName); - this.getters.nodeCreatorSearchBar().type('{enter}{esc}'); + this.getters.nodeCreatorSearchBar().type('{enter}'); + cy.get('body').type('{esc}'); }, openNodeNdv: (nodeTypeName: string) => { this.getters.canvasNodeByName(nodeTypeName).dblclick(); diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.stories.ts b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.stories.ts new file mode 100644 index 0000000000..7991eccca4 --- /dev/null +++ b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.stories.ts @@ -0,0 +1,58 @@ +/* tslint:disable:variable-name */ +import N8nNodeCreatorNode from './NodeCreatorNode.vue'; +import { StoryFn } from '@storybook/vue'; + +export default { + title: 'Modules/Node Creator Node', + component: N8nNodeCreatorNode, +}; + +const DefaultTemplate: StoryFn = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { + N8nNodeCreatorNode, + }, + template: ` + + + + `, +}); + +export const WithTitle = DefaultTemplate.bind({}); +WithTitle.args = { + title: 'Node with title', + tooltipHtml: 'Bold tooltip', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean et vehicula ipsum, eu facilisis lacus. Aliquam commodo vel elit eget mollis. Quisque ac elit non purus iaculis placerat. Quisque fringilla ultrices nisi sed porta.', +}; + +const PanelTemplate: StoryFn = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { + N8nNodeCreatorNode, + }, + data() { + return { + isPanelActive: false, + }; + }, + template: ` + + + + + `, +}); +export const WithPanel = PanelTemplate.bind({}); +WithPanel.args = { + title: 'Node with panel', + isTrigger: true, +}; diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue new file mode 100644 index 0000000000..4e0aef14a9 --- /dev/null +++ b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue @@ -0,0 +1,125 @@ + + + + + + + diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue b/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue new file mode 100644 index 0000000000..dda6b227e7 --- /dev/null +++ b/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/index.ts b/packages/design-system/src/components/N8nNodeCreatorNode/index.ts new file mode 100644 index 0000000000..657f0d26f2 --- /dev/null +++ b/packages/design-system/src/components/N8nNodeCreatorNode/index.ts @@ -0,0 +1,3 @@ +import NodeCreatorNode from './NodeCreatorNode.vue'; + +export default NodeCreatorNode; diff --git a/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue b/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue index 79234705d1..80948018a3 100644 --- a/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue +++ b/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue @@ -2,24 +2,35 @@
- + +
- +
-
+
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }} ?
+
@@ -91,7 +102,7 @@ export default Vue.extend({ diff --git a/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue b/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue index 9d6b50b62b..ae18d680d3 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/CategorizedItems.vue @@ -1,15 +1,14 @@