-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
refactor(core): Consolidate execution lifecycle hooks even more + additional tests #12898
Conversation
Codecov ReportAttention: Patch coverage is
📢 Thoughts on this report? Let us know! |
429dc92
to
d362673
Compare
…the settings disable progress saving
d362673
to
81a01dd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot going on here, I'll come back later to continue reviewing.
}); | ||
this.logger.error('There was an error in the post-execution promise', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we log this here already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's only when not using Sentry. I can remove this, but this line is not the same as the one on the default error reporter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. Let's keep for now but I've always found it confusing that the error reporter both reports to Sentry and logs out errors. I'd expect all error logging to happen via Logger
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most hooks log like this Executing hook (hookFunctionsPush)
so we the log loses the actual hook being executed. Can we add them? e.g. Executing hook workflowExecuteAfter (hookFunctionsPush)
The hook is as important as the bundle it belongs to. Also ideally if we can log this more centrally instead of at every hook.
if (executionData.retryOf !== undefined) { | ||
fullExecutionData.retryOf = executionData.retryOf.toString(); | ||
} | ||
fullExecutionData.retryOf = executionData.retryOf ?? undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retryOf
typings are still not 100% reliable, and sometimes this is null
because the value came from typeorm.
packages/cli/src/execution-lifecycle/__tests__/save-execution-progress.test.ts
Outdated
Show resolved
Hide resolved
} as IExecutionResponse); | ||
test('should handle DB errors when updating the execution', async () => { | ||
const error = new Error('Something went wrong'); | ||
executionRepository.findSingleExecution.mockResolvedValue({} as IExecutionResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
executionRepository.findSingleExecution.mockResolvedValue({} as IExecutionResponse); | |
executionRepository.findSingleExecution.mockResolvedValue(mock<IExecutionResponse>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't get it to work because of all the deep proxies.
packages/cli/src/execution-lifecycle/__tests__/save-execution-progress.test.ts
Outdated
Show resolved
Hide resolved
}); | ||
|
||
executionRepository.findSingleExecution.mockResolvedValue({} as IExecutionResponse); | ||
test('should populate `.data` when it is missing', async () => { | ||
const fullExecutionData = {} as IExecutionResponse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const fullExecutionData = {} as IExecutionResponse; | |
const fullExecutionData = mock<IExecutionResponse>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@@ -392,7 +383,7 @@ export class WorkflowRunner { | |||
data.executionMode, | |||
executionId, | |||
data.workflowData, | |||
{ retryOf: data.retryOf ? data.retryOf.toString() : undefined }, | |||
{ retryOf: data.retryOf ?? undefined }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ retryOf: data.retryOf ?? undefined }, | |
{ retryOf: data.retryOf }, |
@@ -2284,7 +2284,7 @@ export interface IWorkflowExecutionDataProcess { | |||
executionData?: IRunExecutionData; | |||
runData?: IRunData; | |||
pinData?: IPinData; | |||
retryOf?: string; | |||
retryOf?: string | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it was string | undefined
? If null
is what the DB returns, can we transform it to undefined
when we retrieve?
Having to remember retryOf ?? undefined
disregarding the current type is an accident waiting to happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to spend more time on retryOf
in a separate PR, to make the types around this more reliable.
@@ -179,7 +177,7 @@ export class ActiveExecutions { | |||
data = this.activeExecutions[id]; | |||
returnData.push({ | |||
id, | |||
retryOf: data.executionData.retryOf, | |||
retryOf: data.executionData.retryOf ?? undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retryOf: data.executionData.retryOf ?? undefined, | |
retryOf: data.executionData.retryOf, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here.
await externalHooks.run('workflow.postExecute', [ | ||
fullRunData, | ||
this.workflowData, | ||
this.executionId, | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be wrapped in a try-catch that ignored the error. I take it now that's covered when we run the hook, i.e. we won't crash if the external hook fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no other external hook (besides n8n.stop
) is wrapped in try-catch, and I also clearly remember Jan telling me that if an external hook throws, then we throw that error. If someone has an external hook that breaks because of this, then they need to catch the error in the hook code.
External hooks aren't fire-and-forget. This is also why we await on them and run them sequentially.
type HooksSetupParameters = { | ||
saveSettings: ExecutionSaveSettings; | ||
pushRef?: string; | ||
retryOf?: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to remove this type and use the prop types directly?
Usage hints that these props might not belong together:
hookFunctionsPush
acceptssaveSettings
and does not use ithookFunctionsSaveProgress
acceptspushRef
andretryOf
and does not use themhookFunctionsSave
uses all three propshookFunctionsSaveWorker
acceptssaveSettings
and does not use itgetWorkflowHooksWorkerExecuter
inline omitssaveSettings
so we only pass it the other twogetWorkflowHooksWorkerMain
inline omitssaveSettings
so we only pass it the other two
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this code will be re-written once I convert this file to switch to DI, like I did in #12364
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shaping up nicely! Nothing to block on
|
n8n
|
Project |
n8n
|
Branch Review |
external-hooks-in-lifecycle-hooks
|
Run status |
|
Run duration | 04m 23s |
Commit |
|
Committer | कारतोफ्फेलस्क्रिप्ट™ |
View all properties for this run ↗︎ |
Test results | |
---|---|
|
0
|
|
0
|
|
5
|
|
0
|
|
433
|
View all changes introduced in this branch ↗︎ |
✅ All Cypress E2E specs passed |
Got released with |
Summary
This PR
Container.get
calls inexecution-lifecycle-hooks.ts
to prepare that file for switching to DIhookFunctionsPreExecute
withhookFunctionsSaveProgress
andhookFunctionsExternalHooks
pushRef
andretryOf
retryOf
Review / Merge checklist
release/backport
(if the PR is an urgent fix that needs to be backported)