[IMP] web: move and convert profiling_sweb_view

closes odoo/odoo#76871

X-original-commit: bee8953c3d8b7179bf1f526baa593825f09bfd97
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Francois (fge)
2021-09-20 20:14:07 +00:00
parent 97733eae50
commit 2de7eb29b0
3 changed files with 259 additions and 242 deletions
@@ -1,242 +0,0 @@
/* eslint-disable comma-dangle */
odoo.define('web.profiling_qweb_view', function (require) {
'use strict';
const registry = require('web.field_registry');
const basicFfields = require('web.basic_fields');
const core = require('web.core');
const qweb = core.qweb;
/**
* This widget is intended to be used on Text fields. It will provide Ace Editor
* for display XML and Python profiling.
*/
const ProfilingQwebView = basicFfields.AceEditor.extend({
template: "web.ProfilingQwebView",
xmlDependencies: ['/web/static/src/xml/debug.xml'],
events: _.extend({}, basicFfields.AceEditor.prototype.events, {
'click .dropdown-menu a': '_onSelectView',
}),
/**
* @override
* @params parent {Widget}
*/
init: function () {
this._super.apply(this, arguments);
// {template, xpath, directive, time, duration, query }[]
const results = this.value && JSON.parse(this.value)[0].results || {archs: {}, data: []};
this.profileLines = results.data;
this.viewArch = results.archs;
this.viewIDs = Array.from(new Set(this.profileLines.map(line => line.view_id)));
for (const line of this.profileLines) {
line.xpath = line.xpath.replace(/([^\]])\//g, '$1[1]/').replace(/([^\]])$/g, '$1[1]');
}
this.viewID = this.profileLines.length ? this.profileLines[0].view_id : 0;
this.mode = 'readonly';
},
/**
* Search xml view
*
* @override
* @returns {Promise}
*/
willStart: async function () {
const _super = this._super;
this.views = await this._rpc({
model: 'ir.ui.view',
method: 'search_read',
fields: ['id', 'display_name', 'key'],
domain: [['id', 'in', this.viewIDs]],
});
for (const view of this.views) {
view.delay = 0;
view.query = 0;
const lines = this.profileLines.filter(l => l.view_id === view.id);
const root = lines.find(l => l.xpath === '');
if (root) {
view.delay += root.delay;
view.query += root.query;
} else {
view.delay = lines.map(l => l.delay).reduce((a, b) => a + b);
view.query = lines.map(l => l.query).reduce((a, b) => a + b);
}
view.delay = Math.ceil(view.delay * 10) / 10;
}
await _super.call(this);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Set view to render
*
* @override
* @returns {Promise}
*/
_render: function () {
this.view = this.views.find(view => view.id === this.viewID);
if (this.view) {
const content = this.$('.dropdown-menu a[data-id=' + this.view.id + ']').html();
this.$('.dropdown-toggle').empty().append(content);
const arch = this.viewArch[this.view.id] || '';
if (this.aceSession.getValue() !== arch) {
this.aceSession.setValue(arch);
}
} else {
this.aceSession.setValue('');
}
},
_renderProfilingInformation: function () {
let flat = {};
let arch = [{ xpath: '', children: [] }];
const $rows = this.$('.ace_gutter .ace_gutter-cell');
$rows.find('.o_info').remove();
this.$('.ace_tag-open, .ace_end-tag-close, .ace_end-tag-open, .ace_qweb').each((i, node) => {
const $node = $(node);
const parent = arch[arch.length - 1];
let xpath = parent.xpath;
if ($node.hasClass('ace_end-tag-close')) {
// Close tag.
const tag = $node.prevAll('.ace_tag-name:first').text();
if (parent.tag === tag) {
// can be different when scroll because ace does not display the previous lines.
arch.pop();
}
} else if ($node.hasClass('ace_end-tag-open')) {
// Auto close tag.
const tag = $node.next().text();
if (parent.tag === tag) {
// can be different when scroll because ace does not display the previous lines.
arch.pop();
}
} else if ($node.hasClass('ace_qweb')) {
// QWeb attribute.
const directive = $node.text();
parent.directive.push({
el: node,
directive: directive,
});
// Compute delay and query number.
let delay = 0;
let query = 0;
for (const line of this.profileLines) {
if (line.view_id === this.viewID && line.xpath === xpath && line.directive.includes(directive)) {
delay += line.delay;
query += line.query;
}
}
// Render delay and query number in span visible on hover.
if ((delay || query) && !$node.children('.o_info').length) {
$(qweb.render('web.ProfilingQwebView.hover', { delay: this._formatDelay(delay), query: query })).prependTo($node);
}
} else if ($node.hasClass('ace_tag-open')) {
// Open tag.
const $tag = $node.next('.ace_tag-name');
const tag = $tag.text();
const $row = $($rows[$tag.parent('.ace_line').index()]);
// Add a children to the arch and compute the xpath.
xpath += '/' + tag;
let i = 1;
while (flat[xpath + '[' + i + ']']) {
i++;
}
xpath += '[' + i + ']';
flat[xpath] = { xpath: xpath, tag: tag, children: [], directive: [] };
arch.push(flat[xpath]);
parent.children.push(flat[xpath]);
// Compute delay and query number.
const closed = !!$row.find('.ace_closed').length;
const delays = [];
const querys = [];
const groups = {};
let displayDetail = false;
for (const line of this.profileLines) {
if (line.view_id === this.viewID && (closed ? line.xpath.startsWith(xpath) : line.xpath === xpath)) {
delays.push(line.delay);
querys.push(line.query);
const directive = line.directive.split("=")[0];
if (!groups[directive]) {
groups[directive] = {
delays: [],
querys: [],
};
} else {
displayDetail = true;
}
groups[directive].delays.push(this._formatDelay(line.delay));
groups[directive].querys.push(line.query);
}
}
// Display delay and query number in front of the line.
if (delays.length && !$row.children('.o_info').length) {
$(qweb.render('web.ProfilingQwebView.info', {
delay: this._formatDelay(delays.reduce((a, b) => a + b, 0)),
query: querys.reduce((a, b) => a + b, 0) || '.',
detail: displayDetail,
groups: groups,
})).prependTo($row);
}
}
$node.attr('data-xpath', xpath);
});
},
_formatDelay: function (delay) {
return delay ? _.str.sprintf('%.1f', Math.ceil(delay * 10) / 10) : '.';
},
/**
* Starts the ace library on the given DOM element. This initializes the
* ace editor in readonly mode.
*
* @private
* @param {Node} node - the DOM element the ace library must initialize on
*/
_startAce: function (node) {
this.aceEditor = window.ace.edit(node);
this.aceEditor.setOptions({
maxLines: Infinity,
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: true,
readOnly: true,
});
this.aceEditor.renderer.setOptions({
displayIndentGuides: true,
showGutter: true,
});
this.aceEditor.renderer.$gutter.removeAttribute('aria-hidden');
this.aceEditor.renderer.$cursorLayer.element.style.display = "none";
this.aceEditor.$blockScrolling = true;
this.aceSession = this.aceEditor.getSession();
this.aceSession.setOptions({
useWorker: false,
mode: "ace/mode/qweb",
tabSize: 2,
useSoftTabs: true,
});
// Ace render 3 times when change the value and 1 time per click.
this.aceEditor.renderer.on("afterRender", this._renderProfilingInformation.bind(this));
},
/**
* @private
* @param {MouseEvent} ev
*/
_onSelectView: function (ev) {
ev.preventDefault();
this.viewID = +$(ev.currentTarget).data('id');
this._render();
},
});
registry.add('profiling_qweb_view', ProfilingQwebView);
});
@@ -0,0 +1,259 @@
/** @odoo-module **/
/* eslint-disable comma-dangle */
import registry from "web.field_registry";
import basicFfields from "web.basic_fields";
import core from "web.core";
const qweb = core.qweb;
/**
* This widget is intended to be used on Text fields. It will provide Ace Editor
* for display XML and Python profiling.
*/
const ProfilingQwebView = basicFfields.AceEditor.extend({
template: "web.ProfilingQwebView",
xmlDependencies: ["/web/static/src/xml/debug.xml"],
events: _.extend({}, basicFfields.AceEditor.prototype.events, {
"click .dropdown-menu a": "_onSelectView",
}),
/**
* @override
* @params parent {Widget}
*/
init: function () {
this._super.apply(this, arguments);
// {template, xpath, directive, time, duration, query }[]
const results = (this.value && JSON.parse(this.value)[0].results) || {
archs: {},
data: [],
};
this.profileLines = results.data;
this.viewArch = results.archs;
this.viewIDs = Array.from(new Set(this.profileLines.map((line) => line.view_id)));
for (const line of this.profileLines) {
line.xpath = line.xpath.replace(/([^\]])\//g, "$1[1]/").replace(/([^\]])$/g, "$1[1]");
}
this.viewID = this.profileLines.length ? this.profileLines[0].view_id : 0;
this.mode = "readonly";
},
/**
* Search xml view
*
* @override
* @returns {Promise}
*/
willStart: async function () {
const _super = this._super;
this.views = await this._rpc({
model: "ir.ui.view",
method: "search_read",
fields: ["id", "display_name", "key"],
domain: [["id", "in", this.viewIDs]],
});
for (const view of this.views) {
view.delay = 0;
view.query = 0;
const lines = this.profileLines.filter((l) => l.view_id === view.id);
const root = lines.find((l) => l.xpath === "");
if (root) {
view.delay += root.delay;
view.query += root.query;
} else {
view.delay = lines.map((l) => l.delay).reduce((a, b) => a + b);
view.query = lines.map((l) => l.query).reduce((a, b) => a + b);
}
view.delay = Math.ceil(view.delay * 10) / 10;
}
await _super.call(this);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Set view to render
*
* @override
* @returns {Promise}
*/
_render: function () {
this.view = this.views.find((view) => view.id === this.viewID);
if (this.view) {
const content = this.$(".dropdown-menu a[data-id=" + this.view.id + "]").html();
this.$(".dropdown-toggle").empty().append(content);
const arch = this.viewArch[this.view.id] || "";
if (this.aceSession.getValue() !== arch) {
this.aceSession.setValue(arch);
}
} else {
this.aceSession.setValue("");
}
},
_renderProfilingInformation: function () {
let flat = {};
let arch = [{ xpath: "", children: [] }];
const $rows = this.$(".ace_gutter .ace_gutter-cell");
$rows.find(".o_info").remove();
this.$(".ace_tag-open, .ace_end-tag-close, .ace_end-tag-open, .ace_qweb").each(
(i, node) => {
const $node = $(node);
const parent = arch[arch.length - 1];
let xpath = parent.xpath;
if ($node.hasClass("ace_end-tag-close")) {
// Close tag.
const tag = $node.prevAll(".ace_tag-name:first").text();
if (parent.tag === tag) {
// can be different when scroll because ace does not display the previous lines.
arch.pop();
}
} else if ($node.hasClass("ace_end-tag-open")) {
// Auto close tag.
const tag = $node.next().text();
if (parent.tag === tag) {
// can be different when scroll because ace does not display the previous lines.
arch.pop();
}
} else if ($node.hasClass("ace_qweb")) {
// QWeb attribute.
const directive = $node.text();
parent.directive.push({
el: node,
directive: directive,
});
// Compute delay and query number.
let delay = 0;
let query = 0;
for (const line of this.profileLines) {
if (
line.view_id === this.viewID &&
line.xpath === xpath &&
line.directive.includes(directive)
) {
delay += line.delay;
query += line.query;
}
}
// Render delay and query number in span visible on hover.
if ((delay || query) && !$node.children(".o_info").length) {
$(
qweb.render("web.ProfilingQwebView.hover", {
delay: this._formatDelay(delay),
query: query,
})
).prependTo($node);
}
} else if ($node.hasClass("ace_tag-open")) {
// Open tag.
const $tag = $node.next(".ace_tag-name");
const tag = $tag.text();
const $row = $($rows[$tag.parent(".ace_line").index()]);
// Add a children to the arch and compute the xpath.
xpath += "/" + tag;
let i = 1;
while (flat[xpath + "[" + i + "]"]) {
i++;
}
xpath += "[" + i + "]";
flat[xpath] = { xpath: xpath, tag: tag, children: [], directive: [] };
arch.push(flat[xpath]);
parent.children.push(flat[xpath]);
// Compute delay and query number.
const closed = !!$row.find(".ace_closed").length;
const delays = [];
const querys = [];
const groups = {};
let displayDetail = false;
for (const line of this.profileLines) {
if (
line.view_id === this.viewID &&
(closed ? line.xpath.startsWith(xpath) : line.xpath === xpath)
) {
delays.push(line.delay);
querys.push(line.query);
const directive = line.directive.split("=")[0];
if (!groups[directive]) {
groups[directive] = {
delays: [],
querys: [],
};
} else {
displayDetail = true;
}
groups[directive].delays.push(this._formatDelay(line.delay));
groups[directive].querys.push(line.query);
}
}
// Display delay and query number in front of the line.
if (delays.length && !$row.children(".o_info").length) {
$(
qweb.render("web.ProfilingQwebView.info", {
delay: this._formatDelay(delays.reduce((a, b) => a + b, 0)),
query: querys.reduce((a, b) => a + b, 0) || ".",
detail: displayDetail,
groups: groups,
})
).prependTo($row);
}
}
$node.attr("data-xpath", xpath);
}
);
},
_formatDelay: function (delay) {
return delay ? _.str.sprintf("%.1f", Math.ceil(delay * 10) / 10) : ".";
},
/**
* Starts the ace library on the given DOM element. This initializes the
* ace editor in readonly mode.
*
* @private
* @param {Node} node - the DOM element the ace library must initialize on
*/
_startAce: function (node) {
this.aceEditor = window.ace.edit(node);
this.aceEditor.setOptions({
maxLines: Infinity,
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: true,
readOnly: true,
});
this.aceEditor.renderer.setOptions({
displayIndentGuides: true,
showGutter: true,
});
this.aceEditor.renderer.$gutter.removeAttribute("aria-hidden");
this.aceEditor.renderer.$cursorLayer.element.style.display = "none";
this.aceEditor.$blockScrolling = true;
this.aceSession = this.aceEditor.getSession();
this.aceSession.setOptions({
useWorker: false,
mode: "ace/mode/qweb",
tabSize: 2,
useSoftTabs: true,
});
// Ace render 3 times when change the value and 1 time per click.
this.aceEditor.renderer.on("afterRender", this._renderProfilingInformation.bind(this));
},
/**
* @private
* @param {MouseEvent} ev
*/
_onSelectView: function (ev) {
ev.preventDefault();
this.viewID = +$(ev.currentTarget).data("id");
this._render();
},
});
registry.add("profiling_qweb_view", ProfilingQwebView);