[REF] web, website: remove legacy DebugManager and use new DebugMenu

Previously, the debug manager was rewritten with the new webclient
architecture, however, because the frontend did not have access to the
new infrastructure (env and services), the old debug manager was kept
and used only in the frontend.

Since odoo/odoo#72675 the new env and services are now available, this
commit simply uses the new debug manager in the frontend and removes the
legacy code.

closes odoo/odoo#74609

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Samuel Degueldre
2021-08-03 14:03:18 +00:00
parent 8f1eb69410
commit ce5599926a
15 changed files with 139 additions and 600 deletions
-3
View File
@@ -203,9 +203,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/env.js',
'web/static/src/core/utils/transitions.scss', # included early because used by other files
'web/static/src/core/**/*',
# TODO: split debug files correctly when converting frontend debug
# manager so we don't need action-service and stuff
('remove', 'web/static/src/core/debug/**/*'),
'web/static/src/public/error_notifications.js',
'web/static/src/legacy/scss/base_frontend.scss',
@@ -61,8 +61,12 @@ class DebugContext {
}
const debugContextSymbol = Symbol("debugContext");
export function createDebugContext(env, { categories = [] } = {}) {
return { [debugContextSymbol]: new DebugContext(env, categories) };
}
export function useOwnDebugContext({ categories = [] } = {}) {
useSubEnv({ [debugContextSymbol]: new DebugContext(useEnv(), categories) });
useSubEnv(createDebugContext(useEnv(), { categories }));
}
export function useCurrentDebugContext() {
@@ -4,85 +4,6 @@ import { browser } from "../browser/browser";
import { routeToUrl } from "../browser/router_service";
import { registry } from "../registry";
// @legacy
import dialogs from "web.view_dialogs";
import { ComponentAdapter } from "web.OwlCompatibility";
// Backend Debug Manager Items
function runJSTestsItem({ env }) {
const runTestsURL = browser.location.origin + "/web/tests?mod=*";
return {
type: "item",
description: env._t("Run JS Tests"),
href: runTestsURL,
callback: () => {
browser.open(runTestsURL);
},
sequence: 10,
};
}
function runJSTestsMobileItem({ env }) {
const runTestsMobileURL = browser.location.origin + "/web/tests/mobile?mod=*";
return {
type: "item",
description: env._t("Run JS Mobile Tests"),
href: runTestsMobileURL,
callback: () => {
browser.open(runTestsMobileURL);
},
sequence: 20,
};
}
export function openViewItem({ env }) {
async function onSelected(records) {
const views = await env.services.orm.searchRead(
"ir.ui.view",
[["id", "=", records[0].id]],
["name", "model", "type"],
{ limit: 1 }
);
const view = views[0];
view.type = view.type === "tree" ? "list" : view.type; // ignore tree view
env.services.action.doAction({
type: "ir.actions.act_window",
name: view.name,
res_model: view.model,
views: [[view.id, view.type]],
});
}
return {
type: "item",
description: env._t("Open View"),
callback: () => {
const adapterParent = new ComponentAdapter(null, { Component: owl.Component });
const selectCreateDialog = new dialogs.SelectCreateDialog(adapterParent, {
res_model: "ir.ui.view",
title: env._t("Select a view"),
disable_multiple_selection: true,
domain: [
["type", "!=", "qweb"],
["type", "!=", "search"],
],
on_selected: onSelected,
});
selectCreateDialog.open();
},
sequence: 40,
};
}
// Global Debug Manager Items
function globalSeparator() {
return {
type: "separator",
sequence: 400,
};
}
function activateAssetsDebugging({ env }) {
return {
type: "item",
@@ -155,12 +76,6 @@ function leaveDebugMode({ env }) {
registry
.category("debug")
.category("default")
// Backend
.add("runJSTestsItem", runJSTestsItem)
.add("runJSTestsMobileItem", runJSTestsMobileItem)
.add("openViewItem", openViewItem)
// Global
.add("globalSeparator", globalSeparator)
.add("activateAssetsDebugging", activateAssetsDebugging)
.add("regenerateAssets", regenerateAssets)
.add("becomeSuperuser", becomeSuperuser)
@@ -8,7 +8,6 @@ const { Component } = owl;
export class ProfilingItem extends Component {
setup() {
this.profiling = useService("profiling");
this.actionService = useService("action");
useBus(this.props.bus, "UPDATE", this.render);
}
@@ -16,7 +15,13 @@ export class ProfilingItem extends Component {
this.profiling.setParam(param, ev.target.value);
}
openProfiles() {
this.actionService.doAction("base.action_menu_ir_profile");
if (this.env.services.action) {
// using doAction in the backend to preserve breadcrumbs and stuff
this.env.services.action.doAction("base.action_menu_ir_profile");
} else {
// No action service means we are in the frontend.
window.location = "/web/#action=base.action_menu_ir_profile";
}
}
}
ProfilingItem.components = { DropdownItem };
@@ -18,6 +18,7 @@ import { UncaughtClientError, UncaughtCorsError, UncaughtPromiseError } from "./
const errorHandlerRegistry = registry.category("error_handlers");
const errorDialogRegistry = registry.category("error_dialogs");
const errorNotificationRegistry = registry.category("error_notifications");
// -----------------------------------------------------------------------------
// CORS errors
@@ -71,7 +72,7 @@ errorHandlerRegistry.add("clientErrorHandler", clientErrorHandler, { sequence: 9
* @param {Error} originalError
* @returns {boolean}
*/
function rpcErrorHandler(env, error, originalError) {
export function rpcErrorHandler(env, error, originalError) {
if (!(error instanceof UncaughtPromiseError)) {
return false;
}
@@ -85,9 +86,16 @@ function rpcErrorHandler(env, error, originalError) {
// error is here a RPCError
error.unhandledRejectionEvent.preventDefault();
const exceptionName = originalError.exceptionName;
let ErrorComponent = null;
if (exceptionName && errorDialogRegistry.contains(exceptionName)) {
ErrorComponent = errorDialogRegistry.get(exceptionName);
let ErrorComponent = originalError.Component;
if (!ErrorComponent && exceptionName) {
if (errorNotificationRegistry.contains(exceptionName)) {
const notif = errorNotificationRegistry.get(exceptionName);
env.services.notification.add(notif.message || originalError.data.message, notif);
return true;
}
if (errorDialogRegistry.contains(exceptionName)) {
ErrorComponent = errorDialogRegistry.get(exceptionName);
}
}
env.services.dialog.add(ErrorComponent || RPCErrorDialog, {
@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="WebClient.DebugManager">
<li class="o_debug_manager" role="menuitem">
<t t-set="_devtool_button_title">Open Developer Tools</t>
<a role="button" href="#" class="o_debug_mode"
t-att-title="_devtool_button_title + widget.debug_mode_help"
t-att-aria-label="_devtool_button_title + widget.debug_mode_help"
t-att-data-debug-mode="widget.debug_mode"
data-toggle="dropdown" aria-expanded="false" tabindex="-1" data-display="static">
<span class="fa fa-bug">
<span class="profiling_items text-danger fa fa-circle"></span>
</span>
</a>
<div class="dropdown-menu dropdown-menu-right o_debug_dropdown" role="menu"/>
</li>
</t>
<t t-name="WebClient.DebugManager.Global">
<a role="menuitem" href="#" data-action="split_assets" class="dropdown-item o_debug_split_assets">Activate Assets Debugging</a>
<a role="menuitem" href="#" data-action="tests_assets" class="dropdown-item">Activate Tests Assets Debugging</a>
<a role="menuitem" href="#" data-action="regenerateAssets" class="dropdown-item">Regenerate Assets Bundles</a>
<a t-if="manager._is_admin" role="menuitem" href="/web/become" class="dropdown-item">Become Superuser</a>
<a role="menuitem" href="#" data-action="leave_debug_mode" class="dropdown-item">Leave the Developer Tools</a>
<div class="dropdown-divider" role="separator"/>
<span role="menuitem" class="dropdown-item profiling">
<span class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="enable_profiling"/>
<label class="custom-control-label" for="enable_profiling">
Enable profiling
<span class="profiling_items text-danger fa fa-circle"></span>
</label>
<i id="open_profiling_button" class="open_profiling float-right fa fa-list fa-lg"></i>
</span>
</span>
<span class="profiling_items">
<span role="menuitem" class="dropdown-item profiling">
<span class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input profile_switch" id="profile_sql"/>
<label class="custom-control-label" for="profile_sql">Record sql</label>
</span>
</span>
<span role="menuitem" class="dropdown-item profiling">
<span class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input profile_switch" id="profile_traces_async"/>
<label class="custom-control-label" for="profile_traces_async">Record traces</label>
</span>
</span>
<span role="menuitem" class="dropdown-item profiling profile_traces_async">
<span class="">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text">Interval</div>
</div>
<select id="profile_traces_async_interval" class="profile_param form-control">
<option value="">Default</option>
<option value="0.001">0.001</option>
<option value="0.01">0.01</option>
<option value="0.1">0.1</option>
<option value="1">1</option>
</select>
<!--input type="number" step="any" min="0.001" max="10" value="0.001" /-->
</div>
</span>
</span>
</span>
</t>
</templates>
@@ -1,194 +0,0 @@
odoo.define('web.DebugManager', function (require) {
"use strict";
var core = require('web.core');
var session = require('web.session');
var utils = require('web.utils');
var Widget = require('web.Widget');
const { session: sessionInfo } = require('@web/session');
var QWeb = core.qweb;
/**
* DebugManager base + general features (applicable to any context)
*/
var DebugManager = Widget.extend({
template: "WebClient.DebugManager",
xmlDependencies: ['/web/static/src/legacy/frontend/debug_manager/debug.xml'],
events: {
"click a[data-action]": "perform_callback",
"click .profiling": "handle_profiling",
"change .profiling_param": "handle_profiling",
},
init: function () {
this._super.apply(this, arguments);
this._events = null;
var debug = odoo.debug;
this.debug_mode = debug;
this.debug_mode_help = debug && debug !== '1' ? ' (' + debug + ')' : '';
this.profile_session = sessionInfo.profile_session || false;
this.profile_collectors = sessionInfo.profile_collectors;
if (! Array.isArray(this.profile_collectors)) {
this.profile_collectors = ['sql', 'traces_async']; // use default value when not defined in session.
}
this.profile_params = sessionInfo.profile_params || {};
},
start: function () {
core.bus.on('rpc:result', this, function (req, resp) {
this._debug_events(resp.debug);
});
this.$dropdown = this.$(".o_debug_dropdown");
// whether the current user is an administrator
this._is_admin = session.is_system;
return Promise.resolve(
this._super()
).then(function () {
return this.update();
}.bind(this));
},
/**
* 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);
}
},
/**
* Manage profiling actions.
* open_profiling_button should not prevent dropdown from closing
* other actions should keep dropdown open and interact with backend to enable or disable profiling features.
* The main switch, 'enable_profiling' is a special case since it will create or remove a profile session
*/
handle_profiling: async function (evt) {
const $target = $(evt.target);
const target = $target[0];
if (target.id === 'open_profiling_button') {
this.do_action('base.action_menu_ir_profile');
return;
}
evt.stopPropagation(); // prevent dropdown from closing
const kwargs = {
params: {}
};
if (target.nodeName == "LABEL") {
return
}
kwargs.profile = $('#enable_profiling')[0].checked;
kwargs.collectors = $('input.profile_switch').toArray().filter(i => i.checked).map(i => i.id.replace(/^profile_/, ''));
$('.profile_param').toArray().forEach(i => kwargs.params[i.id.replace(/^profile_/, '')] = i.value);
try {
const resp = await this._rpc({
model: 'ir.profile',
method: 'set_profiling',
kwargs: kwargs,
});
this.profile_session = resp.session;
this.profile_collectors = resp.collectors;
this.profile_params = resp.params;
} catch (e) {
this.profite_session = false;
}
this.update_profiling_state();
},
/**
* Update the profiling dropdown menu state after any change to synchronyze server session and client
* This is mainly usefull to avoid desync in case of multiple tabs
*/
update_profiling_state: function () {
const self = this;
this.$('#enable_profiling')[0].checked = Boolean(this.profile_session);
this.$('.profile_switch').each(function () {
this.checked = (self.profile_collectors.indexOf(this.id.replace(/^profile_/, '')) !== -1);
const params = self.$('.' + this.id);
params.toggleClass('d-none', !this.checked);
params.each(function () {
const params_input = $(this).find('.profile_param')[0];
params_input.value = self.profile_params[params_input.id.replace(/^profile_/, '')] || '';
});
});
this.$profiling_items.toggleClass('d-none', !this.profile_session);
},
_debug_events: function (events) {
if (!this._events) {
return;
}
if (events && events.length) {
this._events.push(events);
}
this.trigger('update-stats', this._events);
},
/**
* Update the debug manager: reinserts all "universal" controls
*/
update: function () {
this.$dropdown
.empty()
.append(QWeb.render('WebClient.DebugManager.Global', {
manager: this,
}));
this.$profiling_items = this.$(".profiling_items");
this.update_profiling_state();
return Promise.resolve();
},
split_assets: function () {
window.location = $.param.querystring(window.location.href, 'debug=assets');
},
tests_assets: function () {
// Enable also 'assets' to see non minimized assets
window.location = $.param.querystring(window.location.href, 'debug=assets,tests');
},
/**
* Delete assets bundles to force their regeneration
*
* @returns {void}
*/
regenerateAssets: function () {
var self = this;
var domain = utils.assetsDomain();
this._rpc({
model: 'ir.attachment',
method: 'search',
args: [domain],
}).then(function (ids) {
self._rpc({
model: 'ir.attachment',
method: 'unlink',
args: [ids],
}).then(window.location.reload());
});
},
leave_debug_mode: function () {
var qs = $.deparam.querystring();
qs.debug = '';
window.location.search = '?' + $.param(qs);
},
/**
* @private
* @param {string} model
* @param {string} operation
* @returns {Promise<boolean>}
*/
_checkAccessRight(model, operation) {
return this._rpc({
model: model,
method: 'check_access_rights',
kwargs: {operation, raise_exception: false},
})
},
});
return DebugManager;
});
@@ -1,13 +0,0 @@
.o_debug_mode .fa { /* Active profiling red dot should be overed on the bottom right corner of the bug icon*/
position: relative;
.fa {
position: absolute;
bottom: -0.1em;
right: -0.4em;
font-size: 0.8em;
}
}
.open_profiling { /* Fix dropdown-item padding missing because of float: right*/
padding: 3px;
}
@@ -1,12 +1,9 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { RPCErrorDialog } from "../core/errors/error_dialogs";
import { ConnectionLostError, RPCError } from "../core/network/rpc_service";
import { lostConnectionHandler } from "@web/core/errors/error_handlers";
import { lostConnectionHandler, rpcErrorHandler } from "@web/core/errors/error_handlers";
const errorNotificationRegistry = registry.category("error_notifications");
const errorDialogRegistry = registry.category("error_dialogs");
const errorHandlerRegistry = registry.category("error_handlers");
/**
@@ -32,41 +29,18 @@ function legacyRPCErrorHandler(env, error, originalError) {
(originalError.message instanceof RPCError ||
originalError.message instanceof ConnectionLostError)
) {
if (originalError.message instanceof ConnectionLostError) {
return lostConnectionHandler(env, error, originalError.message);
}
const event = originalError.event;
originalError = originalError.message;
error.unhandledRejectionEvent.preventDefault();
if (event.isDefaultPrevented()) {
// in theory, here, event was already handled
error.unhandledRejectionEvent.preventDefault();
return true;
}
event.preventDefault();
const exceptionName = originalError.exceptionName;
let ErrorComponent = originalError.Component;
if (!ErrorComponent && exceptionName) {
if (errorNotificationRegistry.contains(exceptionName)) {
const notif = errorNotificationRegistry.get(exceptionName);
env.services.notification.add(notif.message || originalError.data.message, notif);
return true;
}
if (errorDialogRegistry.contains(exceptionName)) {
ErrorComponent = errorDialogRegistry.get(exceptionName);
}
if (originalError instanceof ConnectionLostError) {
return lostConnectionHandler(env, error, originalError);
}
env.services.dialog.add(ErrorComponent || RPCErrorDialog, {
traceback: originalError.traceback || originalError.stack,
message: originalError.message,
name: originalError.name,
exceptionName: originalError.exceptionName,
data: originalError.data,
subType: originalError.subType,
code: originalError.code,
type: originalError.type,
});
return true;
return rpcErrorHandler(env, error, originalError);
}
return false;
}
@@ -0,0 +1,88 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";
import dialogs from "web.view_dialogs";
import { ComponentAdapter } from "web.OwlCompatibility";
function runJSTestsItem({ env }) {
const runTestsURL = browser.location.origin + "/web/tests?mod=*";
return {
type: "item",
description: env._t("Run JS Tests"),
href: runTestsURL,
callback: () => {
browser.open(runTestsURL);
},
sequence: 10,
};
}
function runJSTestsMobileItem({ env }) {
const runTestsMobileURL = browser.location.origin + "/web/tests/mobile?mod=*";
return {
type: "item",
description: env._t("Run JS Mobile Tests"),
href: runTestsMobileURL,
callback: () => {
browser.open(runTestsMobileURL);
},
sequence: 20,
};
}
export function openViewItem({ env }) {
async function onSelected(records) {
const views = await env.services.orm.searchRead(
"ir.ui.view",
[["id", "=", records[0].id]],
["name", "model", "type"],
{ limit: 1 }
);
const view = views[0];
view.type = view.type === "tree" ? "list" : view.type; // ignore tree view
env.services.action.doAction({
type: "ir.actions.act_window",
name: view.name,
res_model: view.model,
views: [[view.id, view.type]],
});
}
return {
type: "item",
description: env._t("Open View"),
callback: () => {
const adapterParent = new ComponentAdapter(null, { Component: owl.Component });
const selectCreateDialog = new dialogs.SelectCreateDialog(adapterParent, {
res_model: "ir.ui.view",
title: env._t("Select a view"),
disable_multiple_selection: true,
domain: [
["type", "!=", "qweb"],
["type", "!=", "search"],
],
on_selected: onSelected,
});
selectCreateDialog.open();
},
sequence: 40,
};
}
// This separates the items defined above from global items that aren't webclient-only
function globalSeparator() {
return {
type: "separator",
sequence: 400,
};
}
registry
.category("debug")
.category("default")
.add("runJSTestsItem", runJSTestsItem)
.add("runJSTestsMobileItem", runJSTestsMobileItem)
.add("globalSeparator", globalSeparator)
.add("openViewItem", openViewItem);
@@ -14,7 +14,7 @@ import { makeTestEnv, prepareRegistriesWithCleanup } from "../../helpers/mock_en
import { makeFakeDialogService, makeFakeLocalizationService } from "../../helpers/mock_services";
import { click, getFixture, legacyExtraNextTick, patchWithCleanup } from "../../helpers/utils";
import { createWebClient, doAction, getActionManagerServerData } from "../../webclient/helpers";
import { openViewItem } from "@web/core/debug/debug_menu_items";
import { openViewItem } from "@web/webclient/debug_items";
import { editView, editSearchView } from "@web/legacy/debug_manager";
const { Component, mount, tags } = owl;
@@ -1,175 +0,0 @@
odoo.define('web.debugManagerTests', function (require) {
"use strict";
var testUtils = require('web.test_utils');
var FormView = require('web.FormView');
var createDebugManager = testUtils.createDebugManager;
QUnit.module('DebugManager', {}, function () {
QUnit.skip("list: edit view menu item", async function (assert) {
assert.expect(3);
var debugManager = await createDebugManager();
await debugManager.appendTo($('#qunit-fixture'));
// Simulate update debug manager from web client
var action = {
views: [{
displayName: "List",
fieldsView: {
view_id: 1,
},
type: "list",
}],
};
var view = {
viewType: "list",
};
await testUtils.nextTick();
await debugManager.update('action', action, view);
var $editView = debugManager.$('a[data-action=edit][data-model="ir.ui.view"]');
assert.strictEqual($editView.length, 1, "should have edit view menu item");
assert.strictEqual($editView.text().trim(), "Edit View: List",
"should have correct menu item text for editing view");
assert.strictEqual($editView.data('id'), 1, "should have correct view_id");
debugManager.destroy();
});
QUnit.skip("form: Manage Attachments option", async function (assert) {
assert.expect(3);
var debugManager = await createDebugManager({
intercepts: {
do_action: function (event) {
assert.deepEqual(event.data.action, {
context: {
default_res_model: "test.model",
default_res_id: 5,
},
domain: [["res_model", "=", "test.model"],["res_id", "=", 5]],
name: "Manage Attachments",
res_model: "ir.attachment",
type: "ir.actions.act_window",
views: [[false, "list"],[false, "form"]],
});
},
},
});
await debugManager.appendTo($('#qunit-fixture'));
// Simulate update debug manager from web client
var action = {
views: [{
displayName: "Form",
fieldsView: {
view_id: 2,
},
type: "form",
}],
res_model: "test.model",
};
var view = {
viewType: "form",
getSelectedIds: function () {
return [5];
},
};
await debugManager.update('action', action, view);
var $attachmentMenu = debugManager.$('a[data-action=get_attachments]');
assert.strictEqual($attachmentMenu.length, 1, "should have Manage Attachments menu item");
assert.strictEqual($attachmentMenu.text().trim(), "Manage Attachments",
"should have correct menu item text");
await testUtils.dom.click(debugManager.$('> a')); // open dropdown
await testUtils.dom.click($attachmentMenu);
debugManager.destroy();
});
QUnit.skip("Debug: Set defaults with right model", async function (assert) {
assert.expect(2);
/* Click on debug > set default,
* set some defaults, click on save
* model and some other data should be sent to server
*/
// We'll need a full blown architecture with some data
var data = {
partner: {
fields: {
foo: {string: "Foo", type: "char", default: "My little Foo Value"},
},
records: [{
id: 1,
foo: "yop",
}]
},
'ir.default': { // We just need this to be defined
fields: {},
},
};
var form = await testUtils.createView({
View: FormView,
model: 'partner',
data: data,
arch: '<form string="Partners">' +
'<field name="foo" />' +
'</form>',
res_id: 1,
});
// Now the real tested component
var debugManager = await createDebugManager({
data: data,
mockRPC: function (route, args) {
if (route == "/web/dataset/call_kw/ir.default/set") {
assert.deepEqual(args.args, ["partner", "foo", "yop", true, true, false],
'Model, field, value and booleans for current user/company should have been passed');
return Promise.resolve();
}
return this._super.apply(this, arguments);
}
});
await debugManager.appendTo($('#qunit-fixture'));
// Simulate update debug manager from web client
var action = {
controlPanelFieldsView: {},
views: [{
fieldsView: {
view_id: 1,
model: 'partner',
type: 'form',
},
type: "form",
}],
res_model: 'partner',
};
// We are all set
await debugManager.update('action', action, form);
// click on set_defaults dropdown
await testUtils.dom.click(debugManager.$('> a')); // open dropdown
await testUtils.dom.click(debugManager.$('a[data-action="set_defaults"]'));
var $modal = $('.modal-content');
assert.strictEqual($modal.length, 1, 'One modal present');
$modal.find('select[id=formview_default_fields] option[value=foo]').prop('selected', true);
// Save
await testUtils.dom.click($modal.find('.modal-footer button').eq(1));
form.destroy();
debugManager.destroy();
});
});
});
+1 -1
View File
@@ -205,7 +205,7 @@
'website/static/src/js/form_editor_registry.js',
'website/static/src/js/menu/content.js',
'website/static/src/js/menu/customize.js',
'website/static/src/js/menu/debug_manager.js',
'website/static/src/js/menu/debug_menu.js',
'website/static/src/js/menu/edit.js',
'website/static/src/js/menu/mobile_view.js',
'website/static/src/js/menu/new_content.js',
@@ -1,21 +0,0 @@
odoo.define('website.debugManager', function (require) {
'use strict';
var config = require('web.config');
var DebugManager = require('web.DebugManager');
var websiteNavbarData = require('website.navbar');
var DebugManagerMenu = websiteNavbarData.WebsiteNavbar.include({
/**
* @override
*/
start: function () {
if (config.isDebug()) {
new DebugManager(this).prependTo(this.$('.o_menu_systray'));
}
return this._super.apply(this, arguments);
},
});
return DebugManagerMenu;
});
@@ -0,0 +1,20 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { DebugMenu } from "@web/core/debug/debug_menu";
import { createDebugContext } from "@web/core/debug/debug_context";
const debugMenuService = {
dependencies: ["orm"],
start(env) {
Object.assign(env, createDebugContext(env, { categories: ["default"] }));
const systray = document.querySelector('.o_menu_systray');
if (systray) {
owl.mount(DebugMenu, {
target: systray,
position: 'first-child',
env,
});
}
}
}
registry.category("services").add("website_debug_menu", debugMenuService);