[IMP] web: improve debug Menu extensibility
This commit renames the DebugManager to "DebugMenu", changes the
structure of its registry and the signature of its hook (useDebugMenu).
In the previous DebugManager:
- the "debug" registry was flat;
- new items were declared and created with the "useDebugManager" hook;
- these items were arbitrary functions returning a set of items.
The problem with that system was its extensibility, meaning that no
external module could hook on these arbitrary functions passed to the
hook.
In the new DebugMenu:
- the "debug" registry has multiple levels:
> root: "global" items, found in most debug menus
> subcategories: each subcategory has a name corresponding to the
context it's instantiated in (e.g.: the "view" subcategory will only
contain views-related debug menu items)
- all items are pre-declared in their corresponding category, and either
return a debug menu item or null;
- the hook activates/deactivates categories, and passes a context which
will be used by the item factories (passed as argument).
This commit is contained in:
committed by
Aaron Bohy
parent
4377241d8c
commit
a688f13cd4
@@ -7,54 +7,60 @@ import { registry } from "../registry";
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
const { Component } = owl;
|
||||
let debugElementsId = 1;
|
||||
|
||||
export class DebugManager extends Component {
|
||||
export class DebugMenu extends Component {
|
||||
setup() {
|
||||
this.debugFactories = {};
|
||||
const globalContext = { env: this.env };
|
||||
this.activeCategories = new Set();
|
||||
this.contexts = new Map([["global", globalContext]]);
|
||||
this.debugService = useService("debug");
|
||||
this.hasAccessRights = false;
|
||||
// Defined as arrow to be passed as prop
|
||||
// @ts-ignore
|
||||
this.beforeOpenDropdown = async () => {
|
||||
if (!this.accessRights) {
|
||||
this.accessRights = await this.debugService.getAccessRights();
|
||||
if (!this.hasAccessRights) {
|
||||
this.hasAccessRights = true;
|
||||
const accessRights = await this.debugService.getAccessRights();
|
||||
Object.assign(globalContext, { accessRights });
|
||||
}
|
||||
};
|
||||
this.env.bus.on("DEBUG-MANAGER:NEW-ITEMS", this, (payload) => {
|
||||
const { inDialog, elementsId, elementsFactory } = payload;
|
||||
if (this.isInDialog === inDialog) {
|
||||
this.debugFactories[elementsId] = elementsFactory;
|
||||
this.env.bus.on("DEBUG-MANAGER:ADD-CONTEXT", this, (payload) => {
|
||||
const { category, context, inDialog, itemId } = payload;
|
||||
if (this.env.inDialog === inDialog) {
|
||||
this.contexts.set(itemId, context);
|
||||
this.activeCategories.add(category);
|
||||
}
|
||||
});
|
||||
this.env.bus.on("DEBUG-MANAGER:REMOVE-ITEMS", this, (payload) => {
|
||||
const { inDialog, elementsId } = payload;
|
||||
if (this.isInDialog === inDialog) {
|
||||
delete this.debugFactories[elementsId];
|
||||
this.env.bus.on("DEBUG-MANAGER:REMOVE-CONTEXT", this, (payload) => {
|
||||
const { category, inDialog, itemId } = payload;
|
||||
if (this.env.inDialog === inDialog) {
|
||||
this.contexts.delete(itemId);
|
||||
this.activeCategories.delete(category);
|
||||
}
|
||||
});
|
||||
if (!this.isInDialog) {
|
||||
this.debugFactories.global = () =>
|
||||
debugRegistry.getAll().map((elFactory) => elFactory(this.env));
|
||||
}
|
||||
}
|
||||
|
||||
get isInDialog() {
|
||||
return this.env.inDialog;
|
||||
}
|
||||
|
||||
getElements() {
|
||||
if (Object.keys(this.debugFactories).length > 0) {
|
||||
const sortedElements = Object.values(this.debugFactories)
|
||||
.map((factory) => factory(this.accessRights))
|
||||
.reduce((acc, elements) => acc.concat(elements))
|
||||
.sort((x, y) => {
|
||||
const xSeq = x.sequence ? x.sequence : 1000;
|
||||
const ySeq = y.sequence ? y.sequence : 1000;
|
||||
return xSeq - ySeq;
|
||||
});
|
||||
return sortedElements;
|
||||
} else {
|
||||
return [];
|
||||
const factories = [];
|
||||
const context = Object.assign({}, ...this.contexts.values());
|
||||
// If not in dialog => gets root (generic) factories
|
||||
if (!this.env.inDialog) {
|
||||
factories.push(...debugRegistry.getAll());
|
||||
}
|
||||
// Retrieves the active categories factories
|
||||
for (const category of this.activeCategories) {
|
||||
factories.push(...debugRegistry.category(category).getAll());
|
||||
}
|
||||
// Builds, filters and sorts all items
|
||||
return factories
|
||||
.map((factory) => factory(context))
|
||||
.filter(Boolean)
|
||||
.sort((x, y) => {
|
||||
const xSeq = x.sequence ? x.sequence : 1000;
|
||||
const ySeq = y.sequence ? y.sequence : 1000;
|
||||
return xSeq - ySeq;
|
||||
});
|
||||
}
|
||||
|
||||
onDropdownItemSelected(ev) {
|
||||
@@ -68,20 +74,22 @@ export class DebugManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DebugManager.debugElementsId = 1;
|
||||
DebugManager.template = "web.DebugManager";
|
||||
DebugMenu.template = "web.DebugMenu";
|
||||
|
||||
export function useDebugManager(elementsFactory) {
|
||||
const elementsId = DebugManager.debugElementsId++;
|
||||
export function useDebugMenu(category, context = {}) {
|
||||
const component = Component.current;
|
||||
const env = component.env;
|
||||
const payload = {
|
||||
elementsId,
|
||||
elementsFactory,
|
||||
category,
|
||||
context,
|
||||
inDialog: env.inDialog,
|
||||
itemId: debugElementsId++,
|
||||
};
|
||||
useEffect(() => {
|
||||
env.bus.trigger("DEBUG-MANAGER:NEW-ITEMS", payload);
|
||||
return () => env.bus.trigger("DEBUG-MANAGER:REMOVE-ITEMS", payload);
|
||||
}, () => []);
|
||||
useEffect(
|
||||
() => {
|
||||
env.bus.trigger("DEBUG-MANAGER:ADD-CONTEXT", payload);
|
||||
return () => env.bus.trigger("DEBUG-MANAGER:REMOVE-CONTEXT", payload);
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.DebugManager" owl="1">
|
||||
<t t-name="web.DebugMenu" owl="1">
|
||||
<Dropdown class="o_debug_manager"
|
||||
beforeOpen="beforeOpenDropdown"
|
||||
t-on-dropdown-item-selected.stop="onDropdownItemSelected"
|
||||
|
||||
@@ -4,10 +4,8 @@ import { browser } from "../browser/browser";
|
||||
import { routeToUrl } from "../browser/router_service";
|
||||
import { registry } from "../registry";
|
||||
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
// Backend Debug Manager Items
|
||||
export function runJSTestsItem(env) {
|
||||
function runJSTestsItem({ env }) {
|
||||
const runTestsURL = browser.location.origin + "/web/tests?mod=*";
|
||||
return {
|
||||
type: "item",
|
||||
@@ -20,7 +18,7 @@ export function runJSTestsItem(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function runJSTestsMobileItem(env) {
|
||||
function runJSTestsMobileItem({ env }) {
|
||||
const runTestsMobileURL = browser.location.origin + "/web/tests/mobile?mod=*";
|
||||
return {
|
||||
type: "item",
|
||||
@@ -33,7 +31,7 @@ export function runJSTestsMobileItem(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function openViewItem(env) {
|
||||
function openViewItem({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Open View"),
|
||||
@@ -48,14 +46,14 @@ export function openViewItem(env) {
|
||||
}
|
||||
|
||||
// Global Debug Manager Items
|
||||
export function globalSeparator(env) {
|
||||
function globalSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 400,
|
||||
};
|
||||
}
|
||||
|
||||
export function activateAssetsDebugging(env) {
|
||||
function activateAssetsDebugging({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Activate Assets Debugging"),
|
||||
@@ -66,7 +64,7 @@ export function activateAssetsDebugging(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function activateTestsAssetsDebugging(env) {
|
||||
function activateTestsAssetsDebugging({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Activate Tests Assets Debugging"),
|
||||
@@ -77,7 +75,7 @@ export function activateTestsAssetsDebugging(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function regenerateAssets(env) {
|
||||
export function regenerateAssets({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Regenerate Assets Bundles"),
|
||||
@@ -97,7 +95,7 @@ export function regenerateAssets(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function becomeSuperuser(env) {
|
||||
function becomeSuperuser({ env }) {
|
||||
const becomeSuperuserULR = browser.location.origin + "/web/become";
|
||||
return {
|
||||
type: "item",
|
||||
@@ -112,7 +110,7 @@ export function becomeSuperuser(env) {
|
||||
};
|
||||
}
|
||||
|
||||
export function leaveDebugMode(env) {
|
||||
function leaveDebugMode({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Leave the Developer Tools"),
|
||||
@@ -125,21 +123,16 @@ export function leaveDebugMode(env) {
|
||||
};
|
||||
}
|
||||
|
||||
const backendDebugManagerItems = [runJSTestsItem, runJSTestsMobileItem, openViewItem];
|
||||
|
||||
for (let item of backendDebugManagerItems) {
|
||||
debugRegistry.add(item.name, item);
|
||||
}
|
||||
|
||||
const globalDebugManagerItems = [
|
||||
globalSeparator,
|
||||
activateAssetsDebugging,
|
||||
regenerateAssets,
|
||||
becomeSuperuser,
|
||||
leaveDebugMode,
|
||||
activateTestsAssetsDebugging,
|
||||
];
|
||||
|
||||
for (let item of globalDebugManagerItems) {
|
||||
debugRegistry.add(item.name, item);
|
||||
}
|
||||
registry
|
||||
.category("debug")
|
||||
// Backend
|
||||
.add("runJSTestsItem", runJSTestsItem)
|
||||
.add("runJSTestsMobileItem", runJSTestsMobileItem)
|
||||
.add("openViewItem", openViewItem)
|
||||
// Global
|
||||
.add("globalSeparator", globalSeparator)
|
||||
.add("activateAssetsDebugging", activateAssetsDebugging)
|
||||
.add("regenerateAssets", regenerateAssets)
|
||||
.add("becomeSuperuser", becomeSuperuser)
|
||||
.add("leaveDebugMode", leaveDebugMode)
|
||||
.add("activateTestsAssetsDebugging", activateTestsAssetsDebugging);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.DebugManager.SetDefaultFooter" owl="1">
|
||||
<t t-name="web.DebugMenu.SetDefaultFooter" owl="1">
|
||||
<button class="btn btn-secondary" t-on-click="trigger('dialog-closed')">Close</button>
|
||||
<button class="btn btn-secondary" t-on-click="saveDefault">Save default</button>
|
||||
</t>
|
||||
|
||||
<t t-name="web.DebugManager.setDefaultBody" owl="1">
|
||||
<t t-name="web.DebugMenu.setDefaultBody" owl="1">
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -62,7 +62,7 @@
|
||||
</table>
|
||||
</t>
|
||||
|
||||
<t t-name="web.DebugManager.getMetadataBody" owl="1">
|
||||
<t t-name="web.DebugMenu.getMetadataBody" owl="1">
|
||||
<table class="table table-sm table-striped">
|
||||
<tr>
|
||||
<th>ID:</th>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { DebugManager } from "./debug_menu";
|
||||
import { DebugMenu } from "./debug_menu";
|
||||
|
||||
export function editModelDebug(env, title, model, id) {
|
||||
return env.services.action.doAction({
|
||||
@@ -21,9 +21,7 @@ export const debugService = {
|
||||
start(env, { orm }) {
|
||||
let accessRightsProm;
|
||||
if (env.debug !== "") {
|
||||
registry
|
||||
.category("systray")
|
||||
.add("web.debug_mode_menu", DebugManager, { sequence: 100 });
|
||||
registry.category("systray").add("web.debug_mode_menu", DebugMenu, { sequence: 100 });
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import core from "web.core";
|
||||
import Context from "web.Context";
|
||||
import { useService } from "../core/service_hook";
|
||||
import { useEffect } from "../core/effect_hook";
|
||||
import { useBus } from "../core/bus_hook";
|
||||
import { ViewNotFoundError } from "../webclient/actions/action_service";
|
||||
import { useDebugManager } from "../core/debug/debug_menu";
|
||||
import { objectToQuery } from "../core/browser/router_service";
|
||||
import core from "web.core";
|
||||
import { ComponentAdapter } from "web.OwlCompatibility";
|
||||
import { mapDoActionOptionAPI } from "./utils";
|
||||
import { setupDebugAction, setupDebugViewForm, setupDebugView } from "./debug_manager";
|
||||
import { cleanDomFromBootstrap } from "./utils";
|
||||
import { objectToQuery } from "../core/browser/router_service";
|
||||
import { useBus } from "../core/bus_hook";
|
||||
import { useDebugMenu } from "../core/debug/debug_menu";
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { useEffect } from "../core/effect_hook";
|
||||
import { useService } from "../core/service_hook";
|
||||
import { ViewNotFoundError } from "../webclient/actions/action_service";
|
||||
import { cleanDomFromBootstrap, mapDoActionOptionAPI } from "./utils";
|
||||
|
||||
const { Component, tags } = owl;
|
||||
|
||||
@@ -154,9 +152,7 @@ class ActionAdapter extends ComponentAdapter {
|
||||
export class ClientActionAdapter extends ActionAdapter {
|
||||
setup() {
|
||||
super.setup();
|
||||
useDebugManager((accessRights) =>
|
||||
setupDebugAction(accessRights, this.wowlEnv, this.props.widgetArgs[0])
|
||||
);
|
||||
useDebugMenu("action", { action: this.props.widgetArgs[0] });
|
||||
this.env = Component.env;
|
||||
}
|
||||
|
||||
@@ -231,16 +227,13 @@ export class ViewAdapter extends ActionAdapter {
|
||||
this.shouldUpdateWidget = true;
|
||||
this.magicReload = useMagicLegacyReload();
|
||||
const envWowl = this.env;
|
||||
useDebugManager((accessRights) =>
|
||||
setupDebugAction(accessRights, envWowl, this.props.viewParams.action)
|
||||
);
|
||||
useDebugManager((accessRights) =>
|
||||
setupDebugView(accessRights, envWowl, this, this.props.viewParams.action)
|
||||
);
|
||||
useDebugMenu("action", {
|
||||
action: this.props.viewParams.action,
|
||||
component: this,
|
||||
});
|
||||
useDebugMenu("view");
|
||||
if (this.props.viewInfo.type === "form") {
|
||||
useDebugManager((accessRights) =>
|
||||
setupDebugViewForm(envWowl, this, this.props.viewParams.action)
|
||||
);
|
||||
useDebugMenu("form");
|
||||
}
|
||||
if (!envWowl.inDialog) {
|
||||
useBus(envWowl.bus, "ACTION_MANAGER:UPDATE", (info) => {
|
||||
|
||||
@@ -1,34 +1,59 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { editModelDebug } from "../core/debug/debug_service";
|
||||
import { formatDateTime, parseDateTime } from "../core/l10n/dates";
|
||||
import { formatMany2one } from "../fields/format";
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { formatDateTime, parseDateTime } from "../core/l10n/dates";
|
||||
import { _lt } from "../core/l10n/translation";
|
||||
import { registry } from "../core/registry";
|
||||
import { formatMany2one } from "../fields/format";
|
||||
import { json_node_to_xml } from "../views/view_utils";
|
||||
|
||||
const { hooks, tags } = owl;
|
||||
const { useState } = hooks;
|
||||
|
||||
export function setupDebugAction(accessRights, env, action) {
|
||||
const actionSeparator = {
|
||||
// Action items
|
||||
|
||||
function actionSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 100,
|
||||
};
|
||||
}
|
||||
|
||||
let description = env._t("Edit Action");
|
||||
const editAction = {
|
||||
function accessSeparator({ accessRights, action }) {
|
||||
const { canSeeModelAccess, canSeeRecordRules } = accessRights;
|
||||
if (!action.res_model || (!canSeeModelAccess && !canSeeRecordRules)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 200,
|
||||
};
|
||||
}
|
||||
|
||||
function editAction({ action, env }) {
|
||||
if (!action.id) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Edit Action");
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, action.type, action.id);
|
||||
},
|
||||
sequence: 110,
|
||||
};
|
||||
}
|
||||
|
||||
description = env._t("View Fields");
|
||||
const viewFields = {
|
||||
function viewFields({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Fields");
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
@@ -51,11 +76,16 @@ export function setupDebugAction(accessRights, env, action) {
|
||||
},
|
||||
sequence: 120,
|
||||
};
|
||||
}
|
||||
|
||||
description = env._t("Manage Filters");
|
||||
const manageFilters = {
|
||||
function manageFilters({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Manage Filters");
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: () => {
|
||||
// manage_filters
|
||||
env.services.action.doAction({
|
||||
@@ -74,8 +104,13 @@ export function setupDebugAction(accessRights, env, action) {
|
||||
},
|
||||
sequence: 130,
|
||||
};
|
||||
}
|
||||
|
||||
const technicalTranslation = {
|
||||
function technicalTranslation({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Technical Translation"),
|
||||
callback: async () => {
|
||||
@@ -88,16 +123,16 @@ export function setupDebugAction(accessRights, env, action) {
|
||||
},
|
||||
sequence: 140,
|
||||
};
|
||||
}
|
||||
|
||||
const accessSeparator = {
|
||||
type: "separator",
|
||||
sequence: 200,
|
||||
};
|
||||
|
||||
description = env._t("View Access Rights");
|
||||
const viewAccessRights = {
|
||||
function viewAccessRights({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeModelAccess) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Access Rights");
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
@@ -120,9 +155,14 @@ export function setupDebugAction(accessRights, env, action) {
|
||||
},
|
||||
sequence: 210,
|
||||
};
|
||||
}
|
||||
|
||||
description = env._t("Model Record Rules");
|
||||
const viewRecordRules = {
|
||||
function viewRecordRules({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeRecordRules) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Model Record Rules");
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("View Record Rules"),
|
||||
callback: async () => {
|
||||
@@ -147,26 +187,6 @@ export function setupDebugAction(accessRights, env, action) {
|
||||
},
|
||||
sequence: 220,
|
||||
};
|
||||
|
||||
const result = [actionSeparator];
|
||||
if (action.id) {
|
||||
result.push(editAction);
|
||||
}
|
||||
if (action.res_model) {
|
||||
result.push(viewFields);
|
||||
result.push(manageFilters);
|
||||
result.push(technicalTranslation);
|
||||
if (accessRights.canSeeModelAccess || accessRights.canSeeRecordRules) {
|
||||
result.push(accessSeparator);
|
||||
if (accessRights.canSeeModelAccess) {
|
||||
result.push(viewAccessRights);
|
||||
}
|
||||
if (accessRights.canSeeRecordRules) {
|
||||
result.push(viewRecordRules);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class FieldViewGetDialog extends Dialog {}
|
||||
@@ -207,7 +227,7 @@ class GetMetadataDialog extends Dialog {
|
||||
this.state.write_date = formatDateTime(parseDateTime(metadata.write_date));
|
||||
}
|
||||
}
|
||||
GetMetadataDialog.bodyTemplate = "web.DebugManager.getMetadataBody";
|
||||
GetMetadataDialog.bodyTemplate = "web.DebugMenu.getMetadataBody";
|
||||
GetMetadataDialog.title = _lt("View Metadata");
|
||||
|
||||
class SetDefaultDialog extends Dialog {
|
||||
@@ -351,17 +371,19 @@ class SetDefaultDialog extends Dialog {
|
||||
this.trigger("dialog-closed");
|
||||
}
|
||||
}
|
||||
SetDefaultDialog.bodyTemplate = "web.DebugManager.setDefaultBody";
|
||||
SetDefaultDialog.footerTemplate = "web.DebugManager.SetDefaultFooter";
|
||||
SetDefaultDialog.bodyTemplate = "web.DebugMenu.setDefaultBody";
|
||||
SetDefaultDialog.footerTemplate = "web.DebugMenu.SetDefaultFooter";
|
||||
SetDefaultDialog.title = _lt("Set Default");
|
||||
|
||||
export function setupDebugView(accessRights, env, component, action) {
|
||||
const viewId = component.props.viewInfo.view_id;
|
||||
const viewSeparator = {
|
||||
function viewSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 300,
|
||||
};
|
||||
const fieldsViewGet = {
|
||||
}
|
||||
|
||||
function fieldsViewGet({ component, env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Fields View Get"),
|
||||
callback: () => {
|
||||
@@ -372,57 +394,72 @@ export function setupDebugView(accessRights, env, component, action) {
|
||||
},
|
||||
sequence: 340,
|
||||
};
|
||||
}
|
||||
|
||||
export function editView({ accessRights, action, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
const { viewId, viewType } = component.widget;
|
||||
const displayName = action.views
|
||||
.find((v) => v.type === component.widget.viewType)
|
||||
.find((v) => v.type === viewType)
|
||||
.name.toString();
|
||||
let description = env._t("Edit View: ") + displayName;
|
||||
const editView = {
|
||||
const description = env._t("Edit View: ") + displayName;
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, "ir.ui.view", viewId);
|
||||
},
|
||||
sequence: 350,
|
||||
};
|
||||
description = env._t("Edit ControlPanelView");
|
||||
const editControlPanelView = {
|
||||
}
|
||||
|
||||
function editControlPanelView({ accessRights, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Edit ControlPanelView");
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(
|
||||
env,
|
||||
description,
|
||||
"ir.ui.view",
|
||||
component.props.viewParams.action.controlPanelFieldsView.view_id
|
||||
component.props.viewInfo.view_id
|
||||
);
|
||||
},
|
||||
sequence: 360,
|
||||
};
|
||||
const result = [viewSeparator, fieldsViewGet];
|
||||
if (accessRights.canEditView) {
|
||||
result.push(editView);
|
||||
result.push(editControlPanelView);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function setupDebugViewForm(env, component, action) {
|
||||
const setDefaults = {
|
||||
// Form view itemss
|
||||
|
||||
function setDefaults({ action, component, env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Set Defaults"),
|
||||
callback: () => {
|
||||
env.services.dialog.open(SetDefaultDialog, {
|
||||
res_model: action.res_model,
|
||||
component: component,
|
||||
component,
|
||||
});
|
||||
},
|
||||
sequence: 310,
|
||||
};
|
||||
const viewMetadata = {
|
||||
}
|
||||
|
||||
function viewMetadata({ action, component, env }) {
|
||||
const selectedIds = component.widget.getSelectedIds();
|
||||
if (selectedIds.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("View Metadata"),
|
||||
callback: () => {
|
||||
const selectedIds = component.widget.getSelectedIds();
|
||||
env.services.dialog.open(GetMetadataDialog, {
|
||||
res_model: action.res_model,
|
||||
selectedIds,
|
||||
@@ -430,12 +467,19 @@ export function setupDebugViewForm(env, component, action) {
|
||||
},
|
||||
sequence: 320,
|
||||
};
|
||||
}
|
||||
|
||||
function manageAttachments({ action, component, env }) {
|
||||
const selectedIds = component.widget.getSelectedIds();
|
||||
const description = env._t("Manage Attachments");
|
||||
const manageAttachments = {
|
||||
if (selectedIds.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: description,
|
||||
description,
|
||||
callback: () => {
|
||||
const selectedId = component.widget.getSelectedIds()[0];
|
||||
const selectedId = selectedIds[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.attachment",
|
||||
name: description,
|
||||
@@ -456,10 +500,30 @@ export function setupDebugViewForm(env, component, action) {
|
||||
},
|
||||
sequence: 330,
|
||||
};
|
||||
const result = [setDefaults];
|
||||
if (component.widget.getSelectedIds().length === 1) {
|
||||
result.push(viewMetadata);
|
||||
result.push(manageAttachments);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
debugRegistry
|
||||
.category("action")
|
||||
.add("actionSeparator", actionSeparator)
|
||||
.add("editAction", editAction)
|
||||
.add("viewFields", viewFields)
|
||||
.add("manageFilters", manageFilters)
|
||||
.add("technicalTranslation", technicalTranslation)
|
||||
.add("accessSeparator", accessSeparator)
|
||||
.add("viewAccessRights", viewAccessRights)
|
||||
.add("viewRecordRules", viewRecordRules)
|
||||
|
||||
debugRegistry
|
||||
.category("view")
|
||||
.add("viewSeparator", viewSeparator)
|
||||
.add("fieldsViewGet", fieldsViewGet)
|
||||
.add("editView", editView)
|
||||
.add("editControlPanelView", editControlPanelView)
|
||||
|
||||
debugRegistry
|
||||
.category("form")
|
||||
.add("setDefaults", setDefaults)
|
||||
.add("viewMetadata", viewMetadata)
|
||||
.add("manageAttachments", manageAttachments);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "../../core/dialog/dialog";
|
||||
import { DebugManager } from "../../core/debug/debug_menu";
|
||||
import { DebugMenu } from "../../core/debug/debug_menu";
|
||||
import { useEffect } from "../../core/effect_hook";
|
||||
|
||||
const { hooks } = owl;
|
||||
@@ -25,7 +25,7 @@ class ActionDialog extends Dialog {
|
||||
this.title = "title" in this.props ? this.props.title : this.constructor.title;
|
||||
}
|
||||
}
|
||||
ActionDialog.components = { ...Dialog.components, DebugManager };
|
||||
ActionDialog.components = { ...Dialog.components, DebugMenu };
|
||||
ActionDialog.template = "web.ActionDialog";
|
||||
ActionDialog.bodyTemplate = "web.ActionDialogBody";
|
||||
ActionDialog.props = {
|
||||
@@ -52,18 +52,21 @@ class LegacyAdaptedActionDialog extends ActionDialog {
|
||||
const ControllerComponent = this.props && this.props.ActionComponent;
|
||||
const Controller = ControllerComponent && ControllerComponent.Component;
|
||||
this.isLegacy = Controller && Controller.isLegacy;
|
||||
useEffect(() => {
|
||||
if (this.isLegacy) {
|
||||
// Retrieve the widget climbing the wrappers
|
||||
const componentController = this.actionRef.comp;
|
||||
const controller = componentController.componentRef.comp;
|
||||
const viewAdapter = controller.controllerRef.comp;
|
||||
const widget = viewAdapter.widget;
|
||||
// Render legacy footer buttons
|
||||
const footer = this.modalRef.el.querySelector("footer");
|
||||
widget.renderButtons($(footer));
|
||||
}
|
||||
}, () => []); // TODO: should this depend on actionRef.comp?
|
||||
useEffect(
|
||||
() => {
|
||||
if (this.isLegacy) {
|
||||
// Retrieve the widget climbing the wrappers
|
||||
const componentController = this.actionRef.comp;
|
||||
const controller = componentController.componentRef.comp;
|
||||
const viewAdapter = controller.controllerRef.comp;
|
||||
const widget = viewAdapter.widget;
|
||||
// Render legacy footer buttons
|
||||
const footer = this.modalRef.el.querySelector("footer");
|
||||
widget.renderButtons($(footer));
|
||||
}
|
||||
},
|
||||
() => []
|
||||
); // TODO: should this depend on actionRef.comp?
|
||||
}
|
||||
}
|
||||
LegacyAdaptedActionDialog.footerTemplate = "web.LegacyAdaptedActionDialogFooter";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="web.ActionDialog" t-inherit="web.Dialog" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//header/h4[contains(concat(' ',normalize-space(@class),' '),' modal-title ')]" position="before">
|
||||
<DebugManager t-if="env.debug" />
|
||||
<DebugMenu t-if="env.debug" />
|
||||
</xpath>
|
||||
<xpath expr="//main[@class='modal-body']" position="attributes">
|
||||
<attribute name="t-att-class">
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function startClickEverywhere(xmlId, appsMenusOnly) {
|
||||
window.clickEverywhere(xmlId, appsMenusOnly);
|
||||
}
|
||||
|
||||
function runClickTestItem(env) {
|
||||
function runClickTestItem({ env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Run Click Everywhere Test"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { DebugManager, useDebugManager } from "@web/core/debug/debug_menu";
|
||||
import { DebugMenu, useDebugMenu } from "@web/core/debug/debug_menu";
|
||||
import { regenerateAssets } from "@web/core/debug/debug_menu_items";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { debugService } from "@web/core/debug/debug_service";
|
||||
@@ -21,7 +21,7 @@ const debugRegistry = registry.category("debug");
|
||||
let target;
|
||||
let testConfig;
|
||||
|
||||
QUnit.module("DebugManager", (hooks) => {
|
||||
QUnit.module("DebugMenu", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
target = getFixture();
|
||||
registry
|
||||
@@ -38,61 +38,62 @@ QUnit.module("DebugManager", (hooks) => {
|
||||
testConfig = { mockRPC };
|
||||
});
|
||||
QUnit.test("can be rendered", async (assert) => {
|
||||
debugRegistry.add("item_1", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 1",
|
||||
callback: () => {
|
||||
assert.step("callback item_1");
|
||||
},
|
||||
sequence: 10,
|
||||
};
|
||||
});
|
||||
debugRegistry.add("item_2", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 2",
|
||||
callback: () => {
|
||||
assert.step("callback item_2");
|
||||
},
|
||||
sequence: 5,
|
||||
};
|
||||
});
|
||||
debugRegistry.add("item_3", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 3",
|
||||
callback: () => {
|
||||
assert.step("callback item_3");
|
||||
},
|
||||
};
|
||||
});
|
||||
debugRegistry.add("separator", () => {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 20,
|
||||
};
|
||||
});
|
||||
debugRegistry.add("separator_2", () => {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 7,
|
||||
hide: true,
|
||||
};
|
||||
});
|
||||
debugRegistry.add("item_4", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 4",
|
||||
callback: () => {
|
||||
assert.step("callback item_4");
|
||||
},
|
||||
hide: true,
|
||||
sequence: 10,
|
||||
};
|
||||
});
|
||||
debugRegistry
|
||||
.add("item_1", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 1",
|
||||
callback: () => {
|
||||
assert.step("callback item_1");
|
||||
},
|
||||
sequence: 10,
|
||||
};
|
||||
})
|
||||
.add("item_2", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 2",
|
||||
callback: () => {
|
||||
assert.step("callback item_2");
|
||||
},
|
||||
sequence: 5,
|
||||
};
|
||||
})
|
||||
.add("item_3", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 3",
|
||||
callback: () => {
|
||||
assert.step("callback item_3");
|
||||
},
|
||||
};
|
||||
})
|
||||
.add("separator", () => {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 20,
|
||||
};
|
||||
})
|
||||
.add("separator_2", () => {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 7,
|
||||
hide: true,
|
||||
};
|
||||
})
|
||||
.add("item_4", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 4",
|
||||
callback: () => {
|
||||
assert.step("callback item_4");
|
||||
},
|
||||
hide: true,
|
||||
sequence: 10,
|
||||
};
|
||||
});
|
||||
const env = await makeTestEnv(testConfig);
|
||||
const debugManager = await mount(DebugManager, { env, target });
|
||||
const debugManager = await mount(DebugMenu, { env, target });
|
||||
registerCleanup(() => debugManager.destroy());
|
||||
let debugManagerEl = debugManager.el;
|
||||
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
|
||||
@@ -117,7 +118,7 @@ QUnit.module("DebugManager", (hooks) => {
|
||||
assert.verifySteps(["callback item_2", "callback item_1", "callback item_3"]);
|
||||
});
|
||||
|
||||
QUnit.test("Don't display the DebugManager if debug mode is disabled", async (assert) => {
|
||||
QUnit.test("Don't display the DebugMenu if debug mode is disabled", async (assert) => {
|
||||
const dialogContainer = document.createElement("div");
|
||||
dialogContainer.classList.add("o_dialog_container");
|
||||
target.append(dialogContainer);
|
||||
@@ -132,8 +133,9 @@ QUnit.module("DebugManager", (hooks) => {
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"Display the DebugManager correctly in a ActionDialog if debug mode is enabled",
|
||||
"Display the DebugMenu correctly in a ActionDialog if debug mode is enabled",
|
||||
async (assert) => {
|
||||
assert.expect(8);
|
||||
const dialogContainer = document.createElement("div");
|
||||
dialogContainer.classList.add("o_dialog_container");
|
||||
target.append(dialogContainer);
|
||||
@@ -147,26 +149,33 @@ QUnit.module("DebugManager", (hooks) => {
|
||||
sequence: 0,
|
||||
};
|
||||
});
|
||||
const item1 = {
|
||||
type: "item",
|
||||
description: "Item 1",
|
||||
callback: () => {
|
||||
assert.step("callback item_1");
|
||||
},
|
||||
sequence: 10,
|
||||
};
|
||||
const item2 = {
|
||||
type: "item",
|
||||
description: "Item 2",
|
||||
callback: () => {
|
||||
assert.step("callback item_2");
|
||||
},
|
||||
sequence: 20,
|
||||
};
|
||||
debugRegistry
|
||||
.category("custom")
|
||||
.add("item1", () => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 1",
|
||||
callback: () => {
|
||||
assert.step("callback item_1");
|
||||
},
|
||||
sequence: 10,
|
||||
};
|
||||
})
|
||||
.add("item2", ({ customKey }) => {
|
||||
return {
|
||||
type: "item",
|
||||
description: "Item 2",
|
||||
callback: () => {
|
||||
assert.step("callback item_2");
|
||||
assert.strictEqual(customKey, "abc");
|
||||
},
|
||||
sequence: 20,
|
||||
};
|
||||
});
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
useSubEnv({ inDialog: true });
|
||||
useDebugManager(() => [item1, item2]);
|
||||
useDebugMenu("custom", { customKey: "abc" });
|
||||
}
|
||||
}
|
||||
Parent.components = { ActionDialog };
|
||||
@@ -224,7 +233,7 @@ QUnit.module("DebugManager", (hooks) => {
|
||||
registry.category("services").add("localization", makeFakeLocalizationService());
|
||||
debugRegistry.add("regenerateAssets", regenerateAssets);
|
||||
const env = await makeTestEnv(testConfig);
|
||||
const debugManager = await mount(DebugManager, { env, target });
|
||||
const debugManager = await mount(DebugMenu, { env, target });
|
||||
registerCleanup(() => debugManager.destroy());
|
||||
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
|
||||
assert.containsOnce(debugManager.el, "ul.o_dropdown_menu li.o_dropdown_item");
|
||||
|
||||
@@ -13,6 +13,7 @@ export function clearRegistryWithCleanup(registry) {
|
||||
content: {},
|
||||
elements: null,
|
||||
entries: null,
|
||||
subRegistries: {},
|
||||
// Preserve OnUpdate handlers
|
||||
subscriptions: { UPDATE: [...registry.subscriptions.UPDATE] },
|
||||
};
|
||||
@@ -24,6 +25,7 @@ function cloneRegistryWithCleanup(registry) {
|
||||
content: { ...registry.content },
|
||||
elements: null,
|
||||
entries: null,
|
||||
subRegistries: {},
|
||||
// Preserve OnUpdate handlers
|
||||
subscriptions: { UPDATE: [...registry.subscriptions.UPDATE] },
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { clearRegistryWithCleanup } from "../../helpers/mock_env";
|
||||
import { createWebClient, doAction, getActionManagerTestConfig } from "./helpers";
|
||||
|
||||
import { ClientActionAdapter } from "@web/legacy/action_adapters";
|
||||
import { useDebugManager } from "@web/core/debug/debug_menu";
|
||||
import { useDebugMenu } from "@web/core/debug/debug_menu";
|
||||
import { debugService } from "@web/core/debug/debug_service";
|
||||
|
||||
import ControlPanel from "web.ControlPanel";
|
||||
@@ -101,19 +101,23 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
|
||||
patchWithCleanup(ClientActionAdapter.prototype, {
|
||||
setup() {
|
||||
useDebugManager((accessRights) => {
|
||||
assert.step("debugItems executed");
|
||||
assert.ok(this.widget);
|
||||
return [];
|
||||
});
|
||||
useDebugMenu("custom", { widget: this });
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
|
||||
registry
|
||||
.category("debug")
|
||||
.category("custom")
|
||||
.add("item1", ({ widget }) => {
|
||||
assert.step("debugItems executed");
|
||||
assert.ok(widget);
|
||||
return {};
|
||||
});
|
||||
registry.category("services").add("debug", debugService);
|
||||
testConfig.debug = true;
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
const mockRPC = (route) => {
|
||||
if (route.includes("check_access_rights")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { debugService } from "@web/core/debug/debug_service";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { editView } from "@web/legacy/debug_manager";
|
||||
import { clearUncommittedChanges } from "@web/webclient/actions/action_service";
|
||||
import testUtils from "web.test_utils";
|
||||
import ListController from "web.ListController";
|
||||
import AbstractView from "web.AbstractView";
|
||||
import legacyViewRegistry from "web.view_registry";
|
||||
import FormView from "web.FormView";
|
||||
import ListController from "web.ListController";
|
||||
import testUtils from "web.test_utils";
|
||||
import legacyViewRegistry from "web.view_registry";
|
||||
import { clearRegistryWithCleanup } from "../../helpers/mock_env";
|
||||
import { makeFakeUserService } from "../../helpers/mock_services";
|
||||
import { click, legacyExtraNextTick, nextTick } from "../../helpers/utils";
|
||||
@@ -2227,8 +2228,9 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.expect(2);
|
||||
|
||||
serviceRegistry.add("debug", debugService);
|
||||
registry.category("debug").category("view").add("editView", editView);
|
||||
testConfig.debug = "1";
|
||||
const mockRPC = async (route, args) => {
|
||||
const mockRPC = async (route) => {
|
||||
if (route.includes("check_access_rights")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import { registry } from "@web/core/registry";
|
||||
import ToursDialog from "@web_tour/debug/tour_dialog_component";
|
||||
import utils from "web_tour.utils";
|
||||
|
||||
function debugDisableTourItem(env) {
|
||||
function disableTours({ env }) {
|
||||
if (!env.services.user.isSystem) {
|
||||
return {};
|
||||
return null;
|
||||
}
|
||||
const activeTours = env.services.tour.getActiveTours();
|
||||
if (activeTours.length === 0) {
|
||||
return {};
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
@@ -23,13 +23,13 @@ function debugDisableTourItem(env) {
|
||||
}
|
||||
browser.location.reload();
|
||||
},
|
||||
sequence: 33,
|
||||
sequence: 50,
|
||||
};
|
||||
}
|
||||
|
||||
function debugStartTourItem(env) {
|
||||
function startTour({ env }) {
|
||||
if (!env.services.user.isSystem) {
|
||||
return {};
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
@@ -37,11 +37,11 @@ function debugStartTourItem(env) {
|
||||
callback: async () => {
|
||||
env.services.dialog.open(ToursDialog);
|
||||
},
|
||||
sequence: 32,
|
||||
sequence: 60,
|
||||
};
|
||||
}
|
||||
|
||||
registry
|
||||
.category("debug")
|
||||
.add("web_tour.start_tour", debugStartTourItem)
|
||||
.add("web_tour.disable_tour", debugDisableTourItem);
|
||||
.add("web_tour.startTour", startTour)
|
||||
.add("web_tour.disableTours", disableTours);
|
||||
|
||||
Reference in New Issue
Block a user