[IMP] web: report module loading errors in the DOM
Previously, if there were module loading errors, they would typically happen before the error service was ready, and so there is almost always no user-facing error that shows up even though the application state may be severely corrupted. This can be very confusing for developers who are not used to working with JS and might not check the devtools console. This commit makes it so that the module system will replace the contents of the body with an error when some modules were unable to be loaded, with the list of modules and the reason, so that the developer is not confused as to why things aren't working as expected. closes odoo/odoo#104155 Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
var factories = Object.create(null);
|
||||
var jobDeps = [];
|
||||
var jobPromises = [];
|
||||
const failed = [];
|
||||
|
||||
var services = Object.create({});
|
||||
|
||||
@@ -75,41 +76,23 @@
|
||||
});
|
||||
},
|
||||
getWaitedJobs: function () {
|
||||
return jobs
|
||||
.map(function (job) {
|
||||
return job.name;
|
||||
})
|
||||
.filter(function (item, index, self) {
|
||||
// uniq
|
||||
return self.indexOf(item) === index;
|
||||
});
|
||||
return new Set(jobs.map((job) => job.name));
|
||||
},
|
||||
getMissingJobs: function () {
|
||||
var self = this;
|
||||
var waited = this.getWaitedJobs();
|
||||
var missing = [];
|
||||
waited.forEach(function (job) {
|
||||
self.getDependencies(job).forEach(function (job) {
|
||||
if (!(job in self.services)) {
|
||||
missing.push(job);
|
||||
getMissingJobs() {
|
||||
const waited = this.getWaitedJobs();
|
||||
const missing = new Set();
|
||||
for (const job of waited) {
|
||||
for (const dep of this.getDependencies(job)) {
|
||||
if (
|
||||
!(dep in this.services) &&
|
||||
!waited.has(dep) &&
|
||||
!failed.find((job) => job.name === dep)
|
||||
) {
|
||||
missing.add(dep);
|
||||
}
|
||||
});
|
||||
});
|
||||
return missing
|
||||
.filter(function (item, index, self) {
|
||||
return self.indexOf(item) === index;
|
||||
})
|
||||
.filter(function (item) {
|
||||
return waited.indexOf(item) < 0;
|
||||
})
|
||||
.filter(function (job) {
|
||||
return !job.error;
|
||||
});
|
||||
},
|
||||
getFailedJobs: function () {
|
||||
return jobs.filter(function (job) {
|
||||
return !!job.error;
|
||||
});
|
||||
}
|
||||
}
|
||||
return [...missing];
|
||||
},
|
||||
processJobs: function () {
|
||||
var job;
|
||||
@@ -120,6 +103,7 @@
|
||||
var jobExec;
|
||||
function onError(e) {
|
||||
job.error = e;
|
||||
failed.push(job);
|
||||
console.error(`Error while loading ${job.name}: ${e.message}`, e);
|
||||
}
|
||||
var def = new Promise(function (resolve) {
|
||||
@@ -254,7 +238,6 @@
|
||||
};
|
||||
odoo.log = function () {
|
||||
var missing = [];
|
||||
var failed = [];
|
||||
var cycle = null;
|
||||
|
||||
if (jobs.length) {
|
||||
@@ -305,7 +288,6 @@
|
||||
}
|
||||
}
|
||||
missing = odoo.__DEBUG__.getMissingJobs();
|
||||
failed = odoo.__DEBUG__.getFailedJobs();
|
||||
var unloaded = Object.keys(debugJobs) // Object.values is not supported
|
||||
.map(function (key) {
|
||||
return debugJobs[key];
|
||||
@@ -356,12 +338,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
odoo.__DEBUG__.jsModules = {
|
||||
const moduleInfo = {
|
||||
missing: missing,
|
||||
failed: failed.map((mod) => mod.name),
|
||||
unloaded: unloaded ? unloaded.map((mod) => mod.name) : [],
|
||||
cycle,
|
||||
};
|
||||
odoo.__DEBUG__.jsModules = moduleInfo;
|
||||
// The assets_tests bundle contains a lot of module whose dependencies are in other bundles
|
||||
// that may or may not be loaded. This global is used to silence module loading errors when
|
||||
// this bundle is present
|
||||
if (!window.__odooIgnoreModuleErrors) {
|
||||
displayModuleErrors(moduleInfo);
|
||||
}
|
||||
|
||||
didLogInfoResolve(true);
|
||||
};
|
||||
/**
|
||||
@@ -440,4 +430,60 @@
|
||||
// visit each root to find cycles
|
||||
return visitJobs(jobs.map((j) => j.name));
|
||||
}
|
||||
|
||||
function displayModuleErrors({ failed, missing, unloaded, cycle }) {
|
||||
const list = (heading, arr) => {
|
||||
const frag = document.createDocumentFragment();
|
||||
if (!arr || !arr.length) {
|
||||
return frag;
|
||||
}
|
||||
frag.textContent = heading;
|
||||
const ul = document.createElement("ul");
|
||||
for (const el of arr) {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = el;
|
||||
ul.append(li);
|
||||
}
|
||||
frag.appendChild(ul);
|
||||
return frag;
|
||||
};
|
||||
if ([failed, missing, unloaded].some((arr) => arr.length) || cycle) {
|
||||
// Empty body
|
||||
while (document.body.childNodes.length) {
|
||||
document.body.childNodes[0].remove();
|
||||
}
|
||||
const container = document.createElement("div");
|
||||
container.className =
|
||||
"position-fixed w-100 h-100 d-flex align-items-center flex-column bg-white overflow-auto";
|
||||
container.style.zIndex = "10000";
|
||||
const alert = document.createElement("div");
|
||||
alert.className = "alert alert-danger o_error_detail fw-bold m-auto";
|
||||
container.appendChild(alert);
|
||||
alert.appendChild(
|
||||
list(
|
||||
"The following modules failed to load because of an error, you may find more information in the devtools console:",
|
||||
failed
|
||||
)
|
||||
);
|
||||
alert.appendChild(
|
||||
list(
|
||||
"The following modules could not be loaded because they form a dependency cycle:",
|
||||
cycle && [cycle]
|
||||
)
|
||||
);
|
||||
alert.appendChild(
|
||||
list(
|
||||
"The following modules are needed by other modules but have not been defined, they may not be present in the correct asset bundle:",
|
||||
missing
|
||||
)
|
||||
);
|
||||
alert.appendChild(
|
||||
list(
|
||||
"The following modules could not be loaded because they have unmet dependencies, this is a secondary error which is likely caused by one of the above problems:",
|
||||
unloaded
|
||||
)
|
||||
);
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
<script type="text/javascript">
|
||||
window.odoo = {};
|
||||
window.odoo.__session_info__ = {is_report: true};
|
||||
// Using web.assets_common without assets_frontend or assets_backend creates a lot
|
||||
// of module dependency errors. Either assets_common should be reworked to work
|
||||
// as a standalone bundle, or this template should not use it and add what it needs
|
||||
// from it into its own bundle. Silence these errors in the meantime
|
||||
window.__odooIgnoreModuleErrors = true;
|
||||
</script>
|
||||
<t t-call-assets="web.assets_common" t-css="false"/>
|
||||
<t t-call-assets="web.report_assets_common" t-css="false"/>
|
||||
@@ -46,6 +51,13 @@
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="initial-scale=1"/>
|
||||
<title><t t-esc="title or 'Odoo Report'"/></title>
|
||||
<script type="text/javascript">
|
||||
// Using web.assets_common without assets_frontend or assets_backend creates a lot
|
||||
// of module dependency errors. Either assets_common should be reworked to work
|
||||
// as a standalone bundle, or this template should not use it and add what it needs
|
||||
// from it into its own bundle. Silence these errors in the meantime
|
||||
window.__odooIgnoreModuleErrors = true;
|
||||
</script>
|
||||
<t t-call-assets="web.report_assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.assets_common" t-css="false"/>
|
||||
<style>
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
<!-- Call this template instead of "web.assets_tests" to have the proper conditional check -->
|
||||
<template id="conditional_assets_tests" name="Tests Assets Bundle">
|
||||
<t t-call-assets="web.assets_tests" t-if="'tests' in debug or test_mode_enabled" defer_load="True" />
|
||||
<t t-if="'tests' in debug or test_mode_enabled">
|
||||
<t t-call-assets="web.assets_tests" defer_load="True" />
|
||||
<!-- The assets_tests bundle contains a lot of module whose dependencies are in other bundles that may or may not be loaded. This global is used to silence module loading errors when this bundle is present -->
|
||||
<script>window.__odooIgnoreModuleErrors = true</script>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.layout" name="Web layout"><!DOCTYPE html>
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
<title><t t-esc="slide.name"/></title>
|
||||
<t t-call-assets="web.assets_common" t-js="false"/>
|
||||
<t t-call-assets="website_slides.slide_embed_assets" t-js="false"/>
|
||||
<!-- Using web.assets_common without assets_frontend or assets_backend creates a lot
|
||||
of module dependency errors. Either assets_common should be reworked to work
|
||||
as a standalone bundle, or this template should not use it and add what it needs
|
||||
from it into its own bundle. Silence these errors in the meantime -->
|
||||
<script>window.__odooIgnoreModuleErrors = true</script>
|
||||
<t t-call-assets="web.assets_common" t-css="false"/>
|
||||
<t t-call-assets="website_slides.slide_embed_assets" t-css="false"/>
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user