[FIX] mail: search for GIFs should match input

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) <aku@odoo.com>
This commit is contained in:
Didier (did)
2023-08-25 12:21:08 +02:00
committed by Alexandre Kühn
parent 0c3e66340d
commit 3bd65eadfa
4 changed files with 58 additions and 24 deletions
@@ -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<Number, TenorGif>} */
gifs: new Map(),
/** Size, in pixel, of the column. */
columnSize: 0,
},
oddGif: {
/** @type {TenorGif[]} */
gifs: [],
/** @type {Map<Number, TenorGif>} */
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;
}
@@ -79,13 +79,13 @@
<i
t-if="!store.guest"
class="position-absolute top-0 end-0 me-3 mt-3 p-2 o-bg-black bg-opacity-75 fa cursor-pointer opacity-0"
t-att-class="{ 'o-text-white fa-star-o': !isFavorite(gif), 'fa-star o-starred': isFavorite(gif) }"
t-on-click="() => this.onClickFavorite(gif)"
t-att-class="{ 'o-text-white fa-star-o': !isFavorite(gif_value), 'fa-star o-starred': isFavorite(gif_value) }"
t-on-click="() => this.onClickFavorite(gif_value)"
/>
<img
class="img-fluid"
t-att-src="gif.media_formats.tinygif.url"
t-on-click="() => this.onClickGif(gif)"
t-att-src="gif_value.media_formats.tinygif.url"
t-on-click="() => this.onClickGif(gif_value)"
loading="lazy"
alt="GIF"
/>
+13 -3
View File
@@ -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();
@@ -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");
});