diff options
Diffstat (limited to 'addons/website/static/src/js/backend')
| -rw-r--r-- | addons/website/static/src/js/backend/button.js | 51 | ||||
| -rw-r--r-- | addons/website/static/src/js/backend/dashboard.js | 603 |
2 files changed, 654 insertions, 0 deletions
diff --git a/addons/website/static/src/js/backend/button.js b/addons/website/static/src/js/backend/button.js new file mode 100644 index 00000000000..5033d9a690c --- /dev/null +++ b/addons/website/static/src/js/backend/button.js @@ -0,0 +1,51 @@ +odoo.define('website.backend', function (require) { +"use strict"; + +var AbstractField = require('web.AbstractField'); +var core = require('web.core'); +var field_registry = require('web.field_registry'); + +var _t = core._t; + +var WidgetWebsiteButton = AbstractField.extend({ + template: 'WidgetWebsiteButton', + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + isSet: function () { + return true; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _render: function () { + this._super.apply(this, arguments); + + var $value = this.$('.o_value'); + + if (this.value === true) { + $value.html(_t('Published')) + .removeClass('text-danger') + .addClass('text-success'); + } else { + $value.html(_t('Unpublished')) + .removeClass('text-success') + .addClass('text-danger'); + } + }, +}); + +field_registry.add('website_button', WidgetWebsiteButton); + +}); diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js new file mode 100644 index 00000000000..16b1281f222 --- /dev/null +++ b/addons/website/static/src/js/backend/dashboard.js @@ -0,0 +1,603 @@ +odoo.define('website.backendDashboard', 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", + 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', + }, + + 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 = []; + }, + + willStart: function() { + var self = this; + return this._super().then(function() { + return $.when( + 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'); + }); + }, + + fetch_data: function() { + var self = this; + return ajax.jsonRpc('/website/fetch_dashboard_data', 'call', { + '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(), + }).done(function(result) { + self.data = result; + self.dashboards_data = result.dashboards; + self.currency_id = result.currency_id; + self.groups = result.groups; + }); + }, + + 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 = $('<div>').addClass('col-md-12'); + 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() { + 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 = $('<div>'); + 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 = $('<div>').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 = $('<div>').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 = $('<div>').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: + '<div class="ActiveUsers">' + + 'Active Users: <b class="ActiveUsers-value"></b>' + + '</div>' + + }); + }, + + // Utility functions + addLoader: function(selector) { + var loader = '<span class="fa fa-3x fa-spin fa-spinner fa-pulse"/>'; + selector.html("<div class='o_loader'>" + loader + "</div>"); + }, + 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; + +}); |
