Skip to content

Commit

Permalink
feat(editor): Implement workflowSelector parameter type (#10482)
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIvaniv authored Aug 22, 2024
1 parent a73b9a3 commit 84e54be
Show file tree
Hide file tree
Showing 18 changed files with 954 additions and 140 deletions.
1 change: 1 addition & 0 deletions cypress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'
export const AI_MEMORY_POSTGRES_NODE_NAME = 'Postgres Chat Memory';
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';
export const WEBHOOK_NODE_NAME = 'Webhook';
export const EXECUTE_WORKFLOW_NODE_NAME = 'Execute Workflow';

export const META_KEY = Cypress.platform === 'darwin' ? 'meta' : 'ctrl';

Expand Down
82 changes: 82 additions & 0 deletions cypress/e2e/45-workflow-selector-parameter.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { EXECUTE_WORKFLOW_NODE_NAME } from '../constants';
import { WorkflowPage as WorkflowPageClass, NDV } from '../pages';
import { getVisiblePopper } from '../utils';

const workflowPage = new WorkflowPageClass();
const ndv = new NDV();

describe('Workflow Selector Parameter', () => {
beforeEach(() => {
cy.resetDatabase();
cy.signinAsOwner();
['Get_Weather', 'Search_DB'].forEach((workflowName) => {
workflowPage.actions.visit();
cy.createFixtureWorkflow(`Test_Subworkflow_${workflowName}.json`, workflowName);
workflowPage.actions.saveWorkflowOnButtonClick();
});
workflowPage.actions.visit();
workflowPage.actions.addInitialNodeToCanvas(EXECUTE_WORKFLOW_NODE_NAME, {
keepNdvOpen: true,
action: 'Call Another Workflow',
});
});
it('should render sub-workflows list', () => {
ndv.getters.resourceLocator('workflowId').should('be.visible');
ndv.getters.resourceLocatorInput('workflowId').click();

getVisiblePopper()
.should('have.length', 1)
.findChildByTestId('rlc-item')
.should('have.length', 2);
});

it('should show required parameter warning', () => {
ndv.getters.resourceLocator('workflowId').should('be.visible');
ndv.getters.resourceLocatorInput('workflowId').click();
ndv.getters.parameterInputIssues('workflowId').should('exist');
});

it('should filter sub-workflows list', () => {
ndv.getters.resourceLocator('workflowId').should('be.visible');
ndv.getters.resourceLocatorInput('workflowId').click();
ndv.getters.resourceLocatorSearch('workflowId').type('Weather');

getVisiblePopper()
.should('have.length', 1)
.findChildByTestId('rlc-item')
.should('have.length', 1)
.click();

ndv.getters
.resourceLocatorInput('workflowId')
.find('input')
.should('have.value', 'Get_Weather');
});

it('should render sub-workflow links correctly', () => {
ndv.getters.resourceLocator('workflowId').should('be.visible');
ndv.getters.resourceLocatorInput('workflowId').click();

getVisiblePopper().findChildByTestId('rlc-item').first().click();

ndv.getters.resourceLocatorInput('workflowId').find('a').should('exist');
cy.getByTestId('radio-button-expression').eq(1).click();
ndv.getters.resourceLocatorInput('workflowId').find('a').should('not.exist');
});

it('should switch to ID mode on expression', () => {
ndv.getters.resourceLocator('workflowId').should('be.visible');
ndv.getters.resourceLocatorInput('workflowId').click();

getVisiblePopper().findChildByTestId('rlc-item').first().click();
ndv.getters
.resourceLocatorModeSelector('workflowId')
.find('input')
.should('have.value', 'From list');
cy.getByTestId('radio-button-expression').eq(1).click();
ndv.getters
.resourceLocatorModeSelector('workflowId')
.find('input')
.should('have.value', 'By ID');
});
});
53 changes: 53 additions & 0 deletions cypress/fixtures/Test_Subworkflow_Get_Weather.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "Get Weather",
"nodes": [
{
"parameters": {},
"id": "82eed1ba-179b-4f8f-8a85-b45f0d4e5857",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1,
"position": [
560,
340
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "6ad8dc55-20f3-45af-a724-c7ecac90d338",
"name": "response",
"value": "Weather is sunny",
"type": "string"
}
]
},
"options": {}
},
"id": "8f3e00f6-fc92-4aba-817b-93d206158bda",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
780,
340
]
}
],
"pinData": {},
"connections": {
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
}
}
64 changes: 64 additions & 0 deletions cypress/fixtures/Test_Subworkflow_Search_DB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "Search DB",
"nodes": [
{
"parameters": {},
"id": "64465f9b-63de-43f9-8d90-b5b2eb7a2dc7",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1,
"position": [
640,
380
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "6ad8dc55-20f3-45af-a724-c7ecac90d338",
"name": "response",
"value": "10 results found",
"type": "string"
}
]
},
"options": {}
},
"id": "b580fd2b-00c8-4a52-8acb-024f204c0947",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
860,
380
]
}
],
"pinData": {},
"connections": {
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "6026f7a4-f5dc-4c27-9f83-3a02fc6e33ae",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"id": "BFFhCdBZmNSkx4qf",
"tags": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
INodeType,
INodeTypeDescription,
SupplyData,
INodeParameterResourceLocator,
} from 'n8n-workflow';

import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers';
Expand Down Expand Up @@ -41,7 +42,7 @@ export class RetrieverWorkflow implements INodeType {
name: 'retrieverWorkflow',
icon: 'fa:box-open',
group: ['transform'],
version: 1,
version: [1, 1.1],
description: 'Use an n8n Workflow as Retriever',
defaults: {
name: 'Workflow Retriever',
Expand Down Expand Up @@ -105,12 +106,26 @@ export class RetrieverWorkflow implements INodeType {
displayOptions: {
show: {
source: ['database'],
'@version': [{ _cnd: { eq: 1 } }],
},
},
default: '',
required: true,
description: 'The workflow to execute',
},
{
displayName: 'Workflow',
name: 'workflowId',
type: 'workflowSelector',
displayOptions: {
show: {
source: ['database'],
'@version': [{ _cnd: { gte: 1.1 } }],
},
},
default: '',
required: true,
},

// ----------------------------------
// source:parameter
Expand Down Expand Up @@ -301,11 +316,21 @@ export class RetrieverWorkflow implements INodeType {

const workflowInfo: IExecuteWorkflowInfo = {};
if (source === 'database') {
// Read workflow from database
workflowInfo.id = this.executeFunctions.getNodeParameter(
'workflowId',
itemIndex,
) as string;
const nodeVersion = this.executeFunctions.getNode().typeVersion;
if (nodeVersion === 1) {
workflowInfo.id = this.executeFunctions.getNodeParameter(
'workflowId',
itemIndex,
) as string;
} else {
const { value } = this.executeFunctions.getNodeParameter(
'workflowId',
itemIndex,
{},
) as INodeParameterResourceLocator;
workflowInfo.id = value as string;
}

baseMetadata.workflowId = workflowInfo.id;
} else if (source === 'parameter') {
// Read workflow from parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
SupplyData,
ExecutionError,
IDataObject,
INodeParameterResourceLocator,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow';
import type { SetField, SetNodeOptions } from 'n8n-nodes-base/dist/nodes/Set/v2/helpers/interfaces';
Expand All @@ -32,7 +33,7 @@ export class ToolWorkflow implements INodeType {
name: 'toolWorkflow',
icon: 'fa:network-wired',
group: ['transform'],
version: [1, 1.1],
version: [1, 1.1, 1.2],
description: 'Uses another n8n workflow as a tool. Allows packaging any n8n node(s) as a tool.',
defaults: {
name: 'Call n8n Workflow Tool',
Expand Down Expand Up @@ -142,6 +143,7 @@ export class ToolWorkflow implements INodeType {
displayOptions: {
show: {
source: ['database'],
'@version': [{ _cnd: { lte: 1.1 } }],
},
},
default: '',
Expand All @@ -150,6 +152,20 @@ export class ToolWorkflow implements INodeType {
hint: 'Can be found in the URL of the workflow',
},

{
displayName: 'Workflow',
name: 'workflowId',
type: 'workflowSelector',
displayOptions: {
show: {
source: ['database'],
'@version': [{ _cnd: { gte: 1.2 } }],
},
},
default: '',
required: true,
},

// ----------------------------------
// source:parameter
// ----------------------------------
Expand Down Expand Up @@ -368,7 +384,17 @@ export class ToolWorkflow implements INodeType {
const workflowInfo: IExecuteWorkflowInfo = {};
if (source === 'database') {
// Read workflow from database
workflowInfo.id = this.getNodeParameter('workflowId', itemIndex) as string;
const nodeVersion = this.getNode().typeVersion;
if (nodeVersion <= 1.1) {
workflowInfo.id = this.getNodeParameter('workflowId', itemIndex) as string;
} else {
const { value } = this.getNodeParameter(
'workflowId',
itemIndex,
{},
) as INodeParameterResourceLocator;
workflowInfo.id = value as string;
}
} else if (source === 'parameter') {
// Read workflow from parameter
const workflowJson = this.getNodeParameter('workflowJson', itemIndex) as string;
Expand Down
23 changes: 21 additions & 2 deletions packages/editor-ui/src/components/ParameterInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<div class="parameter-input ignore-key-press" :style="parameterInputWrapperStyle">
<ResourceLocator
v-if="isResourceLocatorParameter"
v-if="parameter.type === 'resourceLocator'"
ref="resourceLocator"
:parameter="parameter"
:model-value="modelValueResourceLocator"
Expand All @@ -36,6 +36,25 @@
@blur="onBlur"
@drop="onResourceLocatorDrop"
/>
<WorkflowSelectorParameterInput
v-else-if="parameter.type === 'workflowSelector'"
ref="resourceLocator"
:parameter="parameter"
:model-value="modelValueResourceLocator"
:dependent-parameters-values="dependentParametersValues"
:display-title="displayTitle"
:expression-display-value="expressionDisplayValue"
:expression-computed-value="expressionEvaluated"
:is-value-expression="isModelValueExpression"
:expression-edit-dialog-visible="expressionEditDialogVisible"
:path="path"
:parameter-issues="getIssues"
@update:model-value="valueChanged"
@modal-opener-click="openExpressionEditorModal"
@focus="setFocus"
@blur="onBlur"
@drop="onResourceLocatorDrop"
/>
<ExpressionParameterInput
v-else-if="isModelValueExpression || forceShowExpression"
ref="inputField"
Expand Down Expand Up @@ -939,7 +958,7 @@ const shortPath = computed<string>(() => {
});
const isResourceLocatorParameter = computed<boolean>(() => {
return props.parameter.type === 'resourceLocator';
return props.parameter.type === 'resourceLocator' || props.parameter.type === 'workflowSelector';
});
const isSecretParameter = computed<boolean>(() => {
Expand Down
Loading

0 comments on commit 84e54be

Please sign in to comment.