[IMP] im_livechat: improve session history tree view

This PR improves the livechat session history list view:
- Clicking on a session opens the channel in discuss if the
user is a member of it. Opens the form view otherwise.
- Display duration of the session (last message datetime - first
 message datetime)
- Display country of the visitor

part of task-3332872

closes odoo/odoo#125899

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-06-28 12:30:53 +02:00
parent f75779ba3d
commit 9dd1bcd80e
7 changed files with 78 additions and 2 deletions
+1
View File
@@ -71,6 +71,7 @@ Help your customers with this chat, and analyse their feedback.
'im_livechat/static/src/js/colors_reset_button/*',
'im_livechat/static/src/js/im_livechat_chatbot_steps_one2many.js',
'im_livechat/static/src/js/im_livechat_chatbot_script_answers_m2m.js',
'im_livechat/static/src/views/**/*',
'im_livechat/static/src/chat_window/**/*',
'im_livechat/static/src/composer/**/*',
'im_livechat/static/src/core/**/*',
@@ -18,6 +18,7 @@ class DiscussChannel(models.Model):
anonymous_name = fields.Char('Anonymous Name')
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')], ondelete={'livechat': 'cascade'})
duration = fields.Float('Duration', compute='_compute_duration', help='Duration of the session in hours')
livechat_active = fields.Boolean('Is livechat ongoing?', help='Livechat session is active until visitor leaves the conversation.')
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel')
livechat_operator_id = fields.Many2one('res.partner', string='Operator')
@@ -28,6 +29,13 @@ class DiscussChannel(models.Model):
_sql_constraints = [('livechat_operator_id', "CHECK((channel_type = 'livechat' and livechat_operator_id is not null) or (channel_type != 'livechat'))",
'Livechat Operator ID is required for a channel of type livechat.')]
@api.depends('message_ids')
def _compute_duration(self):
for record in self:
start = record.message_ids[-1].date if record.message_ids else record.create_date
end = record.message_ids[0].date if record.message_ids else fields.Datetime.now()
record.duration = (end - start).total_seconds() / 3600
def _compute_is_chat(self):
super()._compute_is_chat()
for record in self:
@@ -0,0 +1,13 @@
/* @odoo-module */
import { DiscussChannelListController } from "@im_livechat/views/discuss_channel_list/discuss_channel_list_view_controller";
import { listView } from "@web/views/list/list_view";
import { registry } from "@web/core/registry";
const discussChannelListView = {
...listView,
Controller: DiscussChannelListController,
};
registry.category("views").add("im_livechat.discuss_channel_list", discussChannelListView);
@@ -0,0 +1,30 @@
/* @odoo-module */
import { createLocalId } from "@mail/utils/common/misc";
import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
import { ListController } from "@web/views/list/list_controller";
export class DiscussChannelListController extends ListController {
setup() {
super.setup(...arguments);
this.threadService = useService("mail.thread");
this.store = useService("mail.store");
}
async openRecord(record) {
if (!record.data.is_member) {
return super.openRecord(record);
}
const channel =
this.store.threads[createLocalId(record.resModel, record.resId)] ??
(await this.threadService.fetchChannel(record.resId));
if (!channel.is_pinned) {
this.threadService.pin(channel);
}
this.threadService.setDiscussThread(channel);
this.actionService.doAction("mail.action_discuss", {
name: _t("Discuss"),
});
}
}
@@ -28,9 +28,12 @@
<field name="name">discuss.channel.tree</field>
<field name="model">discuss.channel</field>
<field name="arch" type="xml">
<tree string="History" create="false" default_order="create_date desc">
<tree js_class="im_livechat.discuss_channel_list" string="History" create="false" default_order="create_date desc">
<field name="is_member" invisible="1"/>
<field name="create_date" string="Session Date"/>
<field name="name" string="Attendees"/>
<field name="country_id"/>
<field name="duration" widget="float_time" options="{'displaySeconds': True}"/>
<field name="message_ids" string="# Messages"/>
<field name="rating_last_image" string="Rating" widget="image" options='{"size": [20, 20]}' class="bg-view"/>
</tree>
@@ -72,6 +72,20 @@ export class ThreadService {
return thread;
}
/**
* @param {import("@mail/core/common/thread_model").Thread} thread
* @param {number} id
* @returns {Promise<Thread>}
*/
async fetchChannel(id) {
const [channelData] = await this.orm.call("discuss.channel", "channel_info", [id]);
return this.insert({
...channelData,
model: "discuss.channel",
type: channelData.channel.channel_type,
});
}
async fetchChannelMembers(thread) {
const known_member_ids = thread.channelMembers.map((channelMember) => channelMember.id);
const results = await this.rpc("/discuss/channel/members", {
@@ -439,6 +453,9 @@ export class ThreadService {
}
unpin(thread) {
if (this.store.discuss.threadLocalId === thread.localId) {
this.router.replaceState({ active_id: undefined });
}
if (thread.model !== "discuss.channel") {
return;
}
@@ -16,6 +16,7 @@ export class FloatTimeField extends Component {
...standardFieldProps,
inputType: { type: String, optional: true },
placeholder: { type: String, optional: true },
displaySeconds: { type: Boolean, optional: true },
};
static defaultProps = {
inputType: "text",
@@ -31,7 +32,9 @@ export class FloatTimeField extends Component {
}
get formattedValue() {
return formatFloatTime(this.props.record.data[this.props.name]);
return formatFloatTime(this.props.record.data[this.props.name], {
displaySeconds: this.props.displaySeconds,
});
}
}
@@ -48,6 +51,7 @@ export const floatTimeField = {
supportedTypes: ["float"],
isEmpty: () => false,
extractProps: ({ attrs, options }) => ({
displaySeconds: options.displaySeconds,
inputType: options.type,
placeholder: attrs.placeholder,
}),