From 4f4ff204da9c68d2adab1fbcd6684ed342fb601f Mon Sep 17 00:00:00 2001 From: Thanh Dodeur Date: Fri, 30 Oct 2020 07:19:29 +0000 Subject: [PATCH] [FIX] mail: handles unlink button spam on attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, attempting to delete attachments multiple times could result in errors, especially on slow networks. This commit fixes this issue. task-2363522 closes odoo/odoo#63189 X-original-commit: 528adfb313ab3c8d4aeb3309aa7371301ab8085e Signed-off-by: Sébastien Theys (seb) Signed-off-by: Alexandre Kühn (aku) --- .../components/attachment/attachment_tests.js | 35 +++++++++++++++++++ .../src/models/attachment/attachment.js | 24 ++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/addons/mail/static/src/components/attachment/attachment_tests.js b/addons/mail/static/src/components/attachment/attachment_tests.js index b698c042825..2eb43d96f82 100644 --- a/addons/mail/static/src/components/attachment/attachment_tests.js +++ b/addons/mail/static/src/components/attachment/attachment_tests.js @@ -595,6 +595,41 @@ QUnit.test('close attachment viewer', async function (assert) { ); }); +QUnit.test('clicking on the delete attachment button multiple times should do the rpc only once', async function (assert) { + assert.expect(2); + await this.start({ + async mockRPC(route, args) { + if (args.method === "unlink" && args.model === "ir.attachment") { + assert.step('attachment_unlink'); + return; + } + return this._super(...arguments); + }, + }); + const attachment = this.env.models['mail.attachment'].create({ + filename: "test.txt", + id: 750, + mimetype: 'text/plain', + name: "test.txt", + }); + await this.createAttachmentComponent(attachment, { + detailsMode: 'hover', + }); + await afterNextRender(() => { + document.querySelector('.o_Attachment_actionUnlink').click(); + }); + + await afterNextRender(() => { + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + }); + assert.verifySteps( + ['attachment_unlink'], + "The unlink method must be called once" + ); +}); + }); }); }); diff --git a/addons/mail/static/src/models/attachment/attachment.js b/addons/mail/static/src/models/attachment/attachment.js index 03edf5abcab..dc4d59d52de 100644 --- a/addons/mail/static/src/models/attachment/attachment.js +++ b/addons/mail/static/src/models/attachment/attachment.js @@ -97,12 +97,20 @@ function factory(dependencies) { * Remove this attachment globally. */ async remove() { + if (this.isUnlinkPending) { + return; + } if (!this.isTemporary) { - await this.async(() => this.env.services.rpc({ - model: 'ir.attachment', - method: 'unlink', - args: [this.id], - }, { shadow: true })); + this.update({ isUnlinkPending: true }); + try { + await this.async(() => this.env.services.rpc({ + model: 'ir.attachment', + method: 'unlink', + args: [this.id], + }, { shadow: true })); + } finally { + this.update({ isUnlinkPending: false }); + } } else if (this.uploadingAbortController) { this.uploadingAbortController.abort(); } @@ -335,6 +343,12 @@ function factory(dependencies) { compute: '_computeIsTextFile', dependencies: ['fileType'], }), + /** + * True if an unlink RPC is pending, used to prevent multiple unlink attempts. + */ + isUnlinkPending: attr({ + default: false, + }), isViewable: attr({ compute: '_computeIsViewable', dependencies: [