From 3bd65eadfa1d0541573f4011d714614f3493f30a Mon Sep 17 00:00:00 2001 From: "Didier (did)" Date: Tue, 8 Aug 2023 13:03:29 +0000 Subject: [PATCH] [FIX] mail: search for GIFs should match input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this PR, RPC in the search GIFs form could resolve in the wrong order and display outdated result. This PR introduce the use of useSequential hook witch ensure that irrelevant RPC are canceled. closes odoo/odoo#133125 X-original-commit: 39fe84706e02ff7828d5098c9ec56aff274cba64 Signed-off-by: Alexandre Kühn (aku) --- .../discuss/gif_picker/common/gif_picker.js | 39 +++++++++++-------- .../discuss/gif_picker/common/gif_picker.xml | 8 ++-- addons/mail/static/src/utils/common/hooks.js | 16 ++++++-- .../tests/gif_picker/gif_picker_test.js | 19 +++++++++ 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/addons/mail/static/src/discuss/gif_picker/common/gif_picker.js b/addons/mail/static/src/discuss/gif_picker/common/gif_picker.js index 473dc4c8520..7ddacc4ecb7 100644 --- a/addons/mail/static/src/discuss/gif_picker/common/gif_picker.js +++ b/addons/mail/static/src/discuss/gif_picker/common/gif_picker.js @@ -2,7 +2,7 @@ import { useStore } from "@mail/core/common/messaging_hook"; import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays"; -import { useOnBottomScrolled } from "@mail/utils/common/hooks"; +import { useOnBottomScrolled, useSequential } from "@mail/utils/common/hooks"; import { markEventHandled } from "@web/core/utils/misc"; import { Component, onWillStart, useRef, useState } from "@odoo/owl"; @@ -78,6 +78,7 @@ export class GifPicker extends Component { this.orm = useService("orm"); this.store = useStore(); this.userService = useService("user"); + this.sequential = useSequential(); useAutofocus(); useOnBottomScrolled( "scroller", @@ -85,7 +86,7 @@ export class GifPicker extends Component { if (!this.state.showCategories) { this.state.loadingGif = true; if (!this.showFavorite) { - this.searchDebounced(); + this.search(); } else { this.loadFavoritesDebounced(this.offset); } @@ -109,19 +110,18 @@ export class GifPicker extends Component { loadingGif: false, loadingError: false, evenGif: { - /** @type {TenorGif[]} */ - gifs: [], + /** @type {Map} */ + gifs: new Map(), /** Size, in pixel, of the column. */ columnSize: 0, }, oddGif: { - /** @type {TenorGif[]} */ - gifs: [], + /** @type {Map} */ + gifs: new Map(), /** Size, in pixel, of the column. */ columnSize: 0, }, }); - this.searchDebounced = useDebounced(this.search, 200); this.loadFavoritesDebounced = useDebounced(this.loadFavorites, 200); onWillStart(() => { this.loadCategories(); @@ -165,7 +165,6 @@ export class GifPicker extends Component { if (!this.state.searchTerm) { return; } - this.state.loadingGif = true; try { const params = { country: this.userService.lang.slice(3, 5), @@ -175,19 +174,25 @@ export class GifPicker extends Component { if (this.next) { params.position = this.next; } - const { results, next } = await this.rpc("/discuss/gif/search", params, { - silent: true, + const res = await this.sequential(() => { + this.state.loadingGif = true; + const res = this.rpc("/discuss/gif/search", params, { + silent: true, + }); + this.state.loadingGif = false; + return res; }); - if (results) { + if (res) { + const { next, results } = res; this.next = next; for (const gif of results) { this.pushGif(gif); } + this.state.loadingError = false; } } catch { this.state.loadingError = true; } - this.state.loadingGif = false; } /** @@ -195,10 +200,10 @@ export class GifPicker extends Component { */ pushGif(gif) { if (this.state.evenGif.columnSize <= this.state.oddGif.columnSize) { - this.state.evenGif.gifs.push(gif); + this.state.evenGif.gifs.set(gif.id, gif); this.state.evenGif.columnSize += gif.media_formats.tinygif.dims[1]; } else { - this.state.oddGif.gifs.push(gif); + this.state.oddGif.gifs.set(gif.id, gif); this.state.oddGif.columnSize += gif.media_formats.tinygif.dims[1]; } } @@ -206,7 +211,7 @@ export class GifPicker extends Component { onInput() { this.clear(); this.state.loadingGif = true; - this.searchDebounced(); + this.search(); if (this.state.searchTerm) { this.closeCategories(); } else { @@ -227,9 +232,9 @@ export class GifPicker extends Component { } clear() { - this.state.evenGif.gifs = []; + this.state.evenGif.gifs.clear(); this.state.evenGif.columnSize = 0; - this.state.oddGif.gifs = []; + this.state.oddGif.gifs.clear(); this.state.oddGif.columnSize = 0; } diff --git a/addons/mail/static/src/discuss/gif_picker/common/gif_picker.xml b/addons/mail/static/src/discuss/gif_picker/common/gif_picker.xml index c32ef3c6b04..1984a72fbbd 100644 --- a/addons/mail/static/src/discuss/gif_picker/common/gif_picker.xml +++ b/addons/mail/static/src/discuss/gif_picker/common/gif_picker.xml @@ -79,13 +79,13 @@ GIF diff --git a/addons/mail/static/src/utils/common/hooks.js b/addons/mail/static/src/utils/common/hooks.js index d4b6e2e4164..4d3bdf48ee2 100644 --- a/addons/mail/static/src/utils/common/hooks.js +++ b/addons/mail/static/src/utils/common/hooks.js @@ -439,22 +439,32 @@ export function useSequential() { let inProgress = false; let nextFunction; let nextResolve; + let nextReject; async function call() { const resolve = nextResolve; + const reject = nextReject; const func = nextFunction; nextResolve = undefined; + nextReject = undefined; nextFunction = undefined; inProgress = true; - const data = await func(); + try { + const data = await func(); + resolve(data); + } catch (e) { + reject(e); + } inProgress = false; - resolve(data); if (nextFunction && nextResolve) { call(); } } return (func) => { nextResolve?.(); - const prom = new Promise((resolve) => (nextResolve = resolve)); + const prom = new Promise((resolve, reject) => { + nextResolve = resolve; + nextReject = reject; + }); nextFunction = func; if (!inProgress) { call(); diff --git a/addons/mail/static/tests/gif_picker/gif_picker_test.js b/addons/mail/static/tests/gif_picker/gif_picker_test.js index ed32b930f12..158a295753a 100644 --- a/addons/mail/static/tests/gif_picker/gif_picker_test.js +++ b/addons/mail/static/tests/gif_picker/gif_picker_test.js @@ -201,3 +201,22 @@ QUnit.test( await contains(".o-mail-Composer-footer .o-discuss-GifPicker"); } ); + +QUnit.test("Searching for a GIF with a failling RPC should display an error", async (assert) => { + const pyEnv = await startServer(); + const channelId = pyEnv["discuss.channel"].create({ name: "" }); + const { openDiscuss } = await start({ + mockRPC(route) { + if (route === "/discuss/gif/search") { + throw new Error("Rpc failed"); + } + if (route === "/discuss/gif/categories") { + return rpc.categories; + } + }, + }); + await openDiscuss(channelId); + await click("button[aria-label='GIFs']"); + await insertText("input[placeholder='Search for a gif']", "search"); + await contains(".o-discuss-GifPicker-error"); +});