Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions mail_gateway/models/discuss_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,74 @@ class DiscussChannel(models.Model):

def _to_store(self, store: Store, fields):
res = super()._to_store(store, fields)
for record in self.filtered("gateway_id"):
if not record:
continue
gw_channels = self.filtered("gateway_id")
if not gw_channels:
return res
# sudo: res.partner.gateway.channel - technical lookup by token, done
# once for the whole recordset (this runs for every channel on load)
partner_by_key = {}
for gw_channel in (
self.env["res.partner.gateway.channel"]
.sudo()
.search(
[
("gateway_id", "in", gw_channels.gateway_id.ids),
(
"gateway_token",
"in",
[t for t in gw_channels.mapped("gateway_channel_token") if t],
),
]
)
):
partner_by_key.setdefault(
(gw_channel.gateway_id.id, gw_channel.gateway_token),
gw_channel.partner_id,
)
for record in gw_channels:
guests = record.channel_member_ids.guest_id
partner = partner_by_key.get(
(record.gateway_id.id, record.gateway_channel_token),
self.env["res.partner"],
)
store.add_model_values(
self._name,
{
"id": record.id,
"gateway": record.gateway_id._gateway_info(),
"gateway_id": record.id,
"gateway_partner_id": partner.id,
"gateway_guest_id": guests.id if len(guests) == 1 else False,
},
)
return res

def _get_gateway_partner(self):
"""Partner linked to the gateway token of the channel, if any."""
self.ensure_one()
if not self.gateway_id or not self.gateway_channel_token:
return self.env["res.partner"]
# sudo: res.partner.gateway.channel - technical lookup by token
return (
self.env["res.partner.gateway.channel"]
.sudo()
.search(
[
("gateway_id", "=", self.gateway_id.id),
("gateway_token", "=", self.gateway_channel_token),
],
limit=1,
)
.partner_id
)

def _get_gateway_guest(self):
"""Guest of the channel when there is exactly one (sender not yet
linked to a partner), used by the "New Partner" action."""
self.ensure_one()
guests = self.channel_member_ids.guest_id
return guests if len(guests) == 1 else self.env["mail.guest"]

def _generate_avatar_gateway(self):
# We will use this function to set a default avatar on each module
return False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Message} from "@mail/core/common/message_model";
import {patch} from "@web/core/utils/patch";
import {stateToUrl} from "@web/core/browser/router";
import {router} from "@web/core/browser/router";
import {url} from "@web/core/utils/urls";

patch(Message.prototype, {
Expand All @@ -13,7 +13,7 @@ patch(Message.prototype, {
return super.resUrl;
}
return url(
stateToUrl({
router.stateToUrl({
model: this.gateway_thread_data.model,
resId: this.gateway_thread_data.id,
})
Expand Down
118 changes: 43 additions & 75 deletions mail_gateway/static/src/core/common/thread_actions.esm.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,65 @@
import {_t} from "@web/core/l10n/translation";
import {threadActionsRegistry} from "@mail/core/common/thread_actions";

/*
* Conditions are evaluated on every render: they must stay synchronous and free
* of side effects. Starting an RPC and re-rendering the owner from here keeps
* the Discuss fiber from completing (no messages are shown). The data they need
* (gateway partner and guest) comes with the channel store data.
*/
function isVisibleChannel(owner, thread) {
return (
thread?.model === "discuss.channel" &&
(!owner.props.chatWindow || owner.props.chatWindow.isOpen)
);
}

function getProfilePartnerId(thread) {
if (thread.channel_type === "gateway") {
return thread.gateway_partner_id || false;
}
return thread.correspondent?.partner_id?.id || false;
}

threadActionsRegistry
.add("open-gw-new-partner", {
condition({owner, thread}) {
if (
thread?.model !== "discuss.channel" ||
(owner.props.chatWindow && !owner.props.chatWindow.isOpen)
) {
return false;
}
if (thread._guestChecked) {
return Boolean(thread._cachedGuestId);
}
const orm = owner.env.services.orm;
(async () => {
const members = await orm.silent.searchRead(
"discuss.channel.member",
[["channel_id", "=", thread.id]],
["guest_id"]
);
const guestMembers = members.filter((m) => m.guest_id);
if (guestMembers.length === 1) {
thread._cachedGuestId = guestMembers[0].guest_id;
} else {
thread._cachedGuestId = null;
}
thread._guestChecked = true;
owner.render?.();
})();
return false;
},
condition: ({owner, thread}) =>
isVisibleChannel(owner, thread) && Boolean(thread.gateway_guest_id),
icon: "fa fa-fw fa-address-book",
name: _t("New Partner"),
async open({owner, thread}) {
const guestId = thread._cachedGuestId;
if (!guestId) return;
await owner.env.services.action.doAction({
type: "ir.actions.act_window",
res_model: "mail.guest.manage",
context: {default_guest_id: guestId[0]},
views: [[false, "form"]],
target: "new",
});
const guestId = thread.gateway_guest_id;
if (!guestId) {
return;
}
await owner.env.services.action.doAction(
{
type: "ir.actions.act_window",
res_model: "mail.guest.manage",
context: {default_guest_id: guestId},
views: [[false, "form"]],
target: "new",
},
{
// Refresh the gateway partner/guest once the guest is managed
onClose: () =>
thread.store.fetchStoreData("discuss.channel", [thread.id]),
}
);
},
iconLarge: "fa fa-fw fa-lg fa-address-book",
sequence: 18,
})
.add("open-gw-profile", {
condition({owner, thread}) {
if (
thread?.model !== "discuss.channel" ||
(owner.props.chatWindow && !owner.props.chatWindow.isOpen)
) {
return false;
}
if (thread._partnerIdChecked) {
return Boolean(thread._cachedPartnerId);
}
const orm = owner.env.services.orm;
(async () => {
const channelData = await orm.silent.searchRead(
"discuss.channel",
[["id", "=", thread.id]],
["gateway_channel_token"]
);
if (channelData[0]?.gateway_channel_token) {
const gatewayChannel = await orm.silent.searchRead(
"res.partner.gateway.channel",
[["gateway_token", "=", channelData[0].gateway_channel_token]],
["partner_id"]
);
thread._cachedPartnerId = Array.isArray(
gatewayChannel[0]?.partner_id
)
? gatewayChannel[0].partner_id[0]
: gatewayChannel[0]?.partner_id;
} else {
thread._cachedPartnerId = thread.correspondent?.persona?.id;
}
thread._partnerIdChecked = true;
owner.render?.();
})();
return false;
},
condition: ({owner, thread}) =>
isVisibleChannel(owner, thread) && Boolean(getProfilePartnerId(thread)),
icon: "fa fa-fw fa-user-circle-o",
name: _t("Open Contact"),
async open({owner, thread}) {
const partnerId = thread._cachedPartnerId;
await owner.env.services.action.doAction({
type: "ir.actions.act_window",
res_model: "res.partner",
res_id: partnerId,
res_id: getProfilePartnerId(thread),
views: [[false, "form"]],
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ patch(Thread, {
_insert(data) {
const thread = super._insert(...arguments);
if (thread.channel_type === "gateway") {
assignIn(thread, data, ["anonymous_name", "gateway"]);
assignIn(thread, data, [
"anonymous_name",
"gateway",
"gateway_partner_id",
"gateway_guest_id",
]);
}
return thread;
},
Expand Down
33 changes: 33 additions & 0 deletions mail_gateway/tests/test_mail_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,39 @@ def test_discuss_channel_to_store(self):
["gateway_id", "gateway_token", "gateway_message_ids", "channel_type"],
)

def test_discuss_channel_to_store_gateway_partner(self):
impl = self.env["mail.gateway.abstract"]
channel = impl._get_channel(
self.gateway, "ch-partner-tok", {}, force_create=True
)
self.assertFalse(channel._get_gateway_partner())
self.env["res.partner.gateway.channel"].create(
{
"partner_id": self.partner.id,
"gateway_id": self.gateway.id,
"gateway_token": "ch-partner-tok",
}
)
self.assertEqual(channel._get_gateway_partner(), self.partner)
store = Store()
channel._to_store(store, ["channel_type"])
values = [
value
for value in store.get_result()["discuss.channel"]
if value["id"] == channel.id
]
self.assertTrue(
any(value.get("gateway_partner_id") == self.partner.id for value in values)
)

def test_get_gateway_guest(self):
impl = self.env["mail.gateway.abstract"]
channel = impl._get_channel(self.gateway, "ch-guest-tok", {}, force_create=True)
self.assertFalse(channel._get_gateway_guest())
guest = self.env["mail.guest"].create({"name": "GW Guest"})
channel._add_members(guests=guest)
self.assertEqual(channel._get_gateway_guest(), guest)

# ── mail.thread additional coverage ───────────────────────────────────────

def test_get_notify_valid_parameters(self):
Expand Down
Loading