From 36a923cf7bd4d42b8f8decbf01255c41d6dc1671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Tue, 19 Dec 2023 15:10:03 +0100 Subject: [PATCH] feat(editor): Add lead enrichment suggestions to workflow list (#8042) ## Summary We want to show lead enrichment template suggestions to cloud users that agreed to this. This PR introduces the front-end part of this feature - Handoff document - Figma Hi-fi - [How to test](https://linear.app/n8n/issue/ADO-1549/[n8n-fe]-update-workflows-list-page-to-show-fake-door-templates#comment-b6644c99) Tests are being worked on in a separate PR ## Related tickets and issues Fixes ADO-1546 Fixes ADO-1549 Fixes ADO-1604 ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests. --------- Co-authored-by: ricardo --- cypress/e2e/36-suggested-templates.cy.ts | 143 ++++ cypress/fixtures/Suggested_Templates.json | 655 ++++++++++++++++++ cypress/pages/workflow.ts | 1 + cypress/pages/workflows.ts | 7 + .../N8nRecycleScroller/RecycleScroller.vue | 6 + .../RecycleScroller.spec.ts.snap | 1 + packages/editor-ui/src/Interface.ts | 99 ++- packages/editor-ui/src/api/cloudPlans.ts | 8 +- .../src/components/CollectionWorkflowCard.vue | 2 +- packages/editor-ui/src/components/Modals.vue | 13 + packages/editor-ui/src/components/Node.vue | 8 +- .../SuggestedTemplatesPage.vue | 108 +++ .../SuggestedTemplatesPreviewModal.vue | 108 +++ .../SuggestedTemplatesSection.vue | 82 +++ .../src/components/TemplatesInfoCard.vue | 6 +- .../src/components/TemplatesInfoCarousel.vue | 35 +- .../src/components/WorkflowPreview.vue | 3 + .../__tests__/WorkflowPreview.test.ts | 7 +- .../layouts/ResourcesListLayout.vue | 3 + .../editor-ui/src/composables/useToast.ts | 22 + packages/editor-ui/src/constants.ts | 3 + .../src/plugins/i18n/locales/en.json | 9 + .../editor-ui/src/stores/cloudPlan.store.ts | 26 +- packages/editor-ui/src/stores/ui.store.ts | 25 + packages/editor-ui/src/views/NodeView.vue | 8 +- .../editor-ui/src/views/WorkflowsView.vue | 86 ++- 26 files changed, 1387 insertions(+), 87 deletions(-) create mode 100644 cypress/e2e/36-suggested-templates.cy.ts create mode 100644 cypress/fixtures/Suggested_Templates.json create mode 100644 packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesPage.vue create mode 100644 packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesPreviewModal.vue create mode 100644 packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesSection.vue diff --git a/cypress/e2e/36-suggested-templates.cy.ts b/cypress/e2e/36-suggested-templates.cy.ts new file mode 100644 index 0000000000..897714a835 --- /dev/null +++ b/cypress/e2e/36-suggested-templates.cy.ts @@ -0,0 +1,143 @@ +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; + +type SuggestedTemplatesStub = { + sections: SuggestedTemplatesSectionStub[]; +} + +type SuggestedTemplatesSectionStub = { + name: string; + title: string; + description: string; + workflows: Array; +}; + +const WorkflowsListPage = new WorkflowsPageClass(); +const WorkflowPage = new WorkflowPageClass(); + +let fixtureSections: SuggestedTemplatesStub = { sections: [] };; + +describe('Suggested templates - Should render', () => { + + before(() => { + cy.fixture('Suggested_Templates.json').then((data) => { + fixtureSections = data; + }); + }); + + beforeEach(() => { + localStorage.removeItem('SHOW_N8N_SUGGESTED_TEMPLATES'); + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }).as('loadSettings'); + cy.intercept('GET', '/rest/cloud/proxy/templates', { + fixture: 'Suggested_Templates.json', + }); + cy.visit(WorkflowsListPage.url); + cy.wait('@loadSettings'); + }); + + it('should render suggested templates page in empty workflow list', () => { + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('exist'); + WorkflowsListPage.getters.suggestedTemplatesCards().should('have.length', fixtureSections.sections[0].workflows.length); + WorkflowsListPage.getters.suggestedTemplatesSectionDescription().should('contain', fixtureSections.sections[0].description); + }); + + it('should render suggested templates when there are workflows in the list', () => { + WorkflowsListPage.getters.suggestedTemplatesNewWorkflowButton().click(); + cy.createFixtureWorkflow('Test_workflow_1.json', 'Test Workflow'); + cy.visit(WorkflowsListPage.url); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('exist'); + cy.contains(`Explore ${fixtureSections.sections[0].name.toLocaleLowerCase()} workflow templates`).should('exist'); + WorkflowsListPage.getters.suggestedTemplatesCards().should('have.length', fixtureSections.sections[0].workflows.length); + }); + + it('should enable users to signup for suggested templates templates', () => { + // Test the whole flow + WorkflowsListPage.getters.suggestedTemplatesCards().first().click(); + WorkflowsListPage.getters.suggestedTemplatesPreviewModal().should('exist'); + WorkflowsListPage.getters.suggestedTemplatesUseTemplateButton().click(); + cy.url().should('include', '/workflow/new'); + WorkflowPage.getters.infoToast().should('contain', 'Template coming soon!'); + WorkflowPage.getters.infoToast().contains('Notify me when it\'s available').click(); + WorkflowPage.getters.successToast().should('contain', 'We will contact you via email once this template is released.'); + cy.visit(WorkflowsListPage.url); + // Once users have signed up for a template, suggestions should not be shown again + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + +}); + +describe('Suggested templates - Should not render', () => { + beforeEach(() => { + localStorage.removeItem('SHOW_N8N_SUGGESTED_TEMPLATES'); + cy.visit(WorkflowsListPage.url); + }); + + it('should not render suggested templates templates if not in cloud deployment', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'notCloud' } }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint throws error', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', { statusCode: 500 }).as('loadTemplates'); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint returns empty list', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', (req) => { + req.on('response', (res) => { + res.send({ + data: { collections: [] }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); + + it('should not render suggested templates templates if endpoint returns invalid response', () => { + cy.intercept('GET', '/rest/settings', (req) => { + req.on('response', (res) => { + res.send({ + data: { ...res.body.data, deployment: { type: 'cloud' } }, + }); + }); + }); + cy.intercept('GET', '/rest/cloud/proxy/templates', (req) => { + req.on('response', (res) => { + res.send({ + data: { somethingElse: [] }, + }); + }); + }); + WorkflowsListPage.getters.suggestedTemplatesPageContainer().should('not.exist'); + WorkflowsListPage.getters.suggestedTemplatesSectionContainer().should('not.exist'); + }); +}); diff --git a/cypress/fixtures/Suggested_Templates.json b/cypress/fixtures/Suggested_Templates.json new file mode 100644 index 0000000000..3d891ccb53 --- /dev/null +++ b/cypress/fixtures/Suggested_Templates.json @@ -0,0 +1,655 @@ +{ + "sections": [ + { + "name": "Lead enrichment", + "description": "Explore curated lead enrichment workflows or start fresh with a blank canvas", + "workflows": [ + { + "title": "Score new leads with AI from Facebook Lead Ads with AI and get notifications for high scores on Slack", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 24, + "icon": "fa:code-branch", + "defaults": { + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "displayName": "Merge" + } + ] + }, + { + "title": "Verify the email address every time a contact is created in HubSpot", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "name": "n8n-nodes-base.function", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "categories": [ + { + "id": 5, + "name": "Development" + }, + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Function", + "typeVersion": 1 + }, + { + "id": 24, + "icon": "fa:code-branch", + "name": "n8n-nodes-base.merge", + "defaults": { + "name": "Merge", + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "categories": [ + { + "id": 9, + "name": "Core Nodes" + } + ], + "displayName": "Merge", + "typeVersion": 2 + } + ] + }, + { + "title": "Enrich leads from HubSpot with company information via OpenAi", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "displayName": "Function" + } + ] + }, + { + "title": "Score new lead submissions from Facebook Lead Ads with AI and notify me on Slack when it is a high score lead", + "description": "This workflow will help you save tons of time and will notify you fully automatically about the most important incoming leads from Facebook Lead Ads. The workflow will automatically fire for every submission. It will then take the name, company, and email information, enrich the submitter via AI, and score it based on metrics that you can easily set.", + "preview": { + "nodes": [ + { + "parameters": { + "operation": "create", + "base": { + "__rl": true, + "mode": "list", + "value": "" + }, + "table": { + "__rl": true, + "mode": "list", + "value": "" + }, + "columns": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [] + }, + "options": {} + }, + "id": "b09d4f4d-19fa-43de-8148-2d430a04956f", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 2, + "position": [ + 1800, + 740 + ] + }, + { + "parameters": {}, + "id": "551313bb-1e01-4133-9956-e6f09968f2ce", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 920, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "b4c089ee-2adb-435e-8d48-47012c981a11", + "name": "Get image", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1140, + 740 + ] + }, + { + "parameters": { + "operation": "extractHtmlContent", + "options": {} + }, + "id": "04ca2f61-b930-4fbc-b467-3470c0d93d64", + "name": "Extract Information", + "type": "n8n-nodes-base.html", + "typeVersion": 1, + "position": [ + 1360, + 740 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "d1a77493-c579-4ac4-b6a7-708eea2bf8ce", + "name": "Set Information", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 1580, + 740 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Get image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get image": { + "main": [ + [ + { + "node": "Extract Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Information": { + "main": [ + [ + { + "node": "Set Information", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Information": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } + }, + "nodes": [ + { + "id": 14, + "icon": "fa:code", + "defaults": { + "name": "Function", + "color": "#FF9922" + }, + "iconData": { + "icon": "code", + "type": "icon" + }, + "displayName": "Function" + }, + { + "id": 24, + "icon": "fa:code-branch", + "defaults": { + "name": "Merge", + "color": "#00bbcc" + }, + "iconData": { + "icon": "code-branch", + "type": "icon" + }, + "displayName": "Merge" + } + ] + } + ] + } + ] +} diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 7613167f5e..d775a0d61d 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -49,6 +49,7 @@ export class WorkflowPage extends BasePage { successToast: () => cy.get('.el-notification:has(.el-notification--success)'), warningToast: () => cy.get('.el-notification:has(.el-notification--warning)'), errorToast: () => cy.get('.el-notification:has(.el-notification--error)'), + infoToast: () => cy.get('.el-notification:has(.el-notification--info)'), activatorSwitch: () => cy.getByTestId('workflow-activate-switch'), workflowMenu: () => cy.getByTestId('workflow-menu'), firstStepButton: () => cy.getByTestId('canvas-add-button'), diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index 5f0b491d5f..4c18c21859 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -34,6 +34,13 @@ export class WorkflowsPage extends BasePage { // Not yet implemented // myWorkflows: () => cy.getByTestId('my-workflows'), // allWorkflows: () => cy.getByTestId('all-workflows'), + suggestedTemplatesPageContainer: () => cy.getByTestId('suggested-templates-page-container'), + suggestedTemplatesCards: () => cy.getByTestId('templates-info-card').filter(':visible'), + suggestedTemplatesNewWorkflowButton: () => cy.getByTestId('suggested-templates-new-workflow-button'), + suggestedTemplatesSectionContainer: () => cy.getByTestId('suggested-templates-section-container'), + suggestedTemplatesPreviewModal: () => cy.getByTestId('suggested-templates-preview-modal'), + suggestedTemplatesUseTemplateButton: () => cy.getByTestId('use-template-button'), + suggestedTemplatesSectionDescription: () => cy.getByTestId('suggested-template-section-description'), }; actions = { diff --git a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue index abd34dae17..5e67f23660 100644 --- a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue +++ b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue @@ -236,6 +236,9 @@ export default defineComponent({ +
+ +
@@ -263,4 +266,7 @@ export default defineComponent({ position: relative; width: 100%; } +.post-list-container { + margin-top: var(--spacing-3xl); +} diff --git a/packages/design-system/src/components/N8nRecycleScroller/__tests__/__snapshots__/RecycleScroller.spec.ts.snap b/packages/design-system/src/components/N8nRecycleScroller/__tests__/__snapshots__/RecycleScroller.spec.ts.snap index 09da3dec0c..3060dc6f21 100644 --- a/packages/design-system/src/components/N8nRecycleScroller/__tests__/__snapshots__/RecycleScroller.spec.ts.snap +++ b/packages/design-system/src/components/N8nRecycleScroller/__tests__/__snapshots__/RecycleScroller.spec.ts.snap @@ -9,5 +9,6 @@ exports[`components > N8nRecycleScroller > should render correctly 1`] = `
+
" `; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index ccdabbacc6..fbcf70868a 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -6,46 +6,47 @@ import type { TRIGGER_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW, + VIEWS, } from './constants'; import type { IMenuItem } from 'n8n-design-system'; -import type { - GenericValue, - IConnections, - ICredentialsDecrypted, - ICredentialsEncrypted, - ICredentialType, - IDataObject, - INode, - INodeIssues, - INodeParameters, - INodeTypeDescription, - IPinData, - IRunExecutionData, - IRun, - IRunData, - ITaskData, - IWorkflowSettings as IWorkflowSettingsWorkflow, - WorkflowExecuteMode, - PublicInstalledPackage, - INodeTypeNameVersion, - ILoadOptions, - INodeCredentials, - INodeListSearchItems, - NodeParameterValueType, - IDisplayOptions, - IExecutionsSummary, - FeatureFlags, - ExecutionStatus, - ITelemetryTrackProperties, - IUserManagementSettings, - WorkflowSettings, - IUserSettings, - IN8nUISettings, - BannerName, - INodeExecutionData, - INodeProperties, - NodeConnectionType, - INodeCredentialsDetails, +import { + type GenericValue, + type IConnections, + type ICredentialsDecrypted, + type ICredentialsEncrypted, + type ICredentialType, + type IDataObject, + type INode, + type INodeIssues, + type INodeParameters, + type INodeTypeDescription, + type IPinData, + type IRunExecutionData, + type IRun, + type IRunData, + type ITaskData, + type IWorkflowSettings as IWorkflowSettingsWorkflow, + type WorkflowExecuteMode, + type PublicInstalledPackage, + type INodeTypeNameVersion, + type ILoadOptions, + type INodeCredentials, + type INodeListSearchItems, + type NodeParameterValueType, + type IDisplayOptions, + type IExecutionsSummary, + type FeatureFlags, + type ExecutionStatus, + type ITelemetryTrackProperties, + type IUserManagementSettings, + type WorkflowSettings, + type IUserSettings, + type IN8nUISettings, + type BannerName, + type INodeExecutionData, + type INodeProperties, + type NodeConnectionType, + type INodeCredentialsDetails, } from 'n8n-workflow'; import type { BulkCommand, Undoable } from '@/models/history'; import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers'; @@ -1259,6 +1260,10 @@ export interface UIState { bannersHeight: number; bannerStack: BannerName[]; theme: ThemeOption; + suggestedTemplates?: SuggestedTemplates; + pendingNotificationsForViews: { + [key in VIEWS]?: NotificationOptions[]; + }; } export type IFakeDoor = { @@ -1836,3 +1841,21 @@ export type ToggleNodeCreatorOptions = { export type AppliedThemeOption = 'light' | 'dark'; export type ThemeOption = AppliedThemeOption | 'system'; + +export type SuggestedTemplates = { + sections: SuggestedTemplatesSection[]; +}; + +export type SuggestedTemplatesSection = { + name: string; + title: string; + description: string; + workflows: SuggestedTemplatesWorkflowPreview[]; +}; + +export type SuggestedTemplatesWorkflowPreview = { + title: string; + description: string; + preview: IWorkflowData; + nodes: Array>; +}; diff --git a/packages/editor-ui/src/api/cloudPlans.ts b/packages/editor-ui/src/api/cloudPlans.ts index 920563c0d3..c849f353a1 100644 --- a/packages/editor-ui/src/api/cloudPlans.ts +++ b/packages/editor-ui/src/api/cloudPlans.ts @@ -1,4 +1,4 @@ -import type { Cloud, IRestApiContext, InstanceUsage } from '@/Interface'; +import type { Cloud, IRestApiContext, InstanceUsage, LeadEnrichmentTemplates } from '@/Interface'; import { get, post } from '@/utils/apiUtils'; export async function getCurrentPlan(context: IRestApiContext): Promise { @@ -20,3 +20,9 @@ export async function confirmEmail(context: IRestApiContext): Promise { return get(context.baseUrl, '/cloud/proxy/login/code'); } + +export async function fetchSuggestedTemplates( + context: IRestApiContext, +): Promise { + return get(context.baseUrl, '/cloud/proxy/templates'); +} diff --git a/packages/editor-ui/src/components/CollectionWorkflowCard.vue b/packages/editor-ui/src/components/CollectionWorkflowCard.vue index b7775bc973..ed7c951daf 100644 --- a/packages/editor-ui/src/components/CollectionWorkflowCard.vue +++ b/packages/editor-ui/src/components/CollectionWorkflowCard.vue @@ -30,7 +30,7 @@ export default defineComponent({ diff --git a/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesPreviewModal.vue b/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesPreviewModal.vue new file mode 100644 index 0000000000..fda9d5eb9c --- /dev/null +++ b/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesPreviewModal.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesSection.vue b/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesSection.vue new file mode 100644 index 0000000000..cd5af72e51 --- /dev/null +++ b/packages/editor-ui/src/components/SuggestedTemplates/SuggestedTemplatesSection.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/packages/editor-ui/src/components/TemplatesInfoCard.vue b/packages/editor-ui/src/components/TemplatesInfoCard.vue index 98300bdcfd..1ad04039f9 100644 --- a/packages/editor-ui/src/components/TemplatesInfoCard.vue +++ b/packages/editor-ui/src/components/TemplatesInfoCard.vue @@ -1,5 +1,5 @@