From 22c1890139c89e74df67b9673a1d0c85d647eb9d Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:27:44 +0200 Subject: [PATCH] fix: Prevent copying workflow when copying outside of canvas (#10813) --- cypress/e2e/45-ai-assistant.cy.ts | 55 ++++ .../aiAssistant/code_snippet_response.json | 28 ++ cypress/pages/features/ai-assistant.ts | 1 + .../AskAssistantChat/AskAssistantChat.vue | 1 + .../__tests__/AskAssistantChat.spec.ts | 20 ++ .../AskAssistantChat.spec.ts.snap | 242 ++++++++++++++++++ .../AskAssistant/AskAssistantChat.vue | 2 + 7 files changed, 349 insertions(+) create mode 100644 cypress/fixtures/aiAssistant/code_snippet_response.json diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts index 01d07cbd2b..7bf97eeaf9 100644 --- a/cypress/e2e/45-ai-assistant.cy.ts +++ b/cypress/e2e/45-ai-assistant.cy.ts @@ -374,3 +374,58 @@ describe('AI Assistant Credential Help', () => { aiAssistant.getters.credentialEditAssistantButton().should('be.disabled'); }); }); + +describe('General help', () => { + beforeEach(() => { + aiAssistant.actions.enableAssistant(); + wf.actions.visit(); + }); + + it('assistant returns code snippet', () => { + cy.intercept('POST', '/rest/ai-assistant/chat', { + statusCode: 200, + fixture: 'aiAssistant/code_snippet_response.json', + }).as('chatRequest'); + + aiAssistant.getters.askAssistantFloatingButton().should('be.visible'); + aiAssistant.getters.askAssistantFloatingButton().click(); + aiAssistant.getters.askAssistantChat().should('be.visible'); + aiAssistant.getters.placeholderMessage().should('be.visible'); + aiAssistant.getters.chatInput().should('be.visible'); + + aiAssistant.getters.chatInput().type('Show me an expression'); + aiAssistant.getters.sendMessageButton().click(); + + aiAssistant.getters.chatMessagesAll().should('have.length', 3); + aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', 'Show me an expression'); + + aiAssistant.getters + .chatMessagesAssistant() + .eq(0) + .should('contain.text', 'To use expressions in n8n, follow these steps:'); + + aiAssistant.getters + .chatMessagesAssistant() + .eq(0) + .should( + 'include.html', + `
[
+  {
+    "headers": {
+      "host": "n8n.instance.address",
+      ...
+    },
+    "params": {},
+    "query": {},
+    "body": {
+      "name": "Jim",
+      "age": 30,
+      "city": "New York"
+    }
+  }
+]
+
`, + ); + aiAssistant.getters.codeSnippet().should('have.text', '{{$json.body.city}}'); + }); +}); diff --git a/cypress/fixtures/aiAssistant/code_snippet_response.json b/cypress/fixtures/aiAssistant/code_snippet_response.json new file mode 100644 index 0000000000..b05f212de1 --- /dev/null +++ b/cypress/fixtures/aiAssistant/code_snippet_response.json @@ -0,0 +1,28 @@ +{ + "sessionId": "f1d19ed5-0d55-4bad-b49a-f0c56bd6f76f-705b5dbf-12d4-4805-87a3-1e5b3c716d29-W1JgVNrpfitpSNF9rAjB4", + "messages": [ + { + "role": "assistant", + "type": "message", + "text": "To use expressions in n8n, follow these steps:\n\n1. Hover over the parameter where you want to use an expression.\n2. Select **Expressions** in the **Fixed/Expression** toggle.\n3. Write your expression in the parameter, or select **Open expression editor** to open the expressions editor. You can browse the available data in the **Variable selector**. All expressions have the format `{{ your expression here }}`.\n\n### Example: Get data from webhook body\n\nIf your webhook data looks like this:\n\n```json\n[\n {\n \"headers\": {\n \"host\": \"n8n.instance.address\",\n ...\n },\n \"params\": {},\n \"query\": {},\n \"body\": {\n \"name\": \"Jim\",\n \"age\": 30,\n \"city\": \"New York\"\n }\n }\n]\n```\n\nYou can use the following expression to get the value of `city`:\n\n```js\n{{$json.body.city}}\n```\n\nThis expression accesses the incoming JSON-formatted data using n8n's custom `$json` variable and finds the value of `city` (in this example, \"New York\").", + "codeSnippet": "{{$json.body.city}}" + }, + { + "role": "assistant", + "type": "message", + "text": "Did this answer solve your question?", + "quickReplies": [ + { + "text": "Yes, thanks", + "type": "all-good", + "isFeedback": true + }, + { + "text": "No, I am still stuck", + "type": "still-stuck", + "isFeedback": true + } + ] + } + ] +} diff --git a/cypress/pages/features/ai-assistant.ts b/cypress/pages/features/ai-assistant.ts index 8434074737..ea77724dcf 100644 --- a/cypress/pages/features/ai-assistant.ts +++ b/cypress/pages/features/ai-assistant.ts @@ -37,6 +37,7 @@ export class AIAssistant extends BasePage { cy.getByTestId('node-error-view-ask-assistant-button').find('button').first(), credentialEditAssistantButton: () => cy.getByTestId('credentail-edit-ask-assistant-button').find('button').first(), + codeSnippet: () => cy.getByTestId('assistant-code-snippet'), }; actions = { diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue index 1fb5d42c63..de2f5e1768 100644 --- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -171,6 +171,7 @@ function growInput() {
{ }); expect(container).toMatchSnapshot(); }); + it('renders message with code snippet', () => { + const { container } = render(AskAssistantChat, { + props: { + user: { firstName: 'Kobi', lastName: 'Dog' }, + messages: [ + { + id: '1', + type: 'text', + role: 'assistant', + content: + 'Hi Max! Here is my top solution to fix the error in your **Transform data** node👇', + codeSnippet: + "node.on('input', function(msg) {\n if (msg.seed) { dummyjson.seed = msg.seed; }\n try {\n var value = dummyjson.parse(node.template, {mockdata: msg});\n if (node.syntax === 'json') {\n try { value = JSON.parse(value); }\n catch(e) { node.error(RED._('datagen.errors.json-error')); }\n }\n if (node.fieldType === 'msg') {\n RED.util.setMessageProperty(msg,node.field,value);\n }\n else if (node.fieldType === 'flow') {\n node.context().flow.set(node.field,value);\n }\n else if (node.fieldType === 'global') {\n node.context().global.set(node.field,value);\n }\n node.send(msg);\n }\n catch(e) {", + read: false, + }, + ], + }, + }); + expect(container).toMatchSnapshot(); + }); }); diff --git a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap index c7aa8da5cb..8baba648f9 100644 --- a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap +++ b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap @@ -1180,6 +1180,248 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = ` `; +exports[`AskAssistantChat > renders message with code snippet 1`] = ` +
+
+
+
+
+ + + + + + + + + + + + AI Assistant + +
+
+ beta +
+
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + +
+ + Assistant + +
+
+ + + +
+

+ Hi Max! Here is my top solution to fix the error in your + + Transform data + + node👇 +

+ + +
+ +
+

+ node.on('input', function(msg) { +
+ +if (msg.seed) { dummyjson.seed = msg.seed; } +
+ +try { +
+ +var value = dummyjson.parse(node.template, {mockdata: msg}); +
+ +if (node.syntax === 'json') { +
+ +try { value = JSON.parse(value); } +
+ +catch(e) { node.error(RED._('datagen.errors.json-error')); } +
+ +} +
+ +if (node.fieldType === 'msg') { +
+ +RED.util.setMessageProperty(msg,node.field,value); +
+ +} +
+ +else if (node.fieldType === 'flow') { +
+ +node.context().flow.set(node.field,value); +
+ +} +
+ +else if (node.fieldType === 'global') { +
+ +node.context().global.set(node.field,value); +
+ +} +
+ +node.send(msg); +
+ +} +
+ +catch(e) { +

+
+ +
+ +
+ +
+ +
+
+