summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/tools/debug_manager.js
diff options
context:
space:
mode:
authorRomain Derie <rde@odoo.com>2019-06-03 14:26:14 +0000
committerJeremy Kersten <jke@odoo.com>2019-06-05 05:56:33 +0000
commitfbe5b64eb3d962e1c828e77d848f85a8edfcdde8 (patch)
treeeafc70e76b6fc31be31d8a7acb7fb41d09569fe1 /addons/web/static/src/js/tools/debug_manager.js
parent3c37e0e1100c77a1e344f5153be756f12c3c777b (diff)
[IMP] web: rename file to prepare changes on next commit
As this file will be split in 2 parts, this will minimize diff. task-1934445 Comes with odoo/enterprise#4281 Closes #33213
Diffstat (limited to 'addons/web/static/src/js/tools/debug_manager.js')
-rw-r--r--addons/web/static/src/js/tools/debug_manager.js910
1 files changed, 0 insertions, 910 deletions
diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js
deleted file mode 100644
index 29fe1123e1f..00000000000
--- a/addons/web/static/src/js/tools/debug_manager.js
+++ /dev/null
@@ -1,910 +0,0 @@
-odoo.define('web.DebugManager', function (require) {
-"use strict";
-
-var ActionManager = require('web.ActionManager');
-var dialogs = require('web.view_dialogs');
-var startClickEverywhere = require('web.clickEverywhere');
-var config = require('web.config');
-var core = require('web.core');
-var Dialog = require('web.Dialog');
-var field_utils = require('web.field_utils');
-var session = require('web.session');
-var SystrayMenu = require('web.SystrayMenu');
-var utils = require('web.utils');
-var WebClient = require('web.WebClient');
-var Widget = require('web.Widget');
-
-var QWeb = core.qweb;
-var _t = core._t;
-
-/**
- * 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(.show)": function(e) {
- // Open other dropdowns on mouseover
- var $opened = this.$('.o_debug_dropdowns > li.show');
- if($opened.length) {
- $opened.removeClass('show');
- $(e.currentTarget).addClass('show').find('> a').focus();
- }
- },
- },
- 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('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')));
- }
-
- 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 = session.is_system;
- return Promise.resolve(
- this._rpc({
- model: 'res.users',
- method: 'check_access_rights',
- kwargs: {operation: 'write', raise_exception: false},
- }),
- session.user_has_group('base.group_no_one'),
- this._rpc({
- model: 'ir.model.data',
- method: 'xmlid_to_res_id',
- kwargs: {xmlid: 'base.group_no_one'},
- }),
- this._super()
- ).then(function (can_write_user, has_group_no_one, group_no_one_id) {
- this._features_group = can_write_user && group_no_one_id;
- this._has_features = has_group_no_one;
- return this.update();
- }.bind(this));
- },
- leave_debug_mode: function () {
- var qs = $.deparam.querystring();
- 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;
-
- 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);
- },
-
- /**
- * Update the debug manager: reinserts all "universal" controls
- */
- update: function () {
- this.$dropdown
- .empty()
- .append(QWeb.render('WebClient.DebugManager.Global', {
- manager: this,
- }));
- return Promise.resolve();
- },
- select_view: function () {
- var self = this;
- new dialogs.SelectCreateDialog(this, {
- res_model: 'ir.ui.view',
- title: _t('Select a view'),
- disable_multiple_selection: true,
- domain: [['type', '!=', 'qweb'], ['type', '!=', 'search']],
- on_selected: function (records) {
- self._rpc({
- model: 'ir.ui.view',
- method: 'search_read',
- domain: [['id', '=', records[0].id]],
- fields: ['name', 'model', 'type'],
- limit: 1,
- })
- .then(function (views) {
- var view = views[0];
- view.type = view.type === 'tree' ? 'list' : view.type; // ignore tree view
- self.do_action({
- type: 'ir.actions.act_window',
- name: view.name,
- res_model: view.model,
- views: [[view.id, view.type]]
- });
- });
- }
- }).open();
- },
- /**
- * Runs the JS (desktop) tests
- */
- perform_js_tests: function () {
- this.do_action({
- name: _t("JS Tests"),
- target: 'new',
- type: 'ir.actions.act_url',
- url: '/web/tests?mod=*'
- });
- },
- /**
- * Runs the JS mobile tests
- */
- perform_js_mobile_tests: function () {
- this.do_action({
- name: _t("JS Mobile Tests"),
- target: 'new',
- type: 'ir.actions.act_url',
- url: '/web/tests/mobile?mod=*'
- });
- },
- perform_click_everywhere_test: function () {
- var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th");
- $homeMenu.click();
- startClickEverywhere();
- },
- split_assets: function() {
- window.location = $.param.querystring(window.location.href, 'debug=assets');
- },
- tests_assets: function () {
- window.location = $.param.querystring(window.location.href, 'debug=tests');
- },
- /**
- * Delete assets bundles to force their regeneration
- *
- * @returns {void}
- */
- regenerateAssets: function () {
- var self = this;
- var domain = [
- ['res_model', '=', 'ir.ui.view'],
- ['name', 'like', 'assets_']
- ];
- this._rpc({
- model: 'ir.attachment',
- method: 'search',
- args: [domain],
- }).then(function (ids) {
- self._rpc({
- model: 'ir.attachment',
- method: 'unlink',
- args: [ids],
- }).then(self.do_action('reload'));
- });
- }
-});
-
-/**
- * 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 model = this._action.res_model,
- self = this;
- this._rpc({
- model: 'ir.model',
- method: 'search',
- args: [[['model', '=', model]]]
- }).then(function (ids) {
- self.do_action({
- res_model: 'ir.model.fields',
- name: _t('View Fields'),
- views: [[false, 'list'], [false, 'form']],
- domain: [['model_id', '=', model]],
- type: 'ir.actions.act_window',
- context: {
- 'default_model_id': ids[0]
- }
- });
- });
- },
- 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
- }
- });
- },
- translate: function() {
- this._rpc({
- model: 'ir.translation',
- method: 'get_technical_translations',
- args: [this._action.res_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 Promise.all([
- this._super(),
- this._rpc({
- model: 'ir.ui.view',
- method: 'check_access_rights',
- kwargs: {operation: 'write', raise_exception: false},
- })
- .then(function (ar) {
- this._can_edit_views = ar;
- }.bind(this))
- ]
- );
- },
- update: function (tag, descriptor, widget) {
- if (tag === 'action' || tag === 'view') {
- this._controller = widget;
- }
- return this._super(tag, descriptor).then(function () {
- this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.View', {
- action: this._action,
- can_edit: this._can_edit_views,
- controller: this._controller,
- controlPanelView: this._controller && this._controller._controlPanel,
- manager: this,
- view: this._controller && _.findWhere(this._action.views, {
- type: this._controller.viewType,
- }),
- }));
- }.bind(this));
- },
- get_attachments: function() {
- var selectedIDs = this._controller.getSelectedIds();
- if (!selectedIDs.length) {
- console.warn(_t("No attachment available"));
- return;
- }
- this.do_action({
- res_model: 'ir.attachment',
- name: _t('Manage Attachments'),
- views: [[false, 'list'], [false, 'form']],
- type: 'ir.actions.act_window',
- domain: [['res_model', '=', this._action.res_model], ['res_id', '=', selectedIDs[0]]],
- context: {
- default_res_model: this._action.res_model,
- default_res_id: selectedIDs[0],
- },
- });
- },
- get_metadata: function() {
- var self = this;
- var selectedIDs = this._controller.getSelectedIds();
- if (!selectedIDs.length) {
- console.warn(_t("No metadata available"));
- return;
- }
- this._rpc({
- model: this._action.res_model,
- method: 'get_metadata',
- args: [selectedIDs],
- }).then(function(result) {
- var metadata = result[0];
- metadata.creator = field_utils.format.many2one(metadata.create_uid);
- metadata.lastModifiedBy = field_utils.format.many2one(metadata.write_uid);
- var createDate = field_utils.parse.datetime(metadata.create_date);
- metadata.create_date = field_utils.format.datetime(createDate);
- var modificationDate = field_utils.parse.datetime(metadata.write_date);
- metadata.write_date = field_utils.format.datetime(modificationDate);
- var dialog = new Dialog(this, {
- title: _.str.sprintf(_t("Metadata (%s)"), self._action.res_model),
- size: 'medium',
- $content: QWeb.render('WebClient.DebugViewLog', {
- perm : metadata,
- })
- });
- dialog.open().opened(function () {
- dialog.$el.on('click', 'a[data-action="toggle_noupdate"]', function (ev) {
- ev.preventDefault();
- self._rpc({
- model: 'ir.model.data',
- method: 'toggle_noupdate',
- args: [self._action.res_model, metadata.id]
- }).then(function (res) {
- dialog.close();
- self.get_metadata();
- })
- });
- })
- });
- },
- set_defaults: function() {
- var self = this;
-
- var display = function (fieldInfo, value) {
- var displayed = value;
- if (value && fieldInfo.type === 'many2one') {
- displayed = value.data.display_name;
- value = value.data.id;
- } else if (value && fieldInfo.type === 'selection') {
- displayed = _.find(fieldInfo.selection, function (option) {
- return option[0] === value;
- })[1];
- }
- return [value, displayed];
- };
-
- var renderer = this._controller.renderer;
- var state = renderer.state;
- var fields = state.fields;
- var fieldsInfo = state.fieldsInfo.form;
- var fieldNamesInView = state.getFieldNames();
- var fieldNamesOnlyOnView = ['message_attachment_count'];
- var fieldsValues = state.data;
- var modifierDatas = {};
- _.each(fieldNamesInView, function (fieldName) {
- modifierDatas[fieldName] = _.find(renderer.allModifiersData, function (modifierdata) {
- return modifierdata.node.attrs.name === fieldName;
- });
- });
- this.fields = _.chain(fieldNamesInView)
- .difference(fieldNamesOnlyOnView)
- .map(function (fieldName) {
- var modifierData = modifierDatas[fieldName];
- var invisibleOrReadOnly;
- if (modifierData) {
- var evaluatedModifiers = modifierData.evaluatedModifiers[state.id];
- invisibleOrReadOnly = evaluatedModifiers.invisible || evaluatedModifiers.readonly;
- }
- var fieldInfo = fields[fieldName];
- var valueDisplayed = display(fieldInfo, fieldsValues[fieldName]);
- var value = valueDisplayed[0];
- var displayed = valueDisplayed[1];
- // ignore fields which are empty, invisible, readonly, o2m
- // or m2m
- if (!value || invisibleOrReadOnly || fieldInfo.type === 'one2many' ||
- fieldInfo.type === 'many2many' || fieldInfo.type === 'binary' ||
- fieldsInfo[fieldName].options.isPassword || !_.isEmpty(fieldInfo.depends)) {
- return false;
- }
- return {
- name: fieldName,
- string: fieldInfo.string,
- value: value,
- displayed: displayed,
- };
- })
- .compact()
- .sortBy(function (field) { return field.string; })
- .value();
-
- var conditions = _.chain(fieldNamesInView)
- .filter(function (fieldName) {
- var fieldInfo = fields[fieldName];
- return fieldInfo.change_default;
- })
- .map(function (fieldName) {
- var fieldInfo = fields[fieldName];
- var valueDisplayed = display(fieldInfo, fieldsValues[fieldName]);
- var value = valueDisplayed[0];
- var displayed = valueDisplayed[1];
- return {
- name: fieldName,
- string: fieldInfo.string,
- value: value,
- displayed: displayed,
- };
- })
- .value();
- var d = new Dialog(this, {
- title: _t("Set Default"),
- buttons: [
- {text: _t("Close"), close: true},
- {text: _t("Save default"), click: function () {
- var $defaults = d.$el.find('#formview_default_fields');
- var fieldToSet = $defaults.val();
- if (!fieldToSet) {
- $defaults.parent().addClass('o_form_invalid');
- return;
- }
- var selfUser = d.$el.find('#formview_default_self').is(':checked');
- var condition = d.$el.find('#formview_default_conditions').val();
- var value = _.find(self.fields, function (field) {
- return field.name === fieldToSet;
- }).value;
- self._rpc({
- model: 'ir.default',
- method: 'set',
- args: [
- self._action.res_model,
- fieldToSet,
- value,
- selfUser,
- true,
- condition || false,
- ],
- }).then(function () { d.close(); });
- }}
- ]
- });
- d.args = {
- fields: this.fields,
- conditions: conditions,
- };
- d.template = 'FormView.set_default';
- d.open();
- },
- fvg: function() {
- var self = this;
- var dialog = new Dialog(this, { title: _t("Fields View Get") });
- dialog.opened().then(function () {
- $('<pre>').text(utils.json_node_to_xml(
- self._controller.renderer.arch, true)
- ).appendTo(dialog.$el);
- });
- dialog.open();
- },
-});
-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++
- });
- } 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();
- },
- '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 (config.debug) {
- SystrayMenu.Items.push(DebugManager);
-
- WebClient.include({
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
-
- /**
- * @override
- */
- current_action_updated: function (action, controller) {
- this._super.apply(this, arguments);
- var debugManager = _.find(this.menu.systray_menu.widgets, function(item) {
- return item instanceof DebugManager;
- });
- debugManager.update('action', action, controller && controller.widget);
- },
- });
-
- ActionManager.include({
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
-
- /**
- * Returns the action of the controller currently opened in a dialog,
- * i.e. a target='new' action, if any.
- *
- * @returns {Object|null}
- */
- getCurrentActionInDialog: function () {
- if (this.currentDialogController) {
- return this.actions[this.currentDialogController.actionID];
- }
- return null;
- },
- /**
- * Returns the controller currently opened in a dialog, if any.
- *
- * @returns {Object|null}
- */
- getCurrentControllerInDialog: function () {
- return this.currentDialogController;
- },
- });
-
- Dialog.include({
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * @override
- */
- open: function() {
- var self = this;
- // if the dialog is opened by the ActionManager, instantiate a
- // DebugManager and insert it into the DOM once the dialog is opened
- // (delay this with a setTimeout(0) to ensure that the internal
- // state, i.e. the current action and controller, of the
- // ActionManager is set to properly update the DebugManager)
- this.opened(function() {
- setTimeout(function () {
- var parent = self.getParent();
- if (parent instanceof ActionManager) {
- var action = parent.getCurrentActionInDialog();
- if (action) {
- var controller = parent.getCurrentControllerInDialog();
- self.debugManager = new DebugManager(self);
- var $header = self.$modal.find('.modal-header:first');
- return self.debugManager.prependTo($header).then(function () {
- self.debugManager.update('action', action, controller.widget);
- });
- }
- }
- }, 0);
- });
-
- return this._super.apply(this, arguments);
- },
- });
-}
-
-return DebugManager;
-
-});