mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
fix(editor): Resolve expressions for grandparent nodes (#5859)
* fix(editor): Resolve expressions for grandparent nodes
* test: add tests
* test: add tests for bug
* test: add todos
* test: lintfix
* test: add small waits
* test: add linking tests
* test: add test for branch mapping
* test: update workflow values
* test: comment out test
* test: fix up tests with new values
* chore: remove todos
* test: add ticket number for broken test
* test: refactor a bit
* test: uncomment
* test: fix mapping test
* fix: lint issue
* test: split tests
* Revert "test: split tests"
0290d51d7c
* test: update mousedown
* test: split up tests
* test: fix test
* test: fix test
* test: make less flaky
* test: make less flaky
* test: enable teset
This commit is contained in:
parent
d17d050a16
commit
a19d4447ac
|
@ -205,7 +205,7 @@ describe('Data mapping', () => {
|
||||||
'have.text',
|
'have.text',
|
||||||
`{{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input[0].count }} {{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input }}`,
|
`{{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input[0].count }} {{ $node['${SCHEDULE_TRIGGER_NODE_NAME}'].json.input }}`,
|
||||||
);
|
);
|
||||||
ndv.getters.parameterExpressionPreview('value').should('not.exist');
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '[empty]');
|
||||||
|
|
||||||
ndv.actions.selectInputNode('Set');
|
ndv.actions.selectInputNode('Set');
|
||||||
|
|
||||||
|
|
288
cypress/e2e/24-ndv-paired-item.cy.ts
Normal file
288
cypress/e2e/24-ndv-paired-item.cy.ts
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
const workflowPage = new WorkflowPage();
|
||||||
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
describe('NDV', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.resetAll();
|
||||||
|
cy.skipSetup();
|
||||||
|
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
workflowPage.actions.visit();
|
||||||
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps paired input and output items', () => {
|
||||||
|
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||||
|
cy.get('body').paste(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
|
||||||
|
workflowPage.actions.executeWorkflow();
|
||||||
|
|
||||||
|
workflowPage.actions.openNode('Item Lists');
|
||||||
|
|
||||||
|
ndv.getters.inputPanel().contains('6 items').should('exist');
|
||||||
|
ndv.getters.outputPanel().contains('6 items').should('exist');
|
||||||
|
|
||||||
|
ndv.actions.switchInputMode('Table');
|
||||||
|
ndv.actions.switchOutputMode('Table');
|
||||||
|
|
||||||
|
// input to output
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('exist')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputTableRow(4)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(2)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputTableRow(2)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(3)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputTableRow(6)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
// output to input
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(4)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(4)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(2)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(2)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(6)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(3)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(4)
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps paired input and output items based on selected input node', () => {
|
||||||
|
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||||
|
cy.get('body').paste(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
workflowPage.actions.executeWorkflow();
|
||||||
|
workflowPage.actions.openNode('Set2');
|
||||||
|
|
||||||
|
ndv.getters.inputPanel().contains('6 items').should('exist');
|
||||||
|
ndv.getters.outputRunSelector()
|
||||||
|
.should('exist')
|
||||||
|
.should('include.text', '2 of 2 (6 items)');
|
||||||
|
|
||||||
|
ndv.actions.switchInputMode('Table');
|
||||||
|
ndv.actions.switchOutputMode('Table');
|
||||||
|
cy.wait(50);
|
||||||
|
|
||||||
|
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||||
|
ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||||
|
|
||||||
|
ndv.actions.selectInputNode('Set1');
|
||||||
|
ndv.getters.inputHoveringItem().should('have.text', '1000').realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('have.text', '1000');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||||
|
|
||||||
|
ndv.actions.selectInputNode('Item Lists');
|
||||||
|
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
|
||||||
|
ndv.getters.inputHoveringItem().should('have.text', '1111').realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('have.text', '1111');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps paired input and output items based on selected run', () => {
|
||||||
|
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||||
|
cy.get('body').paste(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
workflowPage.actions.executeWorkflow();
|
||||||
|
workflowPage.actions.openNode('Set3');
|
||||||
|
|
||||||
|
ndv.getters.inputRunSelector()
|
||||||
|
.should('exist')
|
||||||
|
.find('input')
|
||||||
|
.should('include.value', '2 of 2 (6 items)');
|
||||||
|
ndv.getters.outputRunSelector()
|
||||||
|
.should('exist')
|
||||||
|
.find('input')
|
||||||
|
.should('include.value', '2 of 2 (6 items)');
|
||||||
|
|
||||||
|
ndv.actions.switchInputMode('Table');
|
||||||
|
ndv.actions.switchOutputMode('Table');
|
||||||
|
|
||||||
|
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
|
||||||
|
ndv.getters.inputRunSelector().find('input')
|
||||||
|
.should('include.value', '1 of 2 (6 items)');
|
||||||
|
ndv.getters.outputRunSelector().find('input')
|
||||||
|
.should('include.value', '1 of 2 (6 items)');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '1111')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '1111')
|
||||||
|
.realHover();
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(3)
|
||||||
|
.should('have.text', '4444')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(3)
|
||||||
|
.should('have.text', '4444')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.actions.changeOutputRunSelector('2 of 2 (6 items)');
|
||||||
|
cy.wait(50);
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '1000')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '1000')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(3)
|
||||||
|
.should('have.text', '2000')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(3)
|
||||||
|
.should('have.text', '2000')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves expression with default item when input node is not parent, while still pairing items', () => {
|
||||||
|
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||||
|
cy.get('body').paste(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
workflowPage.actions.executeWorkflow();
|
||||||
|
workflowPage.actions.openNode('Set2');
|
||||||
|
|
||||||
|
ndv.getters.inputPanel().contains('6 items').should('exist');
|
||||||
|
ndv.getters.outputRunSelector()
|
||||||
|
.should('exist')
|
||||||
|
.should('include.text', '2 of 2 (6 items)');
|
||||||
|
|
||||||
|
ndv.actions.switchInputMode('Table');
|
||||||
|
ndv.actions.switchOutputMode('Table');
|
||||||
|
|
||||||
|
ndv.getters.backToCanvas().realHover(); // reset to default hover
|
||||||
|
ndv.getters.inputHoveringItem().should('have.text', '1111').realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1111');
|
||||||
|
|
||||||
|
ndv.actions.selectInputNode('Code1');
|
||||||
|
ndv.getters.inputTableRow(1).realHover();
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '1000')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '1000');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||||
|
|
||||||
|
ndv.actions.selectInputNode('Code');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1).realHover();
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '6666')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||||
|
|
||||||
|
ndv.actions.selectInputNode('When clicking');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1).realHover();
|
||||||
|
ndv.getters.inputTableRow(1).should('have.text', "This is an item, but it's empty.").realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('have.length', 6);
|
||||||
|
ndv.getters.parameterExpressionPreview('value').should('include.text', '1000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can pair items between input and output across branches and runs', () => {
|
||||||
|
cy.fixture('Test_workflow_5.json').then((data) => {
|
||||||
|
cy.get('body').paste(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
workflowPage.actions.executeWorkflow();
|
||||||
|
workflowPage.actions.openNode('IF');
|
||||||
|
|
||||||
|
ndv.actions.switchInputMode('Table');
|
||||||
|
ndv.actions.switchOutputMode('Table');
|
||||||
|
|
||||||
|
ndv.actions.switchOutputBranch('False Branch (2 items)');
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '8888')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(5)
|
||||||
|
.should('have.text', '8888')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.getters.outputTableRow(2)
|
||||||
|
.should('have.text', '9999')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputTableRow(6)
|
||||||
|
.should('have.text', '9999')
|
||||||
|
.invoke('attr', 'data-test-id')
|
||||||
|
.should('equal', 'hovering-item');
|
||||||
|
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.openNode('Set5');
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '8888')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.inputHoveringItem().should('not.exist');
|
||||||
|
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '1111')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
|
||||||
|
ndv.actions.switchIntputBranch('False Branch');
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '8888')
|
||||||
|
.realHover();
|
||||||
|
ndv.getters.outputHoveringItem().should('have.text', '8888');
|
||||||
|
|
||||||
|
ndv.actions.changeOutputRunSelector('1 of 2 (4 items)')
|
||||||
|
ndv.getters.outputTableRow(1)
|
||||||
|
.should('have.text', '1111')
|
||||||
|
.realHover();
|
||||||
|
// todo there's a bug here need to fix ADO-534
|
||||||
|
// ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,6 +15,7 @@ describe('NDV', () => {
|
||||||
workflowPage.actions.renameWorkflow(uuid());
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show up when double clicked on a node and close when Back to canvas clicked', () => {
|
it('should show up when double clicked on a node and close when Back to canvas clicked', () => {
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
workflowPage.getters.canvasNodes().first().dblclick();
|
workflowPage.getters.canvasNodes().first().dblclick();
|
||||||
|
|
292
cypress/fixtures/Test_workflow_5.json
Normal file
292
cypress/fixtures/Test_workflow_5.json
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "8147b3a74cd161276e0f3bfc17369a724afab0d377593fada8be82d34c0c6a95"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n id: 6666\n },\n {\n id: 3333\n },\n {\n id: 9999\n },\n {\n id: 1111\n },\n {\n id: 4444\n },\n {\n id: 8888\n },\n]"
|
||||||
|
},
|
||||||
|
"id": "5f023c7c-67ca-47a0-8a90-8227fcf29b9c",
|
||||||
|
"name": "Code",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
-520,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"value": "={{ $json.id }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "bd454282-9dd7-465f-9b9a-654a0c8532ec",
|
||||||
|
"name": "Set2",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-40,
|
||||||
|
780
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
-740,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "sort",
|
||||||
|
"sortFieldsUi": {
|
||||||
|
"sortField": [
|
||||||
|
{
|
||||||
|
"fieldName": "id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "555a150c-d735-4331-b628-c1f1cfed2da1",
|
||||||
|
"name": "Item Lists",
|
||||||
|
"type": "n8n-nodes-base.itemLists",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-280,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"value": "={{ $json.id }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "02372cb6-aac8-45c3-8600-f699901289ac",
|
||||||
|
"name": "Set",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-60,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "00d73944-218c-4896-af68-3f2855a922d1",
|
||||||
|
"name": "Set1",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-280,
|
||||||
|
780
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"number": [
|
||||||
|
{
|
||||||
|
"value1": "={{ $json.id }}",
|
||||||
|
"operation": "smallerEqual",
|
||||||
|
"value2": 6666
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "211a7bef-32d1-4928-9cef-3a45f2e61379",
|
||||||
|
"name": "IF",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
160,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "dcbd4745-832f-43d8-8a3c-dd80e8ca2777",
|
||||||
|
"name": "Set3",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
140,
|
||||||
|
780
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n id: 1000\n },\n {\n id: 300\n },\n {\n id: 2000\n },\n {\n id: 100\n },\n {\n id: 400\n },\n {\n id: 1300\n },\n]"
|
||||||
|
},
|
||||||
|
"id": "ec9c8f16-f3c8-4054-a6e9-4f1ebcdebb71",
|
||||||
|
"name": "Code1",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
-520,
|
||||||
|
780
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "42e89478-a53a-4d10-b20c-1dc5d5f953d5",
|
||||||
|
"name": "Set4",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
460,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "5085eb1c-0345-4b9d-856a-2955279f2c5d",
|
||||||
|
"name": "Set5",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
460,
|
||||||
|
660
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"Code": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Item Lists",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Set2": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set3",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Code",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Code1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Item Lists": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Set2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Set": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "IF",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Set1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IF": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set4",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Set5",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set5",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Set1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,12 @@ export class NDV extends BasePage {
|
||||||
executePrevious: () => cy.getByTestId('execute-previous-node'),
|
executePrevious: () => cy.getByTestId('execute-previous-node'),
|
||||||
httpRequestNotice: () => cy.getByTestId('node-parameters-http-notice'),
|
httpRequestNotice: () => cy.getByTestId('node-parameters-http-notice'),
|
||||||
nthParam: (n: number) => cy.getByTestId('node-parameters').find('.parameter-item').eq(n),
|
nthParam: (n: number) => cy.getByTestId('node-parameters').find('.parameter-item').eq(n),
|
||||||
|
inputRunSelector: () => this.getters.inputPanel().findChildByTestId('run-selector'),
|
||||||
|
outputRunSelector: () => this.getters.outputPanel().findChildByTestId('run-selector'),
|
||||||
|
outputHoveringItem: () => this.getters.outputPanel().findChildByTestId('hovering-item'),
|
||||||
|
inputHoveringItem: () => this.getters.inputPanel().findChildByTestId('hovering-item'),
|
||||||
|
outputBranches: () => this.getters.outputPanel().findChildByTestId('branches'),
|
||||||
|
inputBranches: () => this.getters.inputPanel().findChildByTestId('branches'),
|
||||||
};
|
};
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -119,5 +125,29 @@ export class NDV extends BasePage {
|
||||||
this.actions.editPinnedData();
|
this.actions.editPinnedData();
|
||||||
this.actions.savePinnedData();
|
this.actions.savePinnedData();
|
||||||
},
|
},
|
||||||
|
changeInputRunSelector: (runName: string) => {
|
||||||
|
this.getters.inputRunSelector().click();
|
||||||
|
cy.get('.el-select-dropdown:visible .el-select-dropdown__item')
|
||||||
|
.contains(runName)
|
||||||
|
.click();
|
||||||
|
},
|
||||||
|
changeOutputRunSelector: (runName: string) => {
|
||||||
|
this.getters.outputRunSelector().click();
|
||||||
|
cy.get('.el-select-dropdown:visible .el-select-dropdown__item')
|
||||||
|
.contains(runName)
|
||||||
|
.click();
|
||||||
|
},
|
||||||
|
toggleOutputRunLinking: () => {
|
||||||
|
this.getters.outputRunSelector().find('button').click();
|
||||||
|
},
|
||||||
|
toggleInputRunLinking: () => {
|
||||||
|
this.getters.inputRunSelector().find('button').click();
|
||||||
|
},
|
||||||
|
switchOutputBranch: (name: string) => {
|
||||||
|
this.getters.outputBranches().get('span').contains(name).click();
|
||||||
|
},
|
||||||
|
switchIntputBranch: (name: string) => {
|
||||||
|
this.getters.inputBranches().get('span').contains(name).click();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,11 @@ export default Vue.extend({
|
||||||
return (this.hoveringItem?.itemIndex ?? 0) + 1;
|
return (this.hoveringItem?.itemIndex ?? 0) + 1;
|
||||||
},
|
},
|
||||||
hoveringItem(): TargetItem | null {
|
hoveringItem(): TargetItem | null {
|
||||||
return this.ndvStore.hoveringItem;
|
if (this.ndvStore.isInputParentOfActiveNode) {
|
||||||
|
return this.ndvStore.hoveringItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
isDragging(): boolean {
|
isDragging(): boolean {
|
||||||
return this.ndvStore.isDraggableDragging;
|
return this.ndvStore.isDraggableDragging;
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
:class="$style.hint"
|
:class="$style.hint"
|
||||||
data-test-id="parameter-expression-preview"
|
data-test-id="parameter-expression-preview"
|
||||||
class="ph-no-capture"
|
class="ph-no-capture"
|
||||||
:highlight="!!(expressionOutput && targetItem)"
|
:highlight="!!(expressionOutput && targetItem) && isInputParentOfActiveNode"
|
||||||
:hint="expressionOutput"
|
:hint="expressionOutput"
|
||||||
:singleLine="true"
|
:singleLine="true"
|
||||||
/>
|
/>
|
||||||
|
@ -153,25 +153,29 @@ export default mixins(showMessage, workflowHelpers).extend({
|
||||||
targetItem(): TargetItem | null {
|
targetItem(): TargetItem | null {
|
||||||
return this.ndvStore.hoveringItem;
|
return this.ndvStore.hoveringItem;
|
||||||
},
|
},
|
||||||
|
isInputParentOfActiveNode(): boolean {
|
||||||
|
return this.ndvStore.isInputParentOfActiveNode;
|
||||||
|
},
|
||||||
expressionValueComputed(): string | null {
|
expressionValueComputed(): string | null {
|
||||||
const inputNodeName: string | undefined = this.ndvStore.ndvInputNodeName;
|
|
||||||
const value = isResourceLocatorValue(this.value) ? this.value.value : this.value;
|
const value = isResourceLocatorValue(this.value) ? this.value.value : this.value;
|
||||||
if (this.activeNode === null || !this.isValueExpression || typeof value !== 'string') {
|
if (!this.activeNode || !this.isValueExpression || typeof value !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputRunIndex: number | undefined = this.ndvStore.ndvInputRunIndex;
|
|
||||||
const inputBranchIndex: number | undefined = this.ndvStore.ndvInputBranchIndex;
|
|
||||||
|
|
||||||
let computedValue: NodeParameterValue;
|
let computedValue: NodeParameterValue;
|
||||||
try {
|
try {
|
||||||
const targetItem = this.targetItem ?? undefined;
|
let opts;
|
||||||
computedValue = this.resolveExpression(value, undefined, {
|
if (this.ndvStore.isInputParentOfActiveNode) {
|
||||||
targetItem,
|
opts = {
|
||||||
inputNodeName,
|
targetItem: this.targetItem ?? undefined,
|
||||||
inputRunIndex,
|
inputNodeName: this.ndvStore.ndvInputNodeName,
|
||||||
inputBranchIndex,
|
inputRunIndex: this.ndvStore.ndvInputRunIndex,
|
||||||
});
|
inputBranchIndex: this.ndvStore.ndvInputBranchIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
computedValue = this.resolveExpression(value, undefined, opts);
|
||||||
|
|
||||||
if (computedValue === null) {
|
if (computedValue === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="$style.runSelector" v-if="maxRunIndex > 0" v-show="!editMode.enabled">
|
<div
|
||||||
|
:class="$style.runSelector"
|
||||||
|
v-if="maxRunIndex > 0"
|
||||||
|
v-show="!editMode.enabled"
|
||||||
|
data-test-id="run-selector"
|
||||||
|
>
|
||||||
<n8n-select
|
<n8n-select
|
||||||
size="small"
|
size="small"
|
||||||
:value="runIndex"
|
:value="runIndex"
|
||||||
|
@ -157,7 +162,11 @@
|
||||||
<slot name="run-info"></slot>
|
<slot name="run-info"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="maxOutputIndex > 0 && branches.length > 1" :class="$style.tabs">
|
<div
|
||||||
|
v-if="maxOutputIndex > 0 && branches.length > 1"
|
||||||
|
:class="$style.tabs"
|
||||||
|
data-test-id="branches"
|
||||||
|
>
|
||||||
<n8n-tabs :value="currentOutputIndex" @input="onBranchChange" :options="branches" />
|
<n8n-tabs :value="currentOutputIndex" @input="onBranchChange" :options="branches" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,7 @@
|
||||||
v-for="(row, index1) in tableData.data"
|
v-for="(row, index1) in tableData.data"
|
||||||
:key="index1"
|
:key="index1"
|
||||||
:class="{ [$style.hoveringRow]: isHoveringRow(index1) }"
|
:class="{ [$style.hoveringRow]: isHoveringRow(index1) }"
|
||||||
|
:data-test-id="isHoveringRow(index1) ? 'hovering-item' : undefined"
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
v-for="(data, index2) in row"
|
v-for="(data, index2) in row"
|
||||||
|
|
|
@ -170,16 +170,21 @@ export const expressionManager = mixins(workflowHelpers).extend({
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!useNDVStore().activeNode) {
|
const ndvStore = useNDVStore();
|
||||||
|
if (!ndvStore.activeNode) {
|
||||||
// e.g. credential modal
|
// e.g. credential modal
|
||||||
result.resolved = Expression.resolveWithoutWorkflow(resolvable);
|
result.resolved = Expression.resolveWithoutWorkflow(resolvable);
|
||||||
} else {
|
} else {
|
||||||
result.resolved = this.resolveExpression('=' + resolvable, undefined, {
|
let opts;
|
||||||
targetItem: targetItem ?? undefined,
|
if (ndvStore.isInputParentOfActiveNode) {
|
||||||
inputNodeName: this.ndvStore.ndvInputNodeName,
|
opts = {
|
||||||
inputRunIndex: this.ndvStore.ndvInputRunIndex,
|
targetItem: targetItem ?? undefined,
|
||||||
inputBranchIndex: this.ndvStore.ndvInputBranchIndex,
|
inputNodeName: this.ndvStore.ndvInputNodeName,
|
||||||
});
|
inputRunIndex: this.ndvStore.ndvInputRunIndex,
|
||||||
|
inputBranchIndex: this.ndvStore.ndvInputBranchIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
result.resolved = this.resolveExpression('=' + resolvable, undefined, opts);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
result.resolved = `[${error.message}]`;
|
result.resolved = `[${error.message}]`;
|
||||||
|
|
|
@ -67,9 +67,6 @@ import { getWorkflowPermissions, IPermissions } from '@/permissions';
|
||||||
import { ICredentialsResponse } from '@/Interface';
|
import { ICredentialsResponse } from '@/Interface';
|
||||||
import { useEnvironmentsStore } from '@/stores';
|
import { useEnvironmentsStore } from '@/stores';
|
||||||
|
|
||||||
let cachedWorkflowKey: string | null = '';
|
|
||||||
let cachedWorkflow: Workflow | null = null;
|
|
||||||
|
|
||||||
export function resolveParameter(
|
export function resolveParameter(
|
||||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||||
opts: {
|
opts: {
|
||||||
|
@ -89,10 +86,6 @@ export function resolveParameter(
|
||||||
let parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
let parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
||||||
const executionData = useWorkflowsStore().getWorkflowExecution;
|
const executionData = useWorkflowsStore().getWorkflowExecution;
|
||||||
|
|
||||||
if (opts?.inputNodeName && !parentNode.includes(opts.inputNodeName)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let runIndexParent = opts?.inputRunIndex ?? 0;
|
let runIndexParent = opts?.inputRunIndex ?? 0;
|
||||||
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
|
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
|
||||||
if (opts.targetItem && opts?.targetItem?.nodeName === activeNode!.name && executionData) {
|
if (opts.targetItem && opts?.targetItem?.nodeName === activeNode!.name && executionData) {
|
||||||
|
@ -184,84 +177,20 @@ export function resolveParameter(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentWorkflow(copyData?: boolean): Workflow {
|
function getCurrentWorkflow(copyData?: boolean): Workflow {
|
||||||
const nodes = getNodes();
|
return useWorkflowsStore().getCurrentWorkflow(copyData);
|
||||||
const connections = useWorkflowsStore().allConnections;
|
|
||||||
const cacheKey = JSON.stringify({ nodes, connections });
|
|
||||||
if (!copyData && cachedWorkflow && cacheKey === cachedWorkflowKey) {
|
|
||||||
return cachedWorkflow;
|
|
||||||
}
|
|
||||||
cachedWorkflowKey = cacheKey;
|
|
||||||
|
|
||||||
return getWorkflow(nodes, connections, copyData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a shallow copy of the nodes which means that all the data on the lower
|
|
||||||
// levels still only gets referenced but the top level object is a different one.
|
|
||||||
// This has the advantage that it is very fast and does not cause problems with vuex
|
|
||||||
// when the workflow replaces the node-parameters.
|
|
||||||
function getNodes(): INodeUi[] {
|
function getNodes(): INodeUi[] {
|
||||||
const nodes = useWorkflowsStore().allNodes;
|
return useWorkflowsStore().getNodes();
|
||||||
const returnNodes: INodeUi[] = [];
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
returnNodes.push(Object.assign({}, node));
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnNodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a workflow instance.
|
// Returns a workflow instance.
|
||||||
function getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
function getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
||||||
const nodeTypes = getNodeTypes();
|
return useWorkflowsStore().getWorkflow(nodes, connections, copyData);
|
||||||
let workflowId: string | undefined = useWorkflowsStore().workflowId;
|
|
||||||
if (workflowId && workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
|
||||||
workflowId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowName = useWorkflowsStore().workflowName;
|
|
||||||
|
|
||||||
cachedWorkflow = new Workflow({
|
|
||||||
id: workflowId,
|
|
||||||
name: workflowName,
|
|
||||||
nodes: copyData ? deepCopy(nodes) : nodes,
|
|
||||||
connections: copyData ? deepCopy(connections) : connections,
|
|
||||||
active: false,
|
|
||||||
nodeTypes,
|
|
||||||
settings: useWorkflowsStore().workflowSettings,
|
|
||||||
// @ts-ignore
|
|
||||||
pinData: useWorkflowsStore().getPinData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return cachedWorkflow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeTypes(): INodeTypes {
|
function getNodeTypes(): INodeTypes {
|
||||||
const nodeTypes: INodeTypes = {
|
return useWorkflowsStore().getNodeTypes();
|
||||||
nodeTypes: {},
|
|
||||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => {},
|
|
||||||
// @ts-ignore
|
|
||||||
getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => {
|
|
||||||
const nodeTypeDescription = useNodeTypesStore().getNodeType(nodeType, version);
|
|
||||||
|
|
||||||
if (nodeTypeDescription === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
description: nodeTypeDescription,
|
|
||||||
// As we do not have the trigger/poll functions available in the frontend
|
|
||||||
// we use the information available to figure out what are trigger nodes
|
|
||||||
// @ts-ignore
|
|
||||||
trigger:
|
|
||||||
(![ERROR_TRIGGER_NODE_TYPE, START_NODE_TYPE].includes(nodeType) &&
|
|
||||||
nodeTypeDescription.inputs.length === 0 &&
|
|
||||||
!nodeTypeDescription.webhooks) ||
|
|
||||||
undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return nodeTypes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns connectionInputData to be able to execute an expression.
|
// Returns connectionInputData to be able to execute an expression.
|
||||||
|
|
|
@ -111,6 +111,15 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
isDNVDataEmpty() {
|
isDNVDataEmpty() {
|
||||||
return (panel: 'input' | 'output'): boolean => this[panel].data.isEmpty;
|
return (panel: 'input' | 'output'): boolean => this[panel].data.isEmpty;
|
||||||
},
|
},
|
||||||
|
isInputParentOfActiveNode(): boolean {
|
||||||
|
const inputNodeName = this.ndvInputNodeName;
|
||||||
|
if (!this.activeNode || !inputNodeName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const workflow = useWorkflowsStore().getCurrentWorkflow();
|
||||||
|
const parentNodes = workflow.getParentNodes(this.activeNode.name, 'main', 1);
|
||||||
|
return parentNodes.includes(inputNodeName);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setInputNodeName(name: string | undefined): void {
|
setInputNodeName(name: string | undefined): void {
|
||||||
|
|
|
@ -2,8 +2,10 @@ import {
|
||||||
DEFAULT_NEW_WORKFLOW_NAME,
|
DEFAULT_NEW_WORKFLOW_NAME,
|
||||||
DUPLICATE_POSTFFIX,
|
DUPLICATE_POSTFFIX,
|
||||||
EnterpriseEditionFeature,
|
EnterpriseEditionFeature,
|
||||||
|
ERROR_TRIGGER_NODE_TYPE,
|
||||||
MAX_WORKFLOW_NAME_LENGTH,
|
MAX_WORKFLOW_NAME_LENGTH,
|
||||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||||
|
START_NODE_TYPE,
|
||||||
STORES,
|
STORES,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import {
|
import {
|
||||||
|
@ -36,6 +38,8 @@ import {
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeIssueData,
|
INodeIssueData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
|
INodeTypeData,
|
||||||
|
INodeTypes,
|
||||||
IPinData,
|
IPinData,
|
||||||
IRun,
|
IRun,
|
||||||
IRunData,
|
IRunData,
|
||||||
|
@ -43,6 +47,7 @@ import {
|
||||||
ITaskData,
|
ITaskData,
|
||||||
IWorkflowSettings,
|
IWorkflowSettings,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
@ -86,6 +91,9 @@ const createEmptyWorkflow = (): IWorkflowDb => ({
|
||||||
usedCredentials: [],
|
usedCredentials: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let cachedWorkflowKey: string | null = '';
|
||||||
|
let cachedWorkflow: Workflow | null = null;
|
||||||
|
|
||||||
export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
state: (): WorkflowsState => ({
|
state: (): WorkflowsState => ({
|
||||||
workflow: createEmptyWorkflow(),
|
workflow: createEmptyWorkflow(),
|
||||||
|
@ -256,7 +264,86 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// Workflow actions
|
getNodeTypes(): INodeTypes {
|
||||||
|
const nodeTypes: INodeTypes = {
|
||||||
|
nodeTypes: {},
|
||||||
|
init: async (nodeTypes?: INodeTypeData): Promise<void> => {},
|
||||||
|
// @ts-ignore
|
||||||
|
getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => {
|
||||||
|
const nodeTypeDescription = useNodeTypesStore().getNodeType(nodeType, version);
|
||||||
|
|
||||||
|
if (nodeTypeDescription === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: nodeTypeDescription,
|
||||||
|
// As we do not have the trigger/poll functions available in the frontend
|
||||||
|
// we use the information available to figure out what are trigger nodes
|
||||||
|
// @ts-ignore
|
||||||
|
trigger:
|
||||||
|
(![ERROR_TRIGGER_NODE_TYPE, START_NODE_TYPE].includes(nodeType) &&
|
||||||
|
nodeTypeDescription.inputs.length === 0 &&
|
||||||
|
!nodeTypeDescription.webhooks) ||
|
||||||
|
undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return nodeTypes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns a shallow copy of the nodes which means that all the data on the lower
|
||||||
|
// levels still only gets referenced but the top level object is a different one.
|
||||||
|
// This has the advantage that it is very fast and does not cause problems with vuex
|
||||||
|
// when the workflow replaces the node-parameters.
|
||||||
|
getNodes(): INodeUi[] {
|
||||||
|
const nodes = useWorkflowsStore().allNodes;
|
||||||
|
const returnNodes: INodeUi[] = [];
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
returnNodes.push(Object.assign({}, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnNodes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns a workflow instance.
|
||||||
|
getWorkflow(nodes: INodeUi[], connections: IConnections, copyData?: boolean): Workflow {
|
||||||
|
const nodeTypes = this.getNodeTypes();
|
||||||
|
let workflowId: string | undefined = useWorkflowsStore().workflowId;
|
||||||
|
if (workflowId && workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
||||||
|
workflowId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowName = useWorkflowsStore().workflowName;
|
||||||
|
|
||||||
|
cachedWorkflow = new Workflow({
|
||||||
|
id: workflowId,
|
||||||
|
name: workflowName,
|
||||||
|
nodes: copyData ? deepCopy(nodes) : nodes,
|
||||||
|
connections: copyData ? deepCopy(connections) : connections,
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
settings: useWorkflowsStore().workflowSettings,
|
||||||
|
// @ts-ignore
|
||||||
|
pinData: useWorkflowsStore().getPinData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cachedWorkflow;
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentWorkflow(copyData?: boolean): Workflow {
|
||||||
|
const nodes = this.getNodes();
|
||||||
|
const connections = this.allConnections;
|
||||||
|
const cacheKey = JSON.stringify({ nodes, connections });
|
||||||
|
if (!copyData && cachedWorkflow && cacheKey === cachedWorkflowKey) {
|
||||||
|
return cachedWorkflow;
|
||||||
|
}
|
||||||
|
cachedWorkflowKey = cacheKey;
|
||||||
|
|
||||||
|
return this.getWorkflow(nodes, connections, copyData);
|
||||||
|
},
|
||||||
|
|
||||||
async fetchAllWorkflows(): Promise<IWorkflowDb[]> {
|
async fetchAllWorkflows(): Promise<IWorkflowDb[]> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
|
Loading…
Reference in a new issue