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): Use pinned data to resolve expressions in unexecuted nodes #9693

Merged
merged 31 commits into from
Jun 27, 2024

Conversation

ivov
Copy link
Contributor

@ivov ivov commented Jun 11, 2024

  • Ensure that expressions always resolve to a defined value based on pinned data, even when node is not executed.
  • Ensure pinned data only affects manual executions in expressions resolving

Before
Screenshot 2024-06-18 at 16 44 48

After
Screenshot 2024-06-18 at 16 44 11

https://linear.app/n8n/issue/ADO-2111

@n8n-assistant n8n-assistant bot added core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team labels Jun 11, 2024
* master:
  fix(editor): Add back credential type icon (no-changelog) (#9704)
  feat(editor): Add canvas edge toolbar hover show/hide support (no-changelog) (#9699)
  ci: Fix custom docker builds (no-changelog) (#9702)
  test: Fix e2e for projects missing instance owner (no-changelog) (#9703)
  ci: Refactor e2e tests to be less flaky (no-changelog) (#9695)
  feat(editor): Add move resources option to workflows and credentials on (#9654)
  fix: Introduce `HooksService` (#8962)
  fix(editor): Improve large data warning in input/output panel (#9671)
  ci(editor): Enforce type-safety in @n8n/chat builds as well (no-changelog) (#9685)
  fix(editor): Un-skip workflow save test (no-changelog) (#9698)
  refactor(core): Remove more dead code from event bus (no-changelog) (#9697)
  ci: Remove unused WaitTracker mocking (no-changelog) (#9694)
  feat: Update NPS Value Survey (#9638)
  refactor(core): Remove event bus channel (no-changelog) (#9663)
  refactor(core): Remove event bus helpers (no-changelog) (#9690)
@MiloradFilipovic MiloradFilipovic self-assigned this Jun 12, 2024
@mutdmour mutdmour marked this pull request as ready for review June 18, 2024 15:42
Comment on lines -352 to -385
const workflowsStore = useWorkflowsStore();

if (workflowsStore.shouldReplaceInputDataWithPinData) {
const parentPinData = parentNode.reduce<INodeExecutionData[]>((acc, parentNodeName, index) => {
const pinData = workflowsStore.pinDataByNodeName(parentNodeName);

if (pinData) {
acc.push({
json: pinData[0],
pairedItem: {
item: index,
input: 1,
},
});
}

return acc;
}, []);

if (parentPinData.length > 0) {
if (connectionInputData && connectionInputData.length > 0) {
parentPinData.forEach((parentPinDataEntry) => {
connectionInputData![0].json = {
...connectionInputData![0].json,
...parentPinDataEntry.json,
};
});
} else {
connectionInputData = parentPinData;
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleaning this up, not necessary since WorkflowProxy now supports pinned data directly

@@ -0,0 +1,106 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example workflow to test this

@mutdmour mutdmour changed the title fix(editor): Account for pin data in expressions fix(editor): Use pin data to resolve expressions Jun 18, 2024
@mutdmour mutdmour changed the title fix(editor): Use pin data to resolve expressions fix(editor): Use pin data to resolve expressions in unexecuted nodes Jun 18, 2024
@mutdmour mutdmour changed the title fix(editor): Use pin data to resolve expressions in unexecuted nodes fix(editor): Use pinned data to resolve expressions in unexecuted nodes Jun 19, 2024
Copy link
Contributor Author

@ivov ivov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this! A bit spent today, will continue with fresh eyes tomorrow.

Comment on lines 250 to 253
}: { nodeName?: string; branchIndex?: number; runIndex?: number; shortSyntax?: boolean } = {}) {
if (nodeName === undefined) {
return this.connectionInputData;
}
Copy link
Contributor Author

@ivov ivov Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. When is nodeName not defined? I'm looking at all four callsites for getNodeExecutionOrPinnedData and all of them have nodeName as string. Is this first check needed?
  2. The original is setting defaults for some of the args - should we do that as well here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. removed
  2. getNodeExecutionData does the same. So I have left it for that.

Comment on lines 3 to 14
export function getPinDataIfManualExecution(
workflow: Workflow,
nodeName: string,
mode: WorkflowExecuteMode,
): INodeExecutionData[] | undefined {
const pinData = workflow.getPinDataOfNode(nodeName);
if (pinData && mode === 'manual') {
return pinData;
}

return undefined;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Can we do less work by checking for mode before getting pin data?
  2. Any reason not to make this a method of Workflow? We're trying to move away from *Helpers everywhere.
  3. Would it make sense to make the mode check part of the existing getPinDataOfNode? Pin data should only be relevant for manual executions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. done
  2. I am not a fan of having to use that and this everywhere. Makes it harder to test and refactor. As well as understand what and when a value is set. That's why I prefer pure functions here.
  3. I could see this being used without a manual execution, for example to validate or render data. Though it's not used currently as so. I would keep as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The point about that is valid, but I don't see how moving this method out of Workflow helps there. Can we please move this method into Workflow? 🙏🏽

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We have to start somewhere breaking this apart. I don't want to balloon this PR with refactoring at the same time. I would opt to keep it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn - Workflow is very much convoluted, but I think having more -Helper files is not the way. In the end, this is not worth blocking over and we can revisit when we make an effort to simplify this class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's deal with this another time.
We've started writing tests and refactoring Workflow, and it's possible that a lot of the code in here could be moved into the frontend, and we can then simplify this class.

@@ -349,39 +348,6 @@ function connectionInputData(
}
}

const workflowsStore = useWorkflowsStore();

if (workflowsStore.shouldReplaceInputDataWithPinData) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see another use of shouldReplaceInputDataWithPinData in useWorkflowHelpers - that we cannot simplify also?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked that. That was actually necessary to set the input data for $input and $json to resolve for example.

@@ -360,7 +360,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
function getCurrentWorkflow(copyData?: boolean): Workflow {
const nodes = getNodes();
const connections = allConnections.value;
const cacheKey = JSON.stringify({ nodes, connections });
const cacheKey = JSON.stringify({ nodes, connections, pinData: pinnedWorkflowData.value });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Max pin data size I think defaults to 12 MB and can be customized to be even higher. getCurrentWorkflow seems to be called from multiple spots.

Wondering - Could we have performance issues from frequently stringifying a very large JS object? Is there a limit to how long a key can be?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no limit. I don't have a quick solution here. But removing this caching is much worse for performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole method is performance nightmare, and one of the leading culprits behind UI performance issues. Adding pinData isn't going to make it terribly worse than it already is.

That said, we need to make the Workflow class updatable, and update the properties in it, and get rid of this caching code. We can't just keep adding more properties in here.

At some point we need to own this code across packages, and fix it properly.
I've re-opened the ticket to fix this.

packages/workflow/src/WorkflowDataProxy.ts Outdated Show resolved Hide resolved
packages/workflow/src/WorkflowDataProxy.ts Outdated Show resolved Hide resolved
packages/workflow/src/WorkflowDataProxy.ts Outdated Show resolved Hide resolved
packages/workflow/src/WorkflowDataProxy.ts Outdated Show resolved Hide resolved
packages/workflow/test/WorkflowDataProxy.test.ts Outdated Show resolved Hide resolved
mutdmour
mutdmour previously approved these changes Jun 25, 2024
Copy link
Contributor

⚠️ Some Cypress E2E specs are failing, please fix them before merging

Copy link

cypress bot commented Jun 25, 2024

3 flaky tests on run #5670 ↗︎

0 397 0 0 Flakiness 3

Details:

🌳 🖥️ browsers:node18.12.0-chrome107 🤖 ivov 🗃️ e2e/*
Project: n8n Commit: a55ed16355
Status: Passed Duration: 05:01 💡
Started: Jun 25, 2024 3:08 PM Ended: Jun 25, 2024 3:13 PM
Flakiness  5-ndv.cy.ts • 2 flaky tests

View Output Video

Test Artifacts
NDV > should not retrieve remote options when required params throw errors Screenshots Video
NDV > Stop listening for trigger event from NDV Screenshots Video
Flakiness  10-undo-redo.cy.ts • 1 flaky test

View Output Video

Test Artifacts
Undo/Redo > should undo/redo adding connected nodes Test Replay Screenshots Video

Review all test suite changes for PR #9693 ↗︎

Copy link
Contributor

✅ All Cypress E2E specs passed

@ivov
Copy link
Contributor Author

ivov commented Jun 27, 2024

@mutdmour Thank you for fixing this. Since I "authored" this PR my review doesn't count for CI, so feel free to merge.

@mutdmour mutdmour merged commit 6cb3072 into master Jun 27, 2024
30 checks passed
@mutdmour mutdmour deleted the ado-2111 branch June 27, 2024 08:49
@mutdmour
Copy link
Contributor

Thank you @ivov for the review

@github-actions github-actions bot mentioned this pull request Jun 27, 2024
@janober
Copy link
Member

janober commented Jun 27, 2024

Got released with n8n@1.48.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team Released
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants