[IMP] mail: user searching by most recent chat

task-2622887

closes odoo/odoo#122486

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
zel-odoo
2023-06-05 13:31:23 +02:00
committed by Alexandre Kühn
parent 81be16f275
commit ec57f8d7ff
6 changed files with 166 additions and 27 deletions
@@ -142,7 +142,52 @@ export class SuggestionService {
}
}
}
const sortFunc = (p1, p2) => {
return {
type: "Partner",
mainSuggestions: sort
? this.sortPartnerSuggestions(mainSuggestionList, cleanedSearchTerm, thread)
: mainSuggestionList,
extraSuggestions: sort
? this.sortPartnerSuggestions(extraSuggestionList, cleanedSearchTerm, thread)
: extraSuggestionList,
};
}
/**
* @param {[import("@mail/core/persona_model").Persona]} [partners]
* @param {String} [searchTerm]
* @param {import("@mail/core/thread_model").Thread} thread
* @returns {[import("@mail/core/persona_model").Persona]}
*/
sortPartnerSuggestions(partners, searchTerm = "", thread = undefined) {
const cleanedSearchTerm = cleanTerm(searchTerm);
/**
* Ordering:
* - recent chat partners
* - internal users
* - channel members
* - thread followers
* - longgest match of name, alphabetically if having same length of match
* - longgest match of email, alphabetically if having same length of match
* - id
*/
return partners.sort((p1, p2) => {
const recentChatPartnerIds = this.personaService.getRecentChatPartnerIds();
const recentChatIndex_p1 = recentChatPartnerIds.findIndex(
(partnerId) => partnerId === p1.id
);
const recentChatIndex_p2 = recentChatPartnerIds.findIndex(
(partnerId) => partnerId === p2.id
);
if (recentChatIndex_p1 !== -1 && recentChatIndex_p2 === -1) {
return -1;
} else if (recentChatIndex_p1 === -1 && recentChatIndex_p2 !== -1) {
return 1;
} else if (recentChatIndex_p1 < recentChatIndex_p2) {
return -1;
} else if (recentChatIndex_p1 > recentChatIndex_p2) {
return 1;
}
const isAInternalUser = p1.user?.isInternalUser;
const isBInternalUser = p2.user?.isInternalUser;
if (isAInternalUser && !isBInternalUser) {
@@ -151,7 +196,7 @@ export class SuggestionService {
if (!isAInternalUser && isBInternalUser) {
return 1;
}
if (thread.model === "discuss.channel") {
if (thread?.model === "discuss.channel") {
const isMember1 = thread.channelMembers.some((member) => member.persona === p1);
const isMember2 = thread.channelMembers.some((member) => member.persona === p2);
if (isMember1 && !isMember2) {
@@ -212,12 +257,7 @@ export class SuggestionService {
return 1;
}
return p1.id - p2.id;
};
return {
type: "Partner",
mainSuggestions: sort ? mainSuggestionList.sort(sortFunc) : mainSuggestionList,
extraSuggestions: sort ? extraSuggestionList.sort(sortFunc) : extraSuggestionList,
};
});
}
searchChannelSuggestions(cleanedSearchTerm, thread, sort) {
@@ -55,6 +55,30 @@ export class PersonaService {
this.store.registeredImStatusPartners?.push(persona.id);
}
}
/**
* List of known partner ids with a direct chat, ordered
* by most recent interest (1st item being the most recent)
*
* @returns {[integer]}
*/
getRecentChatPartnerIds() {
return Object.values(this.store.threads)
.filter((thread) => thread.type === "chat")
.sort((a, b) => {
if (!a.lastInterestDateTime && !b.lastInterestDateTime) {
return 0;
}
if (a.lastInterestDateTime && !b.lastInterestDateTime) {
return -1;
}
if (!a.lastInterestDateTime && b.lastInterestDateTime) {
return 1;
}
return b.lastInterestDateTime.ts - a.lastInterestDateTime.ts;
})
.map((thread) => thread.chatPartnerId);
}
}
export const personaService = {
@@ -19,6 +19,8 @@ export class ChannelInvitation extends Component {
this.notification = useService("notification");
this.threadService = useState(useService("mail.thread"));
this.personaService = useService("mail.persona");
/** @type {import("@mail/composer/suggestion_service").SuggestionService} */
this.suggestionService = useService("mail.suggestion");
this.ui = useService("ui");
this.inputRef = useRef("input");
this.searchStr = "";
@@ -56,7 +58,11 @@ export class ChannelInvitation extends Component {
});
selectablePartners.push(newPartner);
}
this.state.selectablePartners = selectablePartners;
this.state.selectablePartners = this.suggestionService.sortPartnerSuggestions(
selectablePartners,
this.searchStr,
this.props.thread
);
this.state.searchResultCount = results["count"];
}
@@ -20,6 +20,8 @@ export class ChannelSelector extends Component {
this.store = useStore();
this.threadService = useState(useService("mail.thread"));
this.personaService = useService("mail.persona");
/** @type {import("@mail/composer/suggestion_service").SuggestionService} */
this.suggestionService = useService("mail.suggestion");
this.orm = useService("orm");
this.state = useState({
value: "",
@@ -65,14 +67,16 @@ export class ChannelSelector extends Component {
10,
this.state.selectedPartners,
]);
const suggestions = results.map((data) => {
this.personaService.insert({ ...data, type: "partner" });
return {
classList: "o-mail-ChannelSelector-suggestion",
label: data.name,
partner: data,
};
});
const suggestions = this.suggestionService
.sortPartnerSuggestions(results, cleanedTerm)
.map((data) => {
this.personaService.insert({ ...data, type: "partner" });
return {
classList: "o-mail-ChannelSelector-suggestion",
label: data.name,
partner: data,
};
});
if (this.store.self.name.includes(cleanedTerm)) {
suggestions.push({
classList: "o-mail-ChannelSelector-suggestion",
+14 -10
View File
@@ -32,17 +32,21 @@ commandProviderRegistry.add("mail.partner", {
/** @type {import("@mail/core/messaging_service").Messaging} */
const messaging = env.services["mail.messaging"];
const threadService = env.services["mail.thread"];
/** @type {import("@mail/composer/suggestion_service").SuggestionService} */
const suggestionService = env.services["mail.suggestion"];
const results = await messaging.searchPartners(options.searchValue);
return results.map(function (partner) {
return {
Component: DialogCommand,
action() {
threadService.openChat({ partnerId: partner.id });
},
name: partner.name,
props: { email: partner.email },
};
});
return suggestionService
.sortPartnerSuggestions(results, options.searchValue)
.map(function (partner) {
return {
Component: DialogCommand,
action() {
threadService.openChat({ partnerId: partner.id });
},
name: partner.name,
props: { email: partner.email },
};
});
},
});
@@ -2,6 +2,7 @@
import { Composer } from "@mail/composer/composer";
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { Command } from "@mail/../tests/helpers/command";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("suggestion", {
@@ -62,3 +63,63 @@ QUnit.test(
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
}
);
QUnit.test("Sort partner suggestions by recent chats", async (assert) => {
const pyEnv = await startServer();
const [partner_1, partner_2, partner_3] = pyEnv["res.partner"].create([
{ name: "User 1" },
{ name: "User 2" },
{ name: "User 3" },
]);
pyEnv["res.users"].create([
{ partner_id: partner_1 },
{ partner_id: partner_2 },
{ partner_id: partner_3 },
]);
pyEnv["discuss.channel"].create([
{ name: "General", channel_type: "channel" },
{
channel_member_ids: [
Command.create({
last_interest_dt: "2023-01-01 00:00:00",
partner_id: pyEnv.currentPartnerId,
}),
Command.create({ partner_id: partner_1 }),
],
channel_type: "chat",
},
{
channel_member_ids: [
Command.create({
last_interest_dt: "2023-01-01 00:00:00",
partner_id: pyEnv.currentPartnerId,
}),
Command.create({ partner_id: partner_2 }),
],
channel_type: "chat",
},
{
channel_member_ids: [
Command.create({
last_interest_dt: "2023-01-01 00:00:00",
partner_id: pyEnv.currentPartnerId,
}),
Command.create({ partner_id: partner_3 }),
],
channel_type: "chat",
},
]);
const { openDiscuss } = await start();
await openDiscuss();
await click(".o-mail-DiscussCategoryItem:contains('User 2')");
await insertText(".o-mail-Composer-input", "This is a test");
await click(".o-mail-Composer-send:not(:disabled)");
assert.containsOnce($, ".o-mail-Message:contains('This is a test')");
await click(".o-mail-DiscussCategoryItem:contains('General')");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "User");
assert.containsN($, ".o-mail-Composer-suggestion", 3);
assert.strictEqual($(".o-mail-Composer-suggestion").eq(0).text(), "User 2");
assert.strictEqual($(".o-mail-Composer-suggestion").eq(1).text(), "User 1");
assert.strictEqual($(".o-mail-Composer-suggestion").eq(2).text(), "User 3");
});