diff --git a/addons/im_chat/__openerp__.py b/addons/im_chat/__openerp__.py index 4b1ba54c374..b8f5151165d 100644 --- a/addons/im_chat/__openerp__.py +++ b/addons/im_chat/__openerp__.py @@ -19,6 +19,8 @@ chat in real time. It support several chats in parallel. 'security/ir.model.access.csv', 'security/im_security.xml', 'views/im_chat.xml', + 'views/im_chat_view.xml', + 'im_chat_data.xml' ], 'depends' : ['base', 'web', 'bus'], 'qweb': ['static/src/xml/*.xml'], diff --git a/addons/im_chat/im_chat.py b/addons/im_chat/im_chat.py index 4be9dc89334..f865aed84f5 100644 --- a/addons/im_chat/im_chat.py +++ b/addons/im_chat/im_chat.py @@ -5,10 +5,11 @@ import logging import time import uuid import random - +import re import simplejson - import openerp +import cgi + from openerp.http import request from openerp.osv import osv, fields from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT @@ -133,6 +134,34 @@ class im_chat_session(osv.Model): user = self.pool['res.users'].read(cr, uid, user_id, ['name'], context=context) self.pool["im_chat.message"].post(cr, uid, uid, session.uuid, "meta", user['name'] + " joined the conversation.", context=context) + def remove_user(self, cr, uid, session_id, context=None): + """ private implementation of removing a user from a given session (and notify the other people) """ + session = self.browse(cr, openerp.SUPERUSER_ID, session_id, context=context) + # send a message to the conversation + user = self.pool['res.users'].read(cr, uid, uid, ['name'], context=context) + self.pool["im_chat.message"].post(cr, uid, uid, session.uuid, "meta", user['name'] + " left the conversation.", context=context) + # close his session state, and remove the user from session + self.update_state(cr, uid, session.uuid, 'closed', context=None) + self.write(cr, uid, [session.id], {"user_ids": [(3, uid)]}, context=context) + # notify the all the channel users and anonymous channel + notifications = [] + for channel_user_id in session.user_ids: + info = self.session_info(cr, channel_user_id.id, [session.id], context=context) + notifications.append([(cr.dbname, 'im_chat.session', channel_user_id.id), info]) + # anonymous are not notified when a new user left : cannot exec session_info as uid = None + info = self.session_info(cr, openerp.SUPERUSER_ID, [session.id], context=context) + notifications.append([session.uuid, info]) + self.pool['bus.bus'].sendmany(cr, uid, notifications) + + def quit_user(self, cr, uid, uuid, context=None): + """ action of leaving a given session """ + sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1) + for session in self.browse(cr, openerp.SUPERUSER_ID, sids, context=context): + if uid and uid in [u.id for u in session.user_ids] and len(session.user_ids) > 2: + self.remove_user(cr, uid, session.id, context=context) + return True + return False + def get_image(self, cr, uid, uuid, user_id, context=None): """ get the avatar of a user in the given session """ #default image @@ -165,6 +194,20 @@ class im_chat_message(osv.Model): 'type' : 'message', } + def _escape_keep_url(self, message): + """ escape the message and transform the url into clickable link """ + safe_message = "" + first = 0 + last = 0 + for m in re.finditer('(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?', message): + last = m.start() + safe_message += cgi.escape(message[first:last]) + safe_message += '%s' % (cgi.escape(m.group(0)), m.group(0)) + first = m.end() + last = m.end() + safe_message += cgi.escape(message[last:]) + return safe_message + def init_messages(self, cr, uid, context=None): """ get unread messages and old messages received less than AWAY_TIMER ago and the session_info for open or folded window @@ -210,7 +253,9 @@ class im_chat_message(osv.Model): session_ids = Session.search(cr, uid, [('uuid','=',uuid)], context=context) notifications = [] for session in Session.browse(cr, uid, session_ids, context=context): - # build the new message + # build and escape the new message + message_content = self._escape_keep_url(message_content) + message_content = self.pool['im_chat.shortcode'].replace_shortcode(cr, uid, message_content, context=context) vals = { "from_id": from_uid, "to_id": session.id, @@ -238,6 +283,24 @@ class im_chat_message(osv.Model): return False +class im_chat_shortcode(osv.Model): + """ Message shortcuts """ + _name = "im_chat.shortcode" + + _columns = { + 'source' : fields.char('Shortcut', required=True, select=True, help="The shortcut which must be replace in the Chat Messages"), + 'substitution' : fields.char('Substitution', required=True, select=True, help="The html code replacing the shortcut"), + 'description' : fields.char('Description'), + } + + def replace_shortcode(self, cr, uid, message, context=None): + ids = self.search(cr, uid, [], context=context) + for shortcode in self.browse(cr, uid, ids, context=context): + regex = "(?:^|\s)(%s)(?:\s|$)" % re.escape(shortcode.source) + message = re.sub(regex, " " + shortcode.substitution + " ", message) + return message + + class im_chat_presence(osv.Model): """ im_chat_presence status can be: online, away or offline. This model is a one2one, but is not attached to res_users to avoid database concurrence errors diff --git a/addons/im_chat/im_chat_data.xml b/addons/im_chat/im_chat_data.xml new file mode 100644 index 00000000000..ecacc1f60a3 --- /dev/null +++ b/addons/im_chat/im_chat_data.xml @@ -0,0 +1,87 @@ + + + + + + + :) + &#128522; + Smile + + + + :( + &#9785; + Sad + + + + :D + &#128517; + Laugh + + + + ;) + &#128521; + Wink + + + + :| + &#128528; + Speechless + + + + :p + &#128523; + Cheeky + + + + :s + &#128534; + Worried + + + + :/ + &#128527; + Smirking + + + + :O + &#128561; + Surprised + + + + :kitten + &#128569; + Kitten + + + + &lt;3 + &#9829; + Heart + + + + + :pinky + <img src='/im_chat/static/src/img/pinky.png'/> + Pinky + + + + :musti + <img src='/im_chat/static/src/img/musti.png'/> + Musti + + + + + diff --git a/addons/im_chat/security/ir.model.access.csv b/addons/im_chat/security/ir.model.access.csv index c1caaf14ce0..915c8a83a4d 100644 --- a/addons/im_chat/security/ir.model.access.csv +++ b/addons/im_chat/security/ir.model.access.csv @@ -3,7 +3,4 @@ access_im_chat_message,im_chat.message,model_im_chat_message,base.group_user,1,0 access_im_chat_session,im_chat.session,model_im_chat_session,base.group_user,1,1,1,0 access_im_chat_conversation_state,im_chat.conversation_state,model_im_chat_conversation_state,base.group_user,1,1,1,0 access_im_chat_presence,im_chat.presence,model_im_chat_presence,base.group_user,1,1,1,1 -access_im_chat_message_portal,im_chat.message,model_im_chat_message,base.group_portal,1,0,1,0 -access_im_chat_session_portal,im_chat.session,model_im_chat_session,base.group_portal,1,1,1,0 -access_im_chat_conversation_state_portal,im_chat.conversation_state,model_im_chat_conversation_state,base.group_portal,1,1,1,0 -access_im_chat_presence_portal,im_chat.presence,model_im_chat_presence,base.group_portal,1,1,1,1 \ No newline at end of file +access_im_chat_shortcode,im_chat.shortcode,model_im_chat_shortcode,base.group_user,1,1,1,1 diff --git a/addons/im_chat/static/src/css/im_common.css b/addons/im_chat/static/src/css/im_common.css index c89acb7d6f1..83dc9bb9c66 100644 --- a/addons/im_chat/static/src/css/im_common.css +++ b/addons/im_chat/static/src/css/im_common.css @@ -93,6 +93,25 @@ float: right; color: gray ; } + +/* options */ +.oe_im_chatview .oe_im_chatview_option_group{ + z-index: 10; +} +.oe_im_chatview .oe_im_chatview_option_list{ + right: 0; + left: auto; +} +.oe_im_chatview .oe_im_chatview_option_list li{ + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333; + white-space: nowrap; +} + .oe_im_chatview .oe_im_chatview_right div, .oe_im_chatview .oe_im_chatview_right button{ cursor: pointer; background: transparent; diff --git a/addons/im_chat/static/src/js/im_chat.js b/addons/im_chat/static/src/js/im_chat.js index 9962fdfebf9..0d27e5cbab4 100644 --- a/addons/im_chat/static/src/js/im_chat.js +++ b/addons/im_chat/static/src/js/im_chat.js @@ -165,6 +165,7 @@ className: "openerp_style oe_im_chatview", events: { "keydown input": "keydown", + "click .oe_im_chatview_options" : "click_options", "click .oe_im_chatview_close": "click_close", "click .oe_im_chatview_header": "click_header" }, @@ -207,6 +208,38 @@ self.$().show(); // prepare the header and the correct state self.update_session(); + + self.init_options(); + }, + init_options: function(){ + this.define_options(); + if(this.$('.oe_im_chatview_option_list li').length === 0){ + this.$('.oe_im_chatview_option_group').hide(); + } + }, + define_options: function(){ + // add here the option to put in the dropdown menu + this._add_option("Shortcuts", "option_shortcut", "fa fa-info-circle"); + this.$('.oe_im_chatview_option_list .option_shortcut').on('click', _.bind(this.click_option_shortcut, this)); + // quit the conversation + this._add_option('Quit discussion', 'im_chat_option_quit', 'fa fa-minus-square'); + this.$('.oe_im_chatview_option_list .im_chat_option_quit').on('click', this, _.bind(this.action_quit_conversation, this)); + + }, + _add_option: function(label, style_class, icon_fa_class){ + if(icon_fa_class){ + label = ' ' + label; + } + this.$('.oe_im_chatview_option_list').append('
  • '+label+'
  • '); + }, + action_quit_conversation: function(){ + var self = this; + var Session = new openerp.Model("im_chat.session"); + return Session.call("quit_user", [this.get("session").uuid]).then(function(res) { + if(! res){ + self.do_warn(_t("Warning"), _t("You are only 2 identified users. Just close the conversation to leave.")); + } + }); }, show: function(){ this.$().animate({ @@ -276,6 +309,7 @@ this.set("pending", this.get("pending") + 1); } this.insert_messages([message]); + this._go_bottom(); }, send_message: function(message, type) { var self = this; @@ -292,15 +326,22 @@ }, insert_messages: function(messages){ var self = this; + var get_anonymous_name = function(){ + var name = self.options["defaultUsername"]; + _.each(self.get('session').users, function(u){ + if(!u.id){ + name = u.name; + } + }); + return name; + }; // avoid duplicated messages messages = _.filter(messages, function(m){ return !_.contains(_.pluck(self.get("messages"), 'id'), m.id) ; }); // escape the message content and set the timezone _.map(messages, function(m){ if(!m.from_id){ - m.from_id = [false, self.options["defaultUsername"]]; + m.from_id = [false, get_anonymous_name()]; } - m.message = self.escape_keep_url(m.message); - m.message = self.smiley(m.message); m.create_date = Date.parse(m.create_date).setTimezone("UTC").toString("yyyy-MM-dd HH:mm:ss"); return m; }); @@ -355,62 +396,6 @@ this.$("input").val(""); this.send_message(mes, "message"); }, - get_smiley_list: function(){ - var kitten = jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined; - var smileys = { - ":'(": "😢", - ":O" : "😱", - "3:)": "😈", - ":)" : "😊", - ":D" : "😅", - ";)" : "😉", - ":p" : "😋", - ":(" : "☹", - ":|" : "😐", - ":/" : "😏", - "8)" : "😳", - ":s" : "😖", - ":pinky" : "", - ":musti" : "", - }; - if(kitten){ - _.extend(smileys, { - ":)" : "😺", - ":D" : "😹", - ";)" : "😼", - ":p" : "😽", - ":(" : "🙀", - ":|" : "😿", - }); - } - return smileys; - }, - smiley: function(str){ - var re_escape = function(str){ - return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); - }; - var smileys = this.get_smiley_list(); - _.each(_.keys(smileys), function(key){ - str = str.replace( new RegExp("(?:^|\\s)(" + re_escape(key) + ")(?:\\s|$)"), ' '+smileys[key]+' '); - }); - return str; - }, - escape_keep_url: function(str){ - var url_regex = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi; - var last = 0; - var txt = ""; - while (true) { - var result = url_regex.exec(str); - if (! result) - break; - txt += _.escape(str.slice(last, result.index)); - last = url_regex.lastIndex; - var url = _.escape(result[0]); - txt += '' + url + ''; - } - txt += _.escape(str.slice(last, str.length)); - return txt; - }, _go_bottom: function() { this.$(".oe_im_chatview_content").scrollTop(this.$(".oe_im_chatview_content").get(0).scrollHeight); }, @@ -420,11 +405,27 @@ focus: function() { this.$(".oe_im_chatview_input").focus(); }, - click_header: function(){ - this.update_fold_state(); + click_options: function(e){ + this.$('.oe_im_chatview_options').dropdown(); + }, + click_option_shortcut: function(){ + openerp.client.action_manager.do_action({ + type: 'ir.actions.act_window', + res_model: 'im_chat.shortcode', + view_mode: 'tree,form', + view_type: 'tree', + views: [[false, 'list'], [false, 'form']], + target: "current", + limit: 80, + }); + }, + click_header: function(event){ + var classes = event.target.className.split(' '); + if(_.contains(classes, 'oe_im_chatview_header_name') || _.contains(classes, 'oe_im_chatview_header')){ + this.update_fold_state(); + } }, click_close: function(event) { - event.stopPropagation(); this.update_fold_state('closed'); }, destroy: function() { diff --git a/addons/im_chat/static/src/xml/im_chat.xml b/addons/im_chat/static/src/xml/im_chat.xml index 0ca4fe307d2..5370635ab59 100644 --- a/addons/im_chat/static/src/xml/im_chat.xml +++ b/addons/im_chat/static/src/xml/im_chat.xml @@ -7,6 +7,14 @@ +
    + + + + +
    ×
    diff --git a/addons/im_chat/views/im_chat_view.xml b/addons/im_chat/views/im_chat_view.xml new file mode 100644 index 00000000000..e43399c4fad --- /dev/null +++ b/addons/im_chat/views/im_chat_view.xml @@ -0,0 +1,47 @@ + + + + + + Chat Shortcode + im_chat.shortcode + tree,form + +

    + Click to define a new chat shortcode. +

    + A shortcode is a keyboard shortcut. For instance, you type #gm and it will be transformed into "Good Morning". +

    +
    +
    + + + im_chat.shortcode.tree + im_chat.shortcode + + + + + + + + + + + im_chat.shortcode.form + im_chat.shortcode + +
    + + + + + + + +
    +
    +
    + +
    +
    \ No newline at end of file diff --git a/addons/im_livechat/__init__.py b/addons/im_livechat/__init__.py index e0f1b9603f9..781be8cfa8d 100644 --- a/addons/im_livechat/__init__.py +++ b/addons/im_livechat/__init__.py @@ -1 +1,2 @@ import im_livechat +import report \ No newline at end of file diff --git a/addons/im_livechat/__openerp__.py b/addons/im_livechat/__openerp__.py index 6f16c5b1872..0830333aa04 100644 --- a/addons/im_livechat/__openerp__.py +++ b/addons/im_livechat/__openerp__.py @@ -20,7 +20,9 @@ chat operators. "security/im_livechat_security.xml", "security/ir.model.access.csv", "views/im_livechat_view.xml", - "views/im_livechat.xml" + "views/im_livechat.xml", + "report/im_livechat_report.xml", + "im_livechat_data.xml" ], 'demo': [ "im_livechat_demo.xml", diff --git a/addons/im_livechat/im_livechat.py b/addons/im_livechat/im_livechat.py index 26f08761ed2..dafc2ed4c6f 100644 --- a/addons/im_livechat/im_livechat.py +++ b/addons/im_livechat/im_livechat.py @@ -23,11 +23,14 @@ import random import openerp import json import openerp.addons.im_chat.im_chat +import datetime from openerp.osv import osv, fields from openerp import tools from openerp import http from openerp.http import request +from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + class im_livechat_channel(osv.Model): _name = 'im_livechat.channel' @@ -173,6 +176,7 @@ class im_livechat_channel(osv.Model): return True class im_chat_session(osv.Model): + _inherit = 'im_chat.session' def _get_fullname(self, cr, uid, ids, fields, arg, context=None): @@ -190,8 +194,11 @@ class im_chat_session(osv.Model): _columns = { 'anonymous_name' : fields.char('Anonymous Name'), + 'create_date': fields.datetime('Create Date', required=True, select=True), 'channel_id': fields.many2one("im_livechat.channel", "Channel"), 'fullname' : fields.function(_get_fullname, type="char", string="Complete name"), + 'feedback_rating': fields.selection([('10','Good'),('5','Ok'),('1','Bad')], 'Grade', help='Feedback from user (Bad, Ok or Good)'), + 'feedback_reason': fields.text('Reason', help="Reason explaining the rating."), } def is_in_session(self, cr, uid, uuid, user_id, context=None): @@ -212,6 +219,18 @@ class im_chat_session(osv.Model): users_infos.append({'id' : False, 'name' : session.anonymous_name, 'im_status' : 'online'}) return users_infos + def quit_user(self, cr, uid, uuid, context=None): + """ action of leaving a given session """ + sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1) + for session in self.browse(cr, openerp.SUPERUSER_ID, sids, context=context): + if session.anonymous_name: + # an identified user can leave an anonymous session if there is still another idenfied user in it + if uid and uid in [u.id for u in session.user_ids] and len(session.user_ids) > 1: + self.remove_user(cr, uid, session.id, context=context) + return True + return False + else: + return super(im_chat_session, self).quit_user(cr, uid, session.id, context=context) class LiveChatController(http.Controller): @@ -249,3 +268,13 @@ class LiveChatController(http.Controller): with reg.cursor() as cr: return len(reg.get('im_livechat.channel').get_available_users(cr, uid, channel)) > 0 + + @http.route('/im_livechat/feedback', type='json', auth="none") + def feedback(self, uuid, rating, reason=None): + cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db + registry = openerp.modules.registry.RegistryManager.get(db) + Session = registry['im_chat.session'] + session_ids = Session.search(cr, uid, [('uuid','=',uuid)], context=context) + Session.write(cr, uid, session_ids, {'feedback_rating' : str(rating), 'feedback_reason' : reason}, context=context) + + diff --git a/addons/im_livechat/im_livechat_data.xml b/addons/im_livechat/im_livechat_data.xml new file mode 100644 index 00000000000..a489763a1dd --- /dev/null +++ b/addons/im_livechat/im_livechat_data.xml @@ -0,0 +1,47 @@ + + + + + + + Open Livechat Channel Menu + reload + + + + + + open + + + + + + + Sessions by day + im_livechat.report + [('nbr_speakers','>', 1)] + {'group_by': ['start_date:day', 'uuid']} + v + + + + + Last 24h stats + im_livechat.report + [('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') )] + {'group_by': ['start_date:day', 'uuid']} + + + + + Operators stats + im_livechat.report + [('nbr_speakers','>', 1), ('user_id', '!=', False)] + {'group_by': ['start_date:month', 'user_id', 'uuid']} + + + + + + \ No newline at end of file diff --git a/addons/im_livechat/report/__init__.py b/addons/im_livechat/report/__init__.py new file mode 100644 index 00000000000..974388ee165 --- /dev/null +++ b/addons/im_livechat/report/__init__.py @@ -0,0 +1 @@ +import im_livechat_report \ No newline at end of file diff --git a/addons/im_livechat/report/im_livechat_report.py b/addons/im_livechat/report/im_livechat_report.py new file mode 100644 index 00000000000..a0b2ce4c323 --- /dev/null +++ b/addons/im_livechat/report/im_livechat_report.py @@ -0,0 +1,55 @@ +from openerp.osv import fields,osv +from openerp import tools + + +class im_livechat_report(osv.Model): + """ Livechat Support Report """ + _name = "im_livechat.report" + _auto = False + _description = "Livechat Support Report" + _columns = { + 'uuid': fields.char('UUID', size=50, readonly=True), + 'start_date': fields.datetime('Start Date of session', readonly=True, help="Start date of the conversation"), + 'start_date_hour': fields.char('Hour of start Date of session', readonly=True), + 'duration': fields.float('Average duration', digits=(16,2), readonly=True, group_operator="avg", help="Duration of the conversation (in seconds)"), + 'time_in_session': fields.float('Time in session', digits=(16,2), readonly=True, group_operator="avg", help="Average time the user spend in the conversation"), + 'time_to_answer': fields.float('Time to answer', digits=(16,2), readonly=True, group_operator="avg", help="Average time to give the first answer to the visitor"), + 'nbr_messages': fields.integer('Average message', readonly=True, group_operator="avg", help="Number of message in the conversation"), + 'nbr_user_messages': fields.integer('Average of messages/user', readonly=True, group_operator="avg", help="Average number of message per user"), + 'nbr_speakers': fields.integer('# of speakers', readonly=True, group_operator="avg", help="Number of different speakers"), + 'rating': fields.float('Rating', readonly=True, group_operator="avg", help="Average Rating"), + 'user_id': fields.many2one('res.users', 'User', readonly=True), + 'session_id': fields.many2one('im_chat.session', 'Session', readonly=True), + 'channel_id': fields.many2one('im_livechat.channel', 'Channel', readonly=True), + } + _order = 'start_date, uuid' + + def init(self, cr): + # Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view ! + tools.drop_view_if_exists(cr, 'im_livechat_report') + cr.execute(""" + CREATE OR REPLACE VIEW im_livechat_report AS ( + SELECT + min(M.id) as id, + S.uuid as uuid, + S.create_date as start_date, + to_char(date_trunc('hour', S.create_date), 'YYYY-MM-DD HH24:MI:SS') as start_date_hour, + EXTRACT('epoch' from ((SELECT (max(create_date)-min(create_date)) FROM im_chat_message WHERE to_id=S.id AND from_id = U.id))) as time_in_session, + EXTRACT('epoch' from ((SELECT min(create_date) FROM im_chat_message WHERE to_id=S.id AND from_id IS NOT NULL)-(SELECT min(create_date) FROM im_chat_message WHERE to_id=S.id AND from_id IS NULL))) as time_to_answer, + EXTRACT('epoch' from (max((SELECT (max(create_date)) FROM im_chat_message WHERE to_id=S.id))-S.create_date)) as duration, + (SELECT count(distinct COALESCE(from_id, 0)) FROM im_chat_message WHERE to_id=S.id) as nbr_speakers, + (SELECT count(id) FROM im_chat_message WHERE to_id=S.id) as nbr_messages, + count(M.id) as nbr_user_messages, + CAST(S.feedback_rating AS INT) as rating, + U.id as user_id, + S.channel_id as channel_id + FROM im_chat_message M + LEFT JOIN im_chat_session S on (S.id = M.to_id) + LEFT JOIN res_users U on (U.id = M.from_id) + WHERE S.channel_id IS NOT NULL + GROUP BY U.id, M.to_id, S.id + ) + """) + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/im_livechat/report/im_livechat_report.xml b/addons/im_livechat/report/im_livechat_report.xml new file mode 100644 index 00000000000..bc8d8df8694 --- /dev/null +++ b/addons/im_livechat/report/im_livechat_report.xml @@ -0,0 +1,56 @@ + + + + + + im_livechat.report.graph + im_livechat.report + + + + + + + + + + + + im_livechat.report.search + im_livechat.report + + + + + + + + + + + + + + + + + + + + + + Livechat Support Report + im_livechat.report + form + graph + + Livechat Support Analysis allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc. + + + + + + + + + \ No newline at end of file diff --git a/addons/im_livechat/security/im_livechat_security.xml b/addons/im_livechat/security/im_livechat_security.xml index d4cd790e058..8e000aacfbd 100644 --- a/addons/im_livechat/security/im_livechat_security.xml +++ b/addons/im_livechat/security/im_livechat_security.xml @@ -32,5 +32,16 @@ + + Live Support Managers can see all session from live support + + + [('to_id.channel_id', '!=', None)] + + + + + + diff --git a/addons/im_livechat/security/ir.model.access.csv b/addons/im_livechat/security/ir.model.access.csv index 0b8be98fd90..cb6097e549a 100644 --- a/addons/im_livechat/security/ir.model.access.csv +++ b/addons/im_livechat/security/ir.model.access.csv @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_ls_chann1,im_livechat.channel,model_im_livechat_channel,,1,0,0,0 access_ls_chann2,im_livechat.channel,model_im_livechat_channel,group_im_livechat,1,1,1,0 access_ls_chann3,im_livechat.channel,model_im_livechat_channel,group_im_livechat_manager,1,1,1,1 +access_livechat_support_report,im_livechat.report,model_im_livechat_report,group_im_livechat_manager,1,1,1,1 diff --git a/addons/im_livechat/static/src/css/im_livechat.css b/addons/im_livechat/static/src/css/im_livechat.css new file mode 100644 index 00000000000..19a20483308 --- /dev/null +++ b/addons/im_livechat/static/src/css/im_livechat.css @@ -0,0 +1,45 @@ +/* feedback */ +.oe_im_chatview_feedback{ + padding: 15px; + vertical-align: middle; +} +.oe_im_chatview_feedback .oe_im_feedback_text { + color: white; + vertical-align: middle; + font-size: 14px; +} +.oe_im_chatview_feedback .oe_im_feedback_small_text { + color: white; + vertical-align: middle; + font-size: 12px; +} +.oe_im_chatview_feedback .oe_im_feedback_choices{ + height:40px; + text-align: center; + margin: 10px 0; +} +.oe_im_chatview_feedback .oe_im_feedback_choices a{ + cursor: pointer; + margin: 5px; + opacity: 0.65; +} +.oe_im_chatview_feedback .oe_im_feedback_choices a:hover{ + opacity:1; +} +.oe_im_chatview_feedback .oe_im_feedback_choices a.selected{ + opacity: 1; +} + +/* feedback reason */ +.oe_im_feedback_reason { + margin: 10px 0; +} +.oe_im_feedback_reason textarea { + width: 100%; + height: 70px; + resize: none; +} +.oe_im_feedback_btn input{ + float: right; + margin-left : 10px; +} diff --git a/addons/im_livechat/static/src/img/bad.png b/addons/im_livechat/static/src/img/bad.png new file mode 100644 index 00000000000..485a59bc3e5 Binary files /dev/null and b/addons/im_livechat/static/src/img/bad.png differ diff --git a/addons/im_livechat/static/src/img/good.png b/addons/im_livechat/static/src/img/good.png new file mode 100644 index 00000000000..9ae6cce415f Binary files /dev/null and b/addons/im_livechat/static/src/img/good.png differ diff --git a/addons/im_livechat/static/src/img/ok.png b/addons/im_livechat/static/src/img/ok.png new file mode 100644 index 00000000000..b79a83240d6 Binary files /dev/null and b/addons/im_livechat/static/src/img/ok.png differ diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index 2d665098bce..802340dcecf 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -20,6 +20,12 @@ this._super.apply(this, arguments); this.shown = true; this.loading_history = true; // since the session is kept after a refresh, anonymous can reload their history + + this.feedback = false; + this.rating = false; + }, + define_options: function(){ + // no options for anonymous user }, show: function(){ this._super.apply(this, arguments); @@ -50,10 +56,52 @@ this.set('session', session); openerp.set_cookie(im_livechat.COOKIE_NAME, JSON.stringify(session), 60*60); }, - click_close: function(e) { - this._super(e); - openerp.set_cookie(im_livechat.COOKIE_NAME, "", -1); + click_close: function(event) { + this.$('.oe_im_chatview_input').attr('disabled','disabled'); + if(!this.feedback && (this.get('messages').length > 1)){ + this.feedback = true; + this.$(".oe_im_chatview_content").html(openerp.qweb.render("support_feedback")); + this.$('.oe_im_feedback_choices a').on('click', _.bind(this.choose_feedback, this)); + this.$('.oe_im_chatview_feedback #submit').on('click', _.bind(this.submit_feedback, this)); + }else{ + if(this.rating){ + this.send_feedback(this.rating); + } + // delete cookie + openerp.set_cookie(im_livechat.COOKIE_NAME, "", -1); + this._super.apply(this, arguments); + } }, + choose_feedback: function(e){ + var self = this; + this.rating = parseInt($(e.currentTarget).data('value')); + this.$('.oe_im_feedback_choices a').removeClass('selected'); + this.$('.oe_im_feedback_choices a[data-value="'+this.rating+'"]').addClass('selected'); + }, + submit_feedback: function(e){ + var self = this; + if(this.rating){ + var reason = self.$('.oe_im_chatview_feedback textarea').val(); + if(this.rating > 1){ + this.send_feedback(this.rating, reason); + }else{ + if(reason){ + this.send_feedback(this.rating, reason); + }else{ + self.$('.oe_im_chatview_feedback textarea').css('border', 'red solid 2px'); + } + } + }else{ + this.feedback = false; + } + }, + send_feedback: function(rate, reason){ + var self = this; + openerp.session.rpc("/im_livechat/feedback", {uuid: this.get('session').uuid, rating: rate, reason : reason}).then(function(res) { + self.$(".oe_im_chatview_feedback").html($(document.createElement('div')).addClass("oe_im_feedback_text").html(_t("Thanks you for your feedback. You can close the chat window now."))); + }); + this.rating = false; + } }); // To avoid exeption when the anonymous has close his @@ -79,7 +127,7 @@ im_livechat.LiveSupport = openerp.Widget.extend({ init: function(server_url, db, channel, options) { - var self = this; + this._super(); options = options || {}; _.defaults(options, { buttonText: _t("Chat with one of our collaborators"), @@ -88,6 +136,10 @@ defaultUsername: _t("Visitor"), }); openerp.session = new openerp.Session(null, server_url, { use_cors: false }); + this.load_template(db, channel, options); + }, + load_template: function(db, channel, options){ + var self = this; // load the qweb templates var defs = []; var templates = ['/im_livechat/static/src/xml/im_livechat.xml', '/im_chat/static/src/xml/im_chat.xml']; diff --git a/addons/im_livechat/static/src/xml/im_livechat.xml b/addons/im_livechat/static/src/xml/im_livechat.xml index 1c7b00466d9..6e9d8f33520 100644 --- a/addons/im_livechat/static/src/xml/im_livechat.xml +++ b/addons/im_livechat/static/src/xml/im_livechat.xml @@ -1,7 +1,31 @@ - - - + + + + + + +
    +
    + Please take a second to rate our Livechat Support ... +
    +
    + + + +
    +
    + Click on the icon that represent the most your feeling. +
    +
    + +
    +
    + +
    +
    +
    +
    \ No newline at end of file diff --git a/addons/im_livechat/views/im_livechat.xml b/addons/im_livechat/views/im_livechat.xml index 6ff631aab8f..dd9fdc98b6e 100644 --- a/addons/im_livechat/views/im_livechat.xml +++ b/addons/im_livechat/views/im_livechat.xml @@ -119,6 +119,8 @@ + + diff --git a/addons/im_livechat/views/im_livechat_view.xml b/addons/im_livechat/views/im_livechat_view.xml index fa18f4cab16..ee23c404f44 100644 --- a/addons/im_livechat/views/im_livechat_view.xml +++ b/addons/im_livechat/views/im_livechat_view.xml @@ -3,7 +3,8 @@ - + + Live Chat Channels im_livechat.channel kanban,form @@ -23,7 +24,7 @@ - + support_channel.kanban im_livechat.channel @@ -121,43 +122,79 @@ - + + + History - im_chat.message - list - [('to_id.channel_id', '!=', None)] - {'search_default_group_by_to_id': 1} + im_chat.session + form + list,form + [('channel_id', '!=', None)] + {'group_by': ['channel_id', 'create_date:month']} - + - - im.chat.message.tree - im_chat.message - - - - - - - - - - - - im.chat.message.search - im_chat.message + + im_chat.session.search + im_chat.session - - - + - - + + + + + + + + im_chat.session.tree + im_chat.session + + + + + + + + + + + + + im_chat.session.form + im_chat.session + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +