diff options
| author | Aaron Bohy <aab@odoo.com> | 2016-04-08 14:53:34 +0200 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2016-04-21 15:21:39 +0200 |
| commit | 977db823c7c84e9edc7c8cf8384eb9cf47275b3a (patch) | |
| tree | c3312f745baf11d51f976a60d6e285480f557d4c /addons/web/static/src/js/widgets/debug_manager.js | |
| parent | e1bfd3fbda97ac7a64a585b07be161d5be606cf9 (diff) | |
[REF] web: stop duplicating web
Avoid duplicating web addon in enterprise by extracting a common basis.
Enterprise features stay in enterprise, but use that common basis.
Mainly:
- JS refactoring and linting
- Conversion of .sass into .less split into multiple files
- Templates cleaning and DOM simplification
- Re-generation of web.pot, and update of .po files
Diffstat (limited to 'addons/web/static/src/js/widgets/debug_manager.js')
| -rw-r--r-- | addons/web/static/src/js/widgets/debug_manager.js | 845 |
1 files changed, 664 insertions, 181 deletions
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js index 74a534ec40e..d95509bb465 100644 --- a/addons/web/static/src/js/widgets/debug_manager.js +++ b/addons/web/static/src/js/widgets/debug_manager.js @@ -1,6 +1,8 @@ odoo.define('web.DebugManager', function (require) { "use strict"; +var ActionManager = require('web.ActionManager'); +var common = require('web.form_common'); var core = require('web.core'); var Dialog = require('web.Dialog'); var formats = require('web.formats'); @@ -8,210 +10,691 @@ var framework = require('web.framework'); var Model = require('web.Model'); var session = require('web.session'); var SystrayMenu = require('web.SystrayMenu'); +var Tour = require('web.Tour'); var utils = require('web.utils'); var ViewManager = require('web.ViewManager'); +var WebClient = require('web.WebClient'); var Widget = require('web.Widget'); var QWeb = core.qweb; var _t = core._t; -if (core.debug) { - var DebugManager = Widget.extend({ - template: "WebClient.DebugManager", - events: { - "click .oe_debug_button": "render_dropdown", - "click .js_debug_dropdown li": "on_debug_click", - }, - start: function() { - this._super(); - this.$dropdown = this.$(".js_debug_dropdown"); - }, - /** - * Updates its attributes according to the inner_widget of the ActionManager - */ - _update: function() { - this.view_manager = odoo.__DEBUG__.services['web.web_client'].action_manager.get_inner_widget(); - if (!this.view_manager instanceof ViewManager) { return; } - this.dataset = this.view_manager.dataset; - this.active_view = this.view_manager.active_view; - if (!this.active_view) { return; } - this.view = this.active_view.controller; - return true; +var ADD = function (id) { return [4, id, false]; }, + REMOVE = function (id) { return [3, id, false]; }; +/** + * DebugManager base + general features (applicable to any context) + */ +var DebugManager = Widget.extend({ + template: "WebClient.DebugManager", + events: { + "click a[data-action]": "perform_callback", + "mouseover .o_debug_dropdowns > li:not(.open)": function(e) { + // Open other dropdowns on mouseover + var $opened = this.$('.o_debug_dropdowns > li.open'); + if($opened.length) { + $opened.removeClass('open'); + $(e.currentTarget).addClass('open').find('> a').focus(); + } }, - /** - * Renders the DebugManager dropdown - */ - render_dropdown: function() { - var self = this; + }, + init: function () { + this._super.apply(this, arguments); + // 15 fps, only actually call after sequences of queries + this._update_stats = _.throttle( + this._update_stats.bind(this), + 1000/15, {leading: false}); + this._events = null; + if (document.querySelector('meta[name=debug]')) { + this._events = []; + } + }, + start: function () { + core.bus.on('action_pushed', this, this.update); + core.bus.on('rpc:result', this, function (req, resp) { + this._debug_events(resp.debug); + }); + this.on('update-stats', this, this._update_stats); + var init; + if ((init = document.querySelector('meta[name=debug]'))) { + this._debug_events(JSON.parse(init.getAttribute('value'))); + } - // Empty the previously rendered dropdown - this.$dropdown.empty(); + this.$dropdown = this.$(".o_debug_dropdown"); + // falsy if can't write to user or couldn't find technical features + // group, otherwise features group id + this._features_group = null; + // whether group is currently enabled for current user + this._has_features = false; + // whether the current user is an administrator + this._is_admin = false; + return $.when( + new Model('res.users').call('check_access_rights', {operation: 'write', raise_exception: false}), + this.session.user_has_group('base.group_no_one'), + new Model('ir.model.data').call('xmlid_to_res_id', {xmlid: 'base.group_no_one'}), + this.session.user_has_group('base.group_system'), + this._super() + ).then(function (can_write_user, has_group_no_one, group_no_one_id, is_admin) { + this._features_group = can_write_user && group_no_one_id; + this._has_features = has_group_no_one; + this._is_admin = is_admin; + return this.update(); + }.bind(this)); + }, + leave_debug_mode: function () { + var qs = $.deparam.querystring(); + delete qs.debug; + window.location.search = '?' + $.param(qs); + }, /** + * Calls the appropriate callback when clicking on a Debug option + */ + perform_callback: function (evt) { + evt.preventDefault(); + var params = $(evt.target).data(); + var callback = params.action; - // Attempt to retrieve the inner_widget of the ActionManager - if (!this._update()) { - // Disable the button when not available - console.warn("DebugManager is not available"); - return; + if (callback && this[callback]) { + // Perform the callback corresponding to the option + this[callback](params, evt); + } else { + console.warn("No handler for ", callback); + } + }, + + _debug_events: function (events) { + if (!this._events) { return; } + if (events && events.length) { + this._events.push(events); + } + this.trigger('update-stats', this._events); + }, + requests_clear: function () { + if (!this._events) { return; } + this._events = []; + this.trigger('update-stats', this._events); + }, + _update_stats: function (rqs) { + var requests = 0, rtime = 0, queries = 0, qtime = 0; + for(var r = 0; r < rqs.length; ++r) { + for (var i = 0; i < rqs[r].length; i++) { + var event = rqs[r][i]; + var query_start, request_start; + switch (event[0]) { + case 'request-start': + request_start = event[3] * 1e3; + break; + case 'request-end': + ++requests; + rtime += (event[3] * 1e3 - request_start) | 0; + break; + case 'sql-start': + query_start = event[3] * 1e3; + break; + case 'sql-end': + ++queries; + qtime += (event[3] * 1e3 - query_start) | 0; + break; + } } + } + this.$('#debugmanager_requests_stats').text( + _.str.sprintf(_t("%d requests (%d ms) %d queries (%d ms)"), + requests, rtime, queries, qtime)); + }, + show_timelines: function () { + if (this._overlay) { + this._overlay.destroy(); + this._overlay = null; + return; + } + this._overlay = new RequestsOverlay(this); + this._overlay.appendTo(document.body); + }, - this.session.user_has_group('base.group_system').then(function(is_admin) { - // Render the dropdown and append it - var dropdown_content = QWeb.render('WebClient.DebugDropdown', { - widget: self, - active_view: self.active_view, - view: self.view, - action: self.view_manager.action, - searchview: self.view_manager.searchview, - is_admin: is_admin, - }); - $(dropdown_content).appendTo(self.$dropdown); - }); - }, - /** - * Calls the appropriate callback when clicking on a Debug option - */ - on_debug_click: function (evt) { - evt.preventDefault(); - - var params = $(evt.target).data(); - var callback = params.action; - - if (callback && this[callback]) { - // Perform the callback corresponding to the option - this[callback](params, evt); - } else { - console.warn("No debug handler for ", callback); + /** + * Update the debug manager: reinserts all "universal" controls + */ + update: function () { + this.$dropdown + .empty() + .append(QWeb.render('WebClient.DebugManager.Global', { + manager: this, + })); + return $.when(); + }, + start_tour: function() { + var dialog = new Dialog(this, { + title: 'Tours', + $content: QWeb.render('WebClient.DebugManager.ToursDialog', { + tours: Tour.tours + }), + }).open(); + + dialog.$('.o_start_tour').on('click', function(e) { + e.preventDefault(); + odoo.__DEBUG__.services['web.Tour'].run($(e.target).data('id')); + }); + }, + select_view: function () { + var self = this; + new common.SelectCreateDialog(this, { + res_model: 'ir.ui.view', + title: _t('Select a view'), + disable_multiple_selection: true, + on_selected: function (element_ids) { + new Model('ir.ui.view') + .query(['name', 'model', 'type']) + .filter([['id', '=', element_ids[0]]]) + .first() + .then(function (view) { + self.do_action({ + type: 'ir.actions.act_window', + name: view.name, + res_model: view.model, + views: [[view.id, view.type]] + }); + }); } - }, - get_metadata: function() { - var ds = this.dataset; - ds.call('get_metadata', [this.view.get_selected_ids()]).done(function(result) { - new Dialog(this, { - title: _.str.sprintf(_t("Metadata (%s)"), ds.model), - size: 'medium', - $content: QWeb.render('WebClient.DebugViewLog', { - perm : result[0], - format : formats.format_value - }) - }).open(); - }); - }, - toggle_layout_outline: function() { - this.view.rendering_engine.toggle_layout_debugging(); - }, - set_defaults: function() { - this.view.open_defaults_dialog(); - }, - perform_js_tests: function() { - this.do_action({ - name: _t("JS Tests"), - target: 'new', - type : 'ir.actions.act_url', - url: '/web/tests?mod=*' + }).open(); + }, + perform_js_tests: function () { + this.do_action({ + name: _t("JS Tests"), + target: 'new', + type: 'ir.actions.act_url', + url: '/web/tests?mod=*' + }); + }, + toggle_technical_features: function () { + if (!this._features_group) { return; } + var command = this._has_features ? REMOVE(this._features_group) : ADD(this._features_group); + new Model('res.users').call('write', [session.uid, { + groups_id: [command] + }]).then(function () { + window.location.reload(); + }); + }, + split_assets: function() { + window.location = $.param.querystring(window.location.href, 'debug=assets'); + }, +}); + +/** + * DebugManager features depending on having an action, and possibly a model + * (window action) + */ +DebugManager.include({ + /** + * Updates current action (action descriptor) on tag = action, + */ + update: function (tag, descriptor) { + if (tag === 'action') { + this._action = descriptor; + } + return this._super().then(function () { + this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.Action', { + manager: this, + action: this._action + })); + }.bind(this)); + }, + edit: function (params, evt) { + this.do_action({ + res_model: params.model, + res_id: params.id, + name: evt.target.text, + type: 'ir.actions.act_window', + views: [[false, 'form']], + view_mode: 'form', + target: 'new', + flags: {action_buttons: true, headless: true} + }); + }, + get_view_fields: function () { + var self = this; + var model = this._action.res_model; + new Model(model).call('fields_get', { + attributes: ['string', 'searchable', 'required', 'readonly', 'type', 'store', 'sortable', 'relation', 'help'] + }).done(function (fields) { + new Dialog(self, { + title: _.str.sprintf(_t("Fields of %s"), model), + $content: $(QWeb.render('WebClient.DebugManager.Action.Fields', { + fields: fields + })) + }).open(); + }); + }, + manage_filters: function () { + this.do_action({ + res_model: 'ir.filters', + name: _t('Manage Filters'), + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + context: { + search_default_my_filters: true, + search_default_model_id: this._action.res_model + } + }); + }, + edit_workflow: function () { + return this.do_action({ + res_model: 'workflow', + name: _t('Edit Workflow'), + domain: [['osv', '=', this._action.res_model]], + views: [[false, 'list'], [false, 'form'], [false, 'diagram']], + type: 'ir.actions.act_window', + view_type: 'list', + view_mode: 'list' + }); + }, + translate: function() { + var model = this._action.res_model; + new Model("ir.translation") + .call('get_technical_translations', [model]) + .then(this.do_action); + } +}); + +/** + * DebugManager features depending on having a form view or single record. + * These could theoretically be split, but for now they'll be considered one + * and the same. + */ +DebugManager.include({ + start: function () { + this._can_edit_views = false; + return $.when( + this._super(), + new Model('ir.ui.view').call( + 'check_access_rights', {operation: 'write', raise_exception: false} + ).then(function (ar) { + this._can_edit_views = ar; + }.bind(this)) + ); + }, + update: function (tag, descriptor, widget) { + switch (tag) { + case 'action': + if (!(widget instanceof ViewManager)) { + this._active_view = null; + this._view_manager = null; + break; + } + this._view_manager = widget; + widget.on('switch_mode', this, function () { + this.update('view', null, widget); }); - }, - get_view_fields: function() { - var self = this; - self.dataset.call('fields_get', [false, {}]).done(function (fields) { - var $root = $('<dl>'); - _(fields).each(function (attributes, name) { - $root.append($('<dt>').append($('<h4>').text(name))); - var $attrs = $('<dl>').appendTo($('<dd>').appendTo($root)); - _(attributes).each(function (def, name) { - if (def instanceof Object) { - def = JSON.stringify(def); - } - $attrs - .append($('<dt>').text(name)) - .append($('<dd style="white-space: pre-wrap;">').text(def)); - }); + case 'view': + this._active_view = widget.active_view; + } + + return this._super(tag, descriptor).then(function () { + this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.View', { + manager: this, + action: this._action, + view: this._active_view, + can_edit: this._can_edit_views, + searchview: this._view_manager && this._view_manager.searchview, + })); + }.bind(this)); + }, + + get_metadata: function() { + var ds = this._view_manager.dataset; + ds.call('get_metadata', [this._active_view.controller.get_selected_ids()]).done(function(result) { + new Dialog(this, { + title: _.str.sprintf(_t("Metadata (%s)"), ds.model), + size: 'medium', + $content: QWeb.render('WebClient.DebugViewLog', { + perm : result[0], + format : formats.format_value + }) + }).open(); + }); + }, + set_defaults: function() { + this._active_view.controller.open_defaults_dialog(); + }, + fvg: function() { + var dialog = new Dialog(this, { title: _t("Fields View Get") }).open(); + $('<pre>').text(utils.json_node_to_xml( + this._active_view.controller.fields_view.arch, true) + ).appendTo(dialog.$el); + }, + print_workflow: function() { + var ids = this._active_view.controller.get_selected_ids(); + framework.blockUI(); + var action = { + context: { active_ids: ids }, + report_name: "workflow.instance.graph", + datas: { + model: this._view_manager.dataset.model, + id: ids[0], + nested: true, + } + }; + this.session.get_file({ + url: '/web/report', + data: {action: JSON.stringify(action)}, + complete: framework.unblockUI + }); + }, +}); +function make_context(width, height, fn) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + // make e.layerX/e.layerY imitate e.offsetX/e.offsetY. + canvas.style.position = 'relative'; + var ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = false; + ctx.mozImageSmoothingEnabled = false; + ctx.oImageSmoothingEnabled = false; + ctx.webkitImageSmoothingEnabled = false; + fn && fn(ctx); + return ctx; +} +var RequestsOverlay = Widget.extend({ + template: 'WebClient.DebugManager.RequestsOverlay', + TRACKS: 8, + TRACK_WIDTH: 9, + events: { + mousemove: function (e) { + this.$tooltip.hide(); + } + }, + init: function () { + this._super.apply(this, arguments); + this._render = _.throttle( + this._render.bind(this), + 1000/15, {leading: false} + ); + }, + start: function () { + var _super = this._super(); + this.$tooltip = this.$('div.o_debug_tooltip'); + this.getParent().on('update-stats', this, this._render); + this._render(); + return _super; + }, + tooltip: function (text, start, end, x, y) { + // x and y are hit point with respect to the viewport. To know where + // this hit point is with respect to the overlay, subtract the offset + // between viewport and overlay, then add scroll factor of overlay + // (which isn't taken in account by the viewport). + // + // Normally the viewport overlay should sum offsets of all + // offsetParents until we reach `null` but in this case the overlay + // should have been added directly to the body, which should have an + // offset of 0. + + var top = y - this.el.offsetTop + this.el.scrollTop + 1; + var left = x - this.el.offsetLeft + this.el.scrollLeft + 1; + this.$tooltip.css({top: top, left: left}).show()[0].innerHTML = ['<p>', text, ' (', (end - start), 'ms)', '</p>'].join(''); + }, + + _render: function () { + var $summary = this.$('header'), + w = $summary[0].clientWidth, + $requests = this.$('.o_debug_requests'); + $summary.find('canvas').attr('width', w); + var tracks = document.getElementById('o_debug_requests_summary'); + + _.invoke(this.getChildren(), 'destroy'); + + var requests = this.getParent()._events; + var bounds = this._get_bounds(requests); + // horizontal scaling factor for summary + var scale = w / (bounds.high - bounds.low); + + // store end-time of "current" requests, to find out which track a + // request should go in, just look for the first track whose end-time + // is smaller than the new request's start time. + var track_ends = _(this.TRACKS).times(_.constant(-Infinity)); + + var ctx = tracks.getContext('2d'); + ctx.lineWidth = this.TRACK_WIDTH; + for (var i = 0; i < requests.length; i++) { + var request = requests[i]; + // FIXME: is it certain that events in the request are sorted by timestamp? + var rstart = Math.floor(request[0][3] * 1e3); + var rend = Math.ceil(request[request.length - 1][3] * 1e3); + // find free track for current request + for(var track=0; track < track_ends.length; ++track) { + if (track_ends[track] < rstart) { break; } + } + // FIXME: display error message of some sort? Re-render with larger area? Something? + if (track >= track_ends.length) { + console.warn("could not find an empty summary track"); + continue; + } + // set new track end + track_ends[track] = rend; + ctx.save(); + ctx.translate(Math.floor((rstart - bounds.low) * scale), track * (this.TRACK_WIDTH + 1)); + this._draw_request(request, ctx, 0, scale); + ctx.restore(); + new RequestDetails(this, request, scale).appendTo($requests); + } + }, + _draw_request: function (request, to_context, step, hscale, handle_event) { + // have one draw surface for each event type: + // * no need to alter context from one event to the next, each surface + // gets its own color for all its lifetime + // * surfaces can be blended in a specified order, which means events + // can be drawn in any order, no need to care about z-index while + // serializing events to the surfaces + var surfaces = { + request: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) { + ctx.strokeStyle = 'blue'; + ctx.fillStyle = '#88f'; + ctx.lineJoin = 'round'; + ctx.lineWidth = 1; + }), + //func: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) { + // ctx.strokeStyle = 'gray'; + // ctx.lineWidth = to_context.lineWidth; + // ctx.translate(0, initial_offset); + //}), + sql: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) { + ctx.strokeStyle = 'red'; + ctx.fillStyle = '#f88'; + ctx.lineJoin = 'round'; + ctx.lineWidth = 1; + }), + template: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) { + ctx.strokeStyle = 'green'; + ctx.fillStyle = '#8f8'; + ctx.lineJoin = 'round'; + ctx.lineWidth = 1; + }) + }; + // apply scaling manually so zooming in improves display precision + var stacks = {}, start = Math.floor(request[0][3] * 1e3 * hscale); + var event_idx = 0; + + var rect_width = to_context.lineWidth; + for (var i = 0; i < request.length; i++) { + var type, m, event = request[i]; + var tag = event[0], timestamp = Math.floor(event[3] * 1e3 * hscale) - start; + + if (m = /(\w+)-start/.exec(tag)) { + type = m[1]; + if (!(type in stacks)) { stacks[type] = []; } + handle_event && handle_event(event_idx, timestamp, event); + stacks[type].push({ + timestamp: timestamp, + idx: event_idx++ }); - new Dialog(self, { - title: _.str.sprintf(_t("Model %s fields"), self.dataset.model), - $content: $root - }).open(); - }); - }, - fvg: function() { - var dialog = new Dialog(this, { title: _t("Fields View Get") }).open(); - $('<pre>').text(utils.json_node_to_xml(this.view.fields_view.arch, true)).appendTo(dialog.$el); - }, - manage_filters: function() { - this.do_action({ - res_model: 'ir.filters', - name: _t('Manage Filters'), - views: [[false, 'list'], [false, 'form']], - type: 'ir.actions.act_window', - context: { - search_default_my_filters: true, - search_default_model_id: this.dataset.model - } - }); - }, - translate: function() { - new Model("ir.translation") - .call('get_technical_translations', [this.dataset.model]) - .then(this.do_action); + } else if (m = /(\w+)-end/.exec(tag)) { + type = m[1]; + var stack = stacks[type]; + var estart = stack.pop(), duration = Math.ceil(timestamp - estart.timestamp); + handle_event && handle_event(estart.idx, timestamp, event); + + var surface = surfaces[type]; + if (!surface) { continue; } // FIXME: support for unknown event types + + var y = step * estart.idx; + // path rectangle for the current event on the relevant surface + surface.rect(estart.timestamp + 0.5, y + 0.5, duration || 1, rect_width); + } + } + // add each layer to the main canvas + var keys = ['request', /*'func', */'template', 'sql']; + for (var j = 0; j < keys.length; ++j) { + // stroke and fill all rectangles for the relevant surface/context + var ctx = surfaces[keys[j]]; + ctx.fill(); + ctx.stroke(); + to_context.drawImage(ctx.canvas, 0, 0); + } + }, + /** + * Returns first and last events in milliseconds + * + * @param requests + * @returns {{low: number, high: number}} + * @private + */ + _get_bounds: function (requests) { + var low = +Infinity; + var high =-+Infinity; + + for (var i = 0; i < requests.length; i++) { + var request = requests[i]; + for (var j = 0; j < request.length; j++) { + var event = request[j]; + var timestamp = event[3]; + low = Math.min(low, timestamp); + high = Math.max(high, timestamp); + } + } + return {low: Math.floor(low * 1e3), high: Math.ceil(high * 1e3)}; + } +}); +var RequestDetails = Widget.extend({ + events: { + click: function () { + this._open = !this._open; + this.render(); }, - edit: function(params, evt) { - this.do_action({ - res_model : params.model, - res_id : params.id, - name: evt.target.text, - type : 'ir.actions.act_window', - view_type : 'form', - view_mode : 'form', - views : [[false, 'form']], - target : 'new', - flags : { - action_buttons : true, - headless: true, + 'mousemove canvas': function (e) { + e.stopPropagation(); + var y = e.y || e.offsetY || e.layerY; + if (!y) { return; } + var event = this._payloads[Math.floor(y / this._REQ_HEIGHT)]; + if (!event) { return; } + + this.getParent().tooltip(event.payload, event.start, event.stop, e.clientX, e.clientY); + } + }, + init: function (parent, request, scale) { + this._super.apply(this, arguments); + this._request = request; + this._open = false; + this._scale = scale; + this._REQ_HEIGHT = 20; + }, + start: function () { + this.el.style.borderBottom = '1px solid black'; + this.render(); + return this._super(); + }, + render: function () { + var request_cell_height = this._REQ_HEIGHT, TITLE_WIDTH = 200; + var request = this._request; + var req_start = request[0][3] * 1e3; + var req_duration = request[request.length - 1][3] * 1e3 - req_start; + var height = request_cell_height * (this._open ? request.length / 2 : 1); + var cell_center = request_cell_height / 2; + var ctx = make_context(210 + Math.ceil(req_duration * this._scale), height, function (ctx) { + ctx.lineWidth = cell_center; + }); + this.$el.empty().append(ctx.canvas); + var payloads = this._payloads = []; + + // lazy version: if the render is single-line (!this._open), the extra + // content will be discarded when the text canvas gets pasted onto the + // main canvas. An improvement would be to not do text rendering + // beyond the first event for "closed" requests events… then again + // that makes for more regular rendering profile? + var text_ctx = make_context(TITLE_WIDTH, height, function (ctx) { + ctx.font = '12px sans-serif'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + ctx.translate(0, cell_center); + }); + + ctx.save(); + ctx.translate(TITLE_WIDTH + 10, ((request_cell_height/4)|0)); + + this.getParent()._draw_request(request, ctx, this._open ? request_cell_height : 0, this._scale, function (idx, timestamp, event) { + if (/-start$/g.test(event[0])) { + payloads.push({ + payload: event[2], + start: timestamp, + stop: null + }); + + // we want ~200px wide, assume the average character is at + // least 4px wide => there can be *at most* 49 characters + var title = event[2]; + title = title.replace(/\s+$/, ''); + title = title.length <= 50 ? title : ('…' + title.slice(-49)); + while (text_ctx.measureText(title).width > 200) { + title = '…' + title.slice(2); } + text_ctx.fillText(title, TITLE_WIDTH, request_cell_height * idx); + } else if (/-end$/g.test(event[0])) { + payloads[idx].stop = timestamp; + } + }); + ctx.restore(); + // add the text layer to the main canvas + ctx.drawImage(text_ctx.canvas, 0, 0); + } +}); + +if (core.debug) { + SystrayMenu.Items.push(DebugManager); + + WebClient.include({ + start: function() { + var self = this; + return this._super().then(function () { + // Override push_action so that it triggers an event each time a new action is pushed + // The DebugManager listens to this event to keep itself up-to-date + var push_action = self.action_manager.push_action; + self.action_manager.push_action = function(widget, descr) { + return push_action.apply(self.action_manager, arguments).then(function () { + core.bus.trigger('action_pushed', 'action', descr, widget); + }); + }; }); }, - edit_workflow: function() { - return this.do_action({ - res_model : 'workflow', - name: _t('Edit Workflow'), - domain : [['osv', '=', this.dataset.model]], - views: [[false, 'list'], [false, 'form'], [false, 'diagram']], - type : 'ir.actions.act_window', - view_type : 'list', - view_mode : 'list' - }); - }, - print_workflow: function() { - if (this.view.get_selected_ids && this.view.get_selected_ids().length == 1) { - framework.blockUI(); - var action = { - context: { active_ids: this.view.get_selected_ids() }, - report_name: "workflow.instance.graph", - datas: { - model: this.dataset.model, - id: this.view.get_selected_ids()[0], - nested: true, - } - }; - this.session.get_file({ - url: '/web/report', - data: {action: JSON.stringify(action)}, - complete: framework.unblockUI + }); + + Dialog.include({ + open: function() { + var self = this; + + var parent = self.getParent(); + if (parent instanceof ActionManager && parent.dialog_widget) { + // Instantiate the DebugManager and insert it into the DOM once dialog is opened + this.opened(function() { + self.debug_manager = new DebugManager(self); + var $header = self.$modal.find('.modal-header'); + return self.debug_manager.prependTo($header).then(function() { + self.debug_manager.update('action', parent.dialog_widget.action, parent.dialog_widget); + }); }); - } else { - this.do_warn("Warning", "No record selected."); } - }, - split_assets: function() { - window.location = $.param.querystring(window.location.href, 'debug=assets'); - }, - leave_debug: function() { - window.location.search="?"; + + return this._super.apply(this, arguments); }, }); - - SystrayMenu.Items.push(DebugManager); - - return DebugManager; } +return DebugManager; + }); |
