flectra.define('website.backend.dashboard', function (require) { 'use strict'; var ajax = require('web.ajax'); var ControlPanelMixin = require('web.ControlPanelMixin'); var core = require('web.core'); var Dialog = require('web.Dialog'); var field_utils = require('web.field_utils'); var session = require('web.session'); var web_client = require('web.web_client'); var Widget = require('web.Widget'); var local_storage = require('web.local_storage'); var _t = core._t; var QWeb = core.qweb; var Dashboard = Widget.extend(ControlPanelMixin, { template: 'website.WebsiteDashboardMain', cssLibs: [ '/web/static/lib/nvd3/nv.d3.css' ], jsLibs: [ '/web/static/lib/nvd3/d3.v3.js', '/web/static/lib/nvd3/nv.d3.js', '/web/static/src/js/libs/nvd3.js' ], events: { 'click .js_link_analytics_settings': 'on_link_analytics_settings', 'click .o_dashboard_action': 'on_dashboard_action', 'click .o_dashboard_action_form': 'on_dashboard_action_form', 'click .o_dashboard_hide_panel': 'on_dashboard_hide_panel', 'click li.js_website_deshboard': 'js_website_deshboard', }, init: function(parent, context) { this._super(parent, context); this.date_range = 'week'; // possible values : 'week', 'month', year' this.date_from = moment().subtract(1, 'week'); this.date_to = moment(); this.hidden_apps = JSON.parse(local_storage.getItem('website_dashboard_hidden_app_ids') || '[]'); this.dashboards_templates = ['website.dashboard_visits']; this.graphs = []; this.is_bound = $.Deferred(); this.dashboards_header = ['website.dashboard_header']; }, willStart: function() { var self = this; return $.when(ajax.loadLibs(this), this._super()).then(function() { return self.fetch_data(); }); }, start: function() { var self = this; return this._super().then(function() { self.update_cp(); self.render_dashboards(); self.render_graphs(); self.$el.parent().addClass('oe_background_grey'); self.bind_menu(); }); }, /** * Fetches dashboard data */ fetch_data: function(website_id=null) { var self = this; return this._rpc({ route: '/website/fetch_dashboard_data', params: { date_from: this.date_from.year()+'-'+(this.date_from.month()+1)+'-'+this.date_from.date(), date_to: this.date_to.year()+'-'+(this.date_to.month()+1)+'-'+this.date_to.date(), 'website_id': website_id, }, }).done(function(result) { self.website_ids = result.website_ids; self.website = result.website; self.current_website = result.current_website; self.data = result; self.dashboards_data = result.dashboards; self.currency_id = result.currency_id; self.groups = result.groups; }); }, js_website_deshboard: function(ev){ ev.preventDefault(); var self = this; $.when(this.fetch_data($(ev.target).data('website_id'))).then(function() { self.$('.o_website_dashboard_content').empty(); self.$('.o_dashboard_common').remove(); self.render_dashboards(); self.render_dashboards_header(); self.render_graphs(); }); }, render_dashboards_header: function() { var self = this; _.each(this.dashboards_header, function(template) { self.$('.o_website_dashboard_content').prepend(QWeb.render(template, {widget: self})); }); }, bind_menu: function() { var self = this; var lazyreflow = _.debounce(this.reflow.bind(this), 200); core.bus.on('resize', this, function() { if ($(window).width() < 768 ) { lazyreflow('all_outside'); } else { lazyreflow(); } }); core.bus.trigger('resize'); this.is_bound.resolve(); }, reflow: function(behavior) { var self = this; var $more_container = this.$('#website_more_container').hide(); var $more = this.$('#website_more'); $more.children('li').insertBefore($more_container); if (behavior === 'all_outside') { // Show list of menu items self.$el.show(); this.$el.find('li').show(); $more_container.hide(); return; } var $toplevel_items = this.$el.find('li').not($more_container).hide(); self.$el.show(); $toplevel_items.each(function() { var remaining_space = self.$el.find('div.navbar-collapse.collapse').width() - $more_container.outerWidth(); self.$el.find('div.navbar-collapse.collapse ul.website_tab :visible').each (function() { if($(this).parent("ul").length){ remaining_space -= $(this).width(); } }); if ($(this).width() >= remaining_space) { return false; } $(this).show(); }); $more.append($toplevel_items.filter(':hidden').show()); $more_container.toggle(!!$more.children().length); var $toplevel = self.$el.children("ul.website_tab li:visible"); if ($toplevel.length === 1) { $toplevel.hide(); } }, on_link_analytics_settings: function(ev) { ev.preventDefault(); var self = this; var dialog = new Dialog(this, { size: 'medium', title: _t('Google Analytics'), $content: QWeb.render('website.ga_dialog_content', { ga_key: this.dashboards_data.visits.ga_client_id, ga_analytics_key: this.dashboards_data.visits.ga_analytics_key, }), buttons: [ { text: _t("Save"), classes: 'btn-primary', close: true, click: function() { var ga_client_id = dialog.$el.find('input[name="ga_client_id"]').val(); var ga_analytics_key = dialog.$el.find('input[name="ga_analytics_key"]').val(); self.on_save_ga_client_id(ga_client_id, ga_analytics_key); }, }, { text: _t("Cancel"), close: true, }, ], }).open(); }, on_save_ga_client_id: function(ga_client_id, ga_analytics_key) { var self = this; return this._rpc({ route: '/website/dashboard/set_ga_data', params: { 'ga_client_id': ga_client_id, 'ga_analytics_key': ga_analytics_key, }, }).then(function (result) { if (result.error) { self.do_warn(result.error.title, result.error.message); return; } self.on_date_range_button('week'); }); }, render_dashboards: function() { var self = this; _.each(this.dashboards_templates, function(template) { self.$('.o_website_dashboard_content').append(QWeb.render(template, {widget: self})); }); }, render_graph: function(div_to_display, chart_values) { this.$(div_to_display).empty(); var self = this; nv.addGraph(function() { var chart = nv.models.lineChart() .x(function(d) { return self.getDate(d); }) .y(function(d) { return self.getValue(d); }) .forceY([0]); chart .useInteractiveGuideline(true) .showLegend(false) .showYAxis(true) .showXAxis(true); var tick_values = self.getPrunedTickValues(chart_values[0].values, 5); chart.xAxis .tickFormat(function(d) { return d3.time.format("%m/%d/%y")(new Date(d)); }) .tickValues(_.map(tick_values, function(d) { return self.getDate(d); })) .rotateLabels(-45); chart.yAxis .tickFormat(d3.format('.02f')); var svg = d3.select(div_to_display) .append("svg"); svg .attr("height", '24em') .datum(chart_values) .call(chart); nv.utils.windowResize(chart.update); return chart; }); }, render_graphs: function() { var self = this; _.each(this.graphs, function(e) { if (self.groups[e.group]) { self.render_graph('#o_graph_' + e.name, self.dashboards_data[e.name].graph); } }); this.render_graph_analytics(this.dashboards_data.visits.ga_client_id); }, render_graph_analytics: function(client_id) { if (!this.dashboards_data.visits || !this.dashboards_data.visits.ga_client_id) { return; } this.load_analytics_api(); var $analytics_components = this.$('.js_analytics_components'); this.addLoader($analytics_components); var self = this; gapi.analytics.ready(function() { $analytics_components.empty(); // 1. Authorize component var $analytics_auth = $('
').addClass('col-md-12'); window.onOriginError = function () { $analytics_components.find('.js_unauthorized_message').remove(); self.display_unauthorized_message($analytics_components, 'not_initialized'); }; gapi.analytics.auth.authorize({ container: $analytics_auth[0], clientid: client_id }); $analytics_auth.appendTo($analytics_components); self.handle_analytics_auth($analytics_components); gapi.analytics.auth.on('signIn', function() { delete window.onOriginError; self.handle_analytics_auth($analytics_components); }); }); }, on_date_range_button: function(date_range) { if (date_range === 'week') { this.date_range = 'week'; this.date_from = moment().subtract(1, 'weeks'); } else if (date_range === 'month') { this.date_range = 'month'; this.date_from = moment().subtract(1, 'months'); } else if (date_range === 'year') { this.date_range = 'year'; this.date_from = moment().subtract(1, 'years'); } else { console.log('Unknown date range. Choose between [week, month, year]'); return; } var self = this; $.when(this.fetch_data()).then(function() { self.$('.o_website_dashboard_content').empty(); self.render_dashboards(); self.render_graphs(); }); }, on_reverse_breadcrumb: function() { web_client.do_push_state({}); this.update_cp(); }, on_dashboard_action: function (ev) { ev.preventDefault(); var $action = $(ev.currentTarget); var additional_context = {}; if (this.date_range === 'week') { additional_context = {search_default_week: true}; } else if (this.date_range === 'month') { additional_context = {search_default_month: true}; } else if (this.date_range === 'year') { additional_context = {search_default_year: true}; } this.do_action($action.attr('name'), { additional_context: additional_context, on_reverse_breadcrumb: this.on_reverse_breadcrumb }); }, on_dashboard_action_form: function (ev) { ev.preventDefault(); var $action = $(ev.currentTarget); this.do_action({ name: $action.attr('name'), res_model: $action.data('res_model'), res_id: $action.data('res_id'), views: [[false, 'form']], type: 'ir.actions.act_window', }, { on_reverse_breadcrumb: this.on_reverse_breadcrumb }); }, on_dashboard_hide_panel: function (ev) { ev.preventDefault(); ev.stopPropagation(); var $action = $(ev.currentTarget); // update hidden module list this.hidden_apps = JSON.parse(local_storage.getItem('website_dashboard_hidden_app_ids') || '[]'); this.hidden_apps.push(JSON.parse($action.data('module_id'))); local_storage.setItem('website_dashboard_hidden_app_ids', JSON.stringify(this.hidden_apps)); // remove box $action.closest(".o_box_item").remove(); }, update_cp: function() { var self = this; if (!this.$searchview) { this.$searchview = $(QWeb.render("website.DateRangeButtons", { widget: this, })); this.$searchview.click('button.js_date_range', function(ev) { self.on_date_range_button($(ev.target).data('date')); $(this).find('button.js_date_range.active').removeClass('active'); $(ev.target).addClass('active'); }); } this.update_control_panel({ cp_content: { $searchview: this.$searchview, }, breadcrumbs: this.getParent().get_breadcrumbs(), }); }, // Loads Analytics API load_analytics_api: function() { var self = this; if (!("gapi" in window)) { (function(w,d,s,g,js,fjs){ g=w.gapi||(w.gapi={});g.analytics={q:[],ready:function(cb){this.q.push(cb);}}; js=d.createElement(s);fjs=d.getElementsByTagName(s)[0]; js.src='https://apis.google.com/js/platform.js'; fjs.parentNode.insertBefore(js,fjs);js.onload=function(){g.load('analytics');}; }(window,document,'script')); gapi.analytics.ready(function() { self.analytics_create_components(); }); } }, handle_analytics_auth: function($analytics_components) { $analytics_components.find('.js_unauthorized_message').remove(); // Check if the user is authenticated and has the right to make API calls if (!gapi.analytics.auth.getAuthResponse()) { this.display_unauthorized_message($analytics_components, 'not_connected'); } else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics') === -1) { this.display_unauthorized_message($analytics_components, 'no_right'); } else { this.make_analytics_calls($analytics_components); } }, display_unauthorized_message: function($analytics_components, reason) { $analytics_components.prepend($(QWeb.render('website.unauthorized_analytics', {reason: reason}))); }, make_analytics_calls: function($analytics_components) { // 2. ActiveUsers component var $analytics_users = $('
'); var activeUsers = new gapi.analytics.ext.ActiveUsers({ container: $analytics_users[0], pollingInterval: 10, }); $analytics_users.appendTo($analytics_components); // 3. View Selector var $analytics_view_selector = $('
').addClass('col-md-12 o_properties_selection'); var viewSelector = new gapi.analytics.ViewSelector({ container: $analytics_view_selector[0], }); viewSelector.execute(); $analytics_view_selector.appendTo($analytics_components); // 4. Chart graph var start_date = '7daysAgo'; if (this.date_range === 'month') { start_date = '30daysAgo'; } else if (this.date_range === 'year') { start_date = '365daysAgo'; } var $analytics_chart_2 = $('
').addClass('col-md-6 col-xs-12'); var breakdownChart = new gapi.analytics.googleCharts.DataChart({ query: { 'dimensions': 'ga:date', 'metrics': 'ga:sessions', 'start-date': start_date, 'end-date': 'yesterday' }, chart: { type: 'LINE', container: $analytics_chart_2[0], options: { title: 'All', width: '100%' } } }); $analytics_chart_2.appendTo($analytics_components); // 5. Chart table var $analytics_chart_1 = $('
').addClass('col-md-6 col-xs-12'); var mainChart = new gapi.analytics.googleCharts.DataChart({ query: { 'dimensions': 'ga:medium', 'metrics': 'ga:sessions', 'sort': '-ga:sessions', 'max-results': '6' }, chart: { type: 'TABLE', container: $analytics_chart_1[0], options: { width: '100%' } } }); $analytics_chart_1.appendTo($analytics_components); // Events handling & animations var table_row_listener; viewSelector.on('change', function(ids) { var options = {query: {ids: ids}}; activeUsers.set({ids: ids}).execute(); mainChart.set(options).execute(); breakdownChart.set(options).execute(); if (table_row_listener) { google.visualization.events.removeListener(table_row_listener); } }); mainChart.on('success', function(response) { var chart = response.chart; var dataTable = response.dataTable; table_row_listener = google.visualization.events.addListener(chart, 'select', function() { var options; if (chart.getSelection().length) { var row = chart.getSelection()[0].row; var medium = dataTable.getValue(row, 0); options = { query: { filters: 'ga:medium==' + medium, }, chart: { options: { title: medium, } } }; } else { options = { chart: { options: { title: 'All', } } }; delete breakdownChart.get().query.filters; } breakdownChart.set(options).execute(); }); }); // Add CSS animation to visually show the when users come and go. activeUsers.once('success', function() { var element = this.container.firstChild; var timeout; this.on('change', function(data) { element = this.container.firstChild; var animationClass = data.delta > 0 ? 'is-increasing' : 'is-decreasing'; element.className += (' ' + animationClass); clearTimeout(timeout); timeout = setTimeout(function() { element.className = element.className.replace(/ is-(increasing|decreasing)/g, ''); }, 3000); }); }); }, /* * Credits to https://github.com/googleanalytics/ga-dev-tools * This is the Active Users component that polls * the number of active users on Analytics each 5 secs */ analytics_create_components: function() { gapi.analytics.createComponent('ActiveUsers', { initialize: function() { this.activeUsers = 0; gapi.analytics.auth.once('signOut', this.handleSignOut_.bind(this)); }, execute: function() { // Stop any polling currently going on. if (this.polling_) { this.stop(); } this.render_(); // Wait until the user is authorized. if (gapi.analytics.auth.isAuthorized()) { this.pollActiveUsers_(); } else { gapi.analytics.auth.once('signIn', this.pollActiveUsers_.bind(this)); } }, stop: function() { clearTimeout(this.timeout_); this.polling_ = false; this.emit('stop', {activeUsers: this.activeUsers}); }, render_: function() { var opts = this.get(); // Render the component inside the container. this.container = typeof opts.container === 'string' ? document.getElementById(opts.container) : opts.container; this.container.innerHTML = opts.template || this.template; this.container.querySelector('b').innerHTML = this.activeUsers; }, pollActiveUsers_: function() { var options = this.get(); var pollingInterval = (options.pollingInterval || 5) * 1000; if (isNaN(pollingInterval) || pollingInterval < 5000) { throw new Error('Frequency must be 5 seconds or more.'); } this.polling_ = true; gapi.client.analytics.data.realtime .get({ids:options.ids, metrics:'rt:activeUsers'}) .then(function(response) { var result = response.result; var newValue = result.totalResults ? +result.rows[0][0] : 0; var oldValue = this.activeUsers; this.emit('success', {activeUsers: this.activeUsers}); if (newValue !== oldValue) { this.activeUsers = newValue; this.onChange_(newValue - oldValue); } if (this.polling_) { this.timeout_ = setTimeout(this.pollActiveUsers_.bind(this), pollingInterval); } }.bind(this)); }, onChange_: function(delta) { var valueContainer = this.container.querySelector('b'); if (valueContainer) { valueContainer.innerHTML = this.activeUsers; } this.emit('change', {activeUsers: this.activeUsers, delta: delta}); if (delta > 0) { this.emit('increase', {activeUsers: this.activeUsers, delta: delta}); } else { this.emit('decrease', {activeUsers: this.activeUsers, delta: delta}); } }, handleSignOut_: function() { this.stop(); gapi.analytics.auth.once('signIn', this.handleSignIn_.bind(this)); }, handleSignIn_: function() { this.pollActiveUsers_(); gapi.analytics.auth.once('signOut', this.handleSignOut_.bind(this)); }, template: '
' + 'Active Users: ' + '
' }); }, // Utility functions addLoader: function(selector) { var loader = ''; selector.html("
" + loader + "
"); }, getDate: function(d) { return new Date(d[0]); }, getValue: function(d) { return d[1]; }, getPrunedTickValues: function(ticks, nb_desired_ticks) { var nb_values = ticks.length; var keep_one_of = Math.max(1, Math.floor(nb_values / nb_desired_ticks)); return _.filter(ticks, function(d, i) { return i % keep_one_of === 0; }); }, format_number: function(value, type, digits, symbol) { if (type === 'currency') { return this.render_monetary_field(value, this.currency_id); } else { return field_utils.format[type](value || 0, {digits: digits}) + ' ' + symbol; } }, render_monetary_field: function(value, currency_id) { var currency = session.get_currency(currency_id); var formatted_value = field_utils.format.float(value || 0, {digits: currency && currency.digits}); if (currency) { if (currency.position === "after") { formatted_value += currency.symbol; } else { formatted_value = currency.symbol + formatted_value; } } return formatted_value; }, }); core.action_registry.add('backend_dashboard', Dashboard); return Dashboard; });