Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Add missing node parameter values to AI Assistant request #10788

Merged
merged 25 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c5d38ed
fix(editor): Add missing node parameter values
MiloradFilipovic Sep 11, 2024
7ce1d38
⚡ Sending resolved expression values to assistant
MiloradFilipovic Sep 11, 2024
c5a05a5
⚡ Not sending ndv inputs if node is not connected. Adding a function …
MiloradFilipovic Sep 11, 2024
a0bdf5b
⚡ Resolving all expressions and sending the values to assistant
MiloradFilipovic Sep 11, 2024
640e2ff
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 12, 2024
990ebbf
⚡ Moving util method to `workflowHelpers` fixing types
MiloradFilipovic Sep 12, 2024
e6c447b
⚡ Improving typing
MiloradFilipovic Sep 12, 2024
b2e0750
🔨 Renaming and refactoring
MiloradFilipovic Sep 12, 2024
7532510
✅ Adding tests
MiloradFilipovic Sep 12, 2024
90a3bb3
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 12, 2024
ab7c00f
🔥 Removing unused import
MiloradFilipovic Sep 12, 2024
60132e7
✔️ Adding missing router mock
MiloradFilipovic Sep 12, 2024
5333764
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 13, 2024
dad019c
👌 Converting `resolveNodeExpressions` to pure function, using `deepCo…
MiloradFilipovic Sep 13, 2024
7fa9b4f
👌 Refactoring for more readability, adding tests
MiloradFilipovic Sep 13, 2024
5ef46b2
👕 Removing unused import
MiloradFilipovic Sep 13, 2024
e47fdd0
👕 Breaking up long lines
MiloradFilipovic Sep 13, 2024
39a455e
✅ Adding more test cases, handling missed edge case
MiloradFilipovic Sep 13, 2024
1fa12b3
🔥 Removing leftover console.log
MiloradFilipovic Sep 13, 2024
60db641
⚡ Adding support for backticks, skipping empty values, updating test …
MiloradFilipovic Sep 17, 2024
29db847
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 17, 2024
8aa94fc
⚡ Updating expression resolving logic
MiloradFilipovic Sep 17, 2024
7173ef0
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 17, 2024
621e70a
Merge branch 'master' into ai-assistant-node-data-improvements
MiloradFilipovic Sep 17, 2024
acbc00f
⚡ Resolving assistant expressions using default values
MiloradFilipovic Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
import { useTagsStore } from '@/stores/tags.store';
import { createTestWorkflow } from '@/__tests__/mocks';
import type { AssignmentCollectionValue } from 'n8n-workflow';

const getDuplicateTestWorkflow = (): IWorkflowDataUpdate => ({
name: 'Duplicate webhook test',
Expand Down Expand Up @@ -70,6 +71,161 @@
vi.clearAllMocks();
});

describe('getNodeParametersWithResolvedExpressions', () => {
it('should correctly detect and resolve expressions in a regular node ', () => {
const nodeParameters = {
curlImport: '',
method: 'GET',
url: '={{ $json.name }}',
authentication: 'none',
provideSslCertificates: false,
sendQuery: false,
sendHeaders: false,
sendBody: false,
options: {},
infoMessage: '',
};
const workflowHelpers = useWorkflowHelpers({ router });
const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters);

Check failure on line 89 in packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Replace `·` with `⏎↹↹↹↹`
expect(resolvedParameters.url).toHaveProperty('resolvedExpressionValue');
});

it('should correctly detect and resolve expressions in a node with assignments (set node) ', () => {
const nodeParameters = {
mode: 'manual',
duplicateItem: false,
assignments: {
assignments: [
{
id: '25d2d012-089b-424d-bfc6-642982a0711f',
name: 'date',
value:
"={{ DateTime.fromFormat('2023-12-12', 'dd/MM/yyyy').toISODate().plus({7, 'days' }) }}",
type: 'number',
},
],
},
includeOtherFields: false,
options: {},
};
const workflowHelpers = useWorkflowHelpers({ router });
const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters);

Check failure on line 112 in packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Replace `·` with `⏎↹↹↹↹`
expect(resolvedParameters).toHaveProperty('assignments');
const assignments = resolvedParameters.assignments as AssignmentCollectionValue;
expect(assignments).toHaveProperty('assignments');
expect(assignments.assignments[0].value).toHaveProperty('resolvedExpressionValue');
});

it('should correctly detect and resolve expressions in a node with filter component', () => {
const nodeParameters = {
mode: 'rules',
rules: {
values: [
{
conditions: {
options: {
caseSensitive: true,
leftValue: '',
typeValidation: 'strict',
version: 2,
},
conditions: [
{
leftValue: "={{ $('Edit Fields 1').item.json.name }}",
rightValue: 12,
operator: {
type: 'number',
operation: 'equals',
},
},
],
combinator: 'and',
},
renameOutput: false,
},
],
},
looseTypeValidation: false,
options: {},
};
const workflowHelpers = useWorkflowHelpers({ router });
const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(
nodeParameters,
) as typeof nodeParameters;
expect(resolvedParameters).toHaveProperty('rules');
expect(resolvedParameters.rules).toHaveProperty('values');
expect(resolvedParameters.rules.values[0].conditions.conditions[0].leftValue).toHaveProperty(
'resolvedExpressionValue',
);
});
it('should correctly detect and resolve expressions in a node with resource locator component', () => {
const nodeParameters = {
authentication: 'oAuth2',
resource: 'sheet',
operation: 'read',
documentId: {
__rl: true,
value: "={{ $('Edit Fields').item.json.document }}",
mode: 'id',
},
sheetName: {
__rl: true,
value: "={{ $('Edit Fields').item.json.sheet }}",
mode: 'id',
},
filtersUI: {},
combineFilters: 'AND',
options: {},
};
const workflowHelpers = useWorkflowHelpers({ router });
const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(
nodeParameters,
) as typeof nodeParameters;
expect(resolvedParameters.documentId.value).toHaveProperty('resolvedExpressionValue');
expect(resolvedParameters.sheetName.value).toHaveProperty('resolvedExpressionValue');
});
it('should correctly detect and resolve expressions in a node with resource mapper component', () => {
const nodeParameters = {
authentication: 'oAuth2',
resource: 'sheet',
operation: 'read',
documentId: {
__rl: true,
value: '1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc',
mode: 'list',
cachedResultName: 'Mapping sheet',
cachedResultUrl:
'https://docs.google.com/spreadsheets/d/1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc/edit?usp=drivesdk',
},
sheetName: {
__rl: true,
value: 'gid=0',
mode: 'list',
cachedResultName: 'Users',
cachedResultUrl:
'https://docs.google.com/spreadsheets/d/1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc/edit#gid=0',
},
filtersUI: {
values: [
{
lookupColumn: 'First name',
lookupValue: "={{ $('Edit Fields 1').item.json.userName }}",
},
],
},
combineFilters: 'AND',
options: {},
};
const workflowHelpers = useWorkflowHelpers({ router });
const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(
nodeParameters,
) as typeof nodeParameters;
expect(resolvedParameters.filtersUI.values[0].lookupValue).toHaveProperty(
'resolvedExpressionValue',
);
});
});

describe('saveAsNewWorkflow', () => {
it('should respect `resetWebhookUrls: false` when duplicating workflows', async () => {
const workflow = getDuplicateTestWorkflow();
Expand Down
37 changes: 37 additions & 0 deletions packages/editor-ui/src/composables/useWorkflowHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,42 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflowId, node, path, isFullPath);
}

/**
* Returns a copy of provided node parameters with added resolvedExpressionValue
* @param nodeParameters
* @returns
*/
function getNodeParametersWithResolvedExpressions(
nodeParameters: INodeParameters,
): INodeParameters {
function recurse(currentObj: INodeParameters, currentPath: string): INodeParameters {
const newObj: INodeParameters = {};
for (const key in currentObj) {
const value = currentObj[key as keyof typeof currentObj];
const path = currentPath ? `${currentPath}.${key}` : key;
if (typeof value === 'object' && value !== null) {
newObj[key] = recurse(value as INodeParameters, path);
} else if (typeof value === 'string' && String(value).startsWith('=')) {
// Resolve the expression if it is one
let resolved;
try {
resolved = resolveExpression(value, nodeParameters);
} catch (error) {
resolved = `Error in expression: "${error.message}"`;
}
newObj[key] = {
value,
resolvedExpressionValue: String(resolved),
};
} else {
newObj[key] = value;
}
}
return newObj;
}
return recurse(nodeParameters, '');
}

function resolveExpression(
expression: string,
siblingParameters: INodeParameters = {},
Expand Down Expand Up @@ -1159,5 +1195,6 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
getWorkflowProjectRole,
promptSaveUnsavedWorkflowChanges,
initState,
getNodeParametersWithResolvedExpressions,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ vi.mock('vue-router', () => ({
name: ENABLED_VIEWS[0],
}),
),
useRouter: vi.fn(),
RouterLink: vi.fn(),
}));

Expand Down
18 changes: 15 additions & 3 deletions packages/editor-ui/src/stores/assistant.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useRoute } from 'vue-router';
import { useSettingsStore } from './settings.store';
import { assert } from '@/utils/assert';
import { useWorkflowsStore } from './workflows.store';
import type { ICredentialType, INodeParameters } from 'n8n-workflow';
import type { IDataObject, ICredentialType, INodeParameters } from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import { ndvEventBus, codeNodeEditorEventBus } from '@/event-bus';
import { useNDVStore } from './ndv.store';
Expand All @@ -26,7 +26,8 @@ import {
getNodeAuthOptions,
getReferencedNodes,
getNodesSchemas,
pruneNodeProperties,
processNodeForAssistant,
isNodeReferencingInputData,
} from '@/utils/nodeTypesUtils';
import { useNodeTypesStore } from './nodeTypes.store';
import { usePostHog } from './posthog.store';
Expand Down Expand Up @@ -420,6 +421,16 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const availableAuthOptions = getNodeAuthOptions(nodeType);
authType = availableAuthOptions.find((option) => option.value === credentialInUse);
}
let nodeInputData: { inputNodeName?: string; inputData?: IDataObject } | undefined = undefined;
const ndvInput = ndvStore.ndvInputData;
if (isNodeReferencingInputData(context.node) && ndvInput?.length) {
const inputData = ndvStore.ndvInputData[0].json;
const inputNodeName = ndvStore.input.nodeName;
nodeInputData = {
inputNodeName,
inputData,
};
}
addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.analyzingError'));
openChat();

Expand All @@ -434,7 +445,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
firstName: usersStore.currentUser?.firstName ?? '',
},
error: context.error,
node: pruneNodeProperties(context.node, ['position']),
node: processNodeForAssistant(context.node, ['position']),
nodeInputData,
executionSchema: schemas,
authType,
},
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/types/assistant.types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Schema } from '@/Interface';
import type { INode, INodeParameters } from 'n8n-workflow';
import type { IDataObject, INode, INodeParameters } from 'n8n-workflow';

export namespace ChatRequest {
interface NodeExecutionSchema {
export interface NodeExecutionSchema {
nodeName: string;
schema: Schema;
}
Expand All @@ -21,6 +21,7 @@ export namespace ChatRequest {
stack?: string;
};
node: INode;
nodeInputData?: IDataObject;
}

export interface InitErrorHelper extends ErrorContext, WorkflowContext {
Expand Down
Loading
Loading