-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
feat: Synchronize deletions when pulling from source control #12170
feat: Synchronize deletions when pulling from source control #12170
Conversation
53630b1
to
002d0b6
Compare
002d0b6
to
b398a56
Compare
Codecov ReportAttention: Patch coverage is 📢 Thoughts on this report? Let us know! |
…-moving-between-projects
@@ -110,7 +111,7 @@ export class Reset extends BaseCommand { | |||
} | |||
|
|||
for (const credential of ownedCredentials) { | |||
await Container.get(CredentialsService).delete(credential); | |||
await Container.get(CredentialsRepository).remove(credential); |
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 change means that we now do not run the credentials.delete
hook.
I don't know if that is what we want or not. Just want to confirm that this change is intentional.
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 wasn't intentional.
I changed it back to calling CredentialsService.delete
and passing the owner. That way it's in line with deleting the workflow above it:
n8n/packages/cli/src/commands/ldap/reset.ts
Lines 108 to 114 in 09115e0
for (const { workflowId } of ownedSharedWorkflows) { | |
await Container.get(WorkflowService).delete(owner, workflowId); | |
} | |
for (const credential of ownedCredentials) { | |
await Container.get(CredentialsService).delete(owner, credential.id); | |
} |
async delete(user: User, credentialId: string) { | ||
await this.externalHooks.run('credentials.delete', [credentialId]); | ||
|
||
const credential = await this.sharedCredentialsRepository.findCredentialForUser( |
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.
The check for if a user has access to a credential belongs in the controller (or in a middleware) IMO.
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.
Oh, I've moved that to the services as much as possible with RBAC, because
- it's more reusable: every controller that wants to get, update or delete a resource does not need access to the repositories to first get that resource and then pass it into a service function, although that would make service functions easier to test
- it's safer: e.g. you can call
credService.remove(user, ids)
from any place and be certain that it will check if the user is allowed to do that action or not, otherwise we put the onus of not creating security flows on the caller, not the callee - transaction handling is easier, the controller does not need to create a transaction, use repositories to get the resources that the user can use in that context, then pass this to one or multiple service functions passing the transaction context as well
- at least workflows currently work like that
n8n/packages/cli/src/workflows/workflow.service.ts
Lines 275 to 282 in 1f5d8c2
/** * Deletes a workflow and returns it. * * If the workflow is active this will deactivate the workflow. * If the user does not have the permissions to delete the workflow this does * nothing and returns void. */ async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
That said, I can change it, if we already have a consensus about this.
n8n Run #8845
Run Properties:
|
Project |
n8n
|
Branch Review |
pay-2304-spike-sync-deleting-workflows-and-moving-between-projects
|
Run status |
Passed #8845
|
Run duration | 04m 49s |
Commit |
9b9c452aa6: 🌳 🖥️ browsers:node18.12.0-chrome107 🤖 despairblue 🗃️ e2e/*
|
Committer | r00gm |
View all properties for this run ↗︎ |
Test results | |
---|---|
Failures |
0
|
Flaky |
1
|
Pending |
0
|
Skipped |
0
|
Passing |
489
|
View all changes introduced in this branch ↗︎ |
✅ All Cypress E2E specs passed |
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'm not familiar enough with a lot of this code, but the parts I do understand, look fine to me. but left a comment about the deletion queries.
async deleteWorkflowsNotInWorkfolder(user: User, candidates: SourceControlledFile[]) { | ||
for (const candidate of candidates) { | ||
await this.workflowService.delete(user, candidate.id); | ||
} | ||
} | ||
|
||
async deleteCredentialsNotInWorkfolder(user: User, candidates: SourceControlledFile[]) { | ||
for (const candidate of candidates) { | ||
await this.credentialsService.delete(user, candidate.id); | ||
} | ||
} | ||
|
||
async deleteVariablesNotInWorkfolder(candidates: SourceControlledFile[]) { | ||
for (const candidate of candidates) { | ||
await this.variablesService.delete(candidate.id); | ||
} | ||
} | ||
|
||
async deleteTagsNotInWorkfolder(candidates: SourceControlledFile[]) { | ||
for (const candidate of candidates) { | ||
await this.tagService.delete(candidate.id); | ||
} | ||
} | ||
|
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.
these currently fire multiple read and then delete queries. maybe we should add a deleteMany
method in all these services, and use that to call a single delete query for each resource type.
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 can do that. Honestly I avoided this because I don't think this is a hot path and being correct was more important to me than being efficient and those functions will always work and there is no chance that they get out of sync with the deleteMany
and vice versa.
What I could do is create a deleteMany
and implement the delete
using that, similar to this: 307ede1
(#12329)
If you approve I'll do that, let's just agree on what we want before we start to work on it.
✅ All Cypress E2E specs passed |
Summary
This PR changes how pulling changes from the git repo works. If workflows, credentials, tags or variables exist locally, but not in the remote, they will be deleted locally.
That is to keep production environment clean by keeping them more tightly in sync with the staging environments.
This PR contains the BE and FE changes, make sure to review them respectively.
Related Linear tickets, Github issues, and Community forum posts
PAY-2304 BE
PAY-2428 FE
Review / Merge checklist
PR Labeled withrelease/backport
(if the PR is an urgent fix that needs to be backported)