diff --git a/addons/web/static/src/boot.js b/addons/web/static/src/boot.js
index fc42d8108af..707c0ec0a41 100644
--- a/addons/web/static/src/boot.js
+++ b/addons/web/static/src/boot.js
@@ -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);
+ }
+ }
})();
diff --git a/addons/web/views/report_templates.xml b/addons/web/views/report_templates.xml
index 5e3762bb9e0..8c70b2c95c0 100644
--- a/addons/web/views/report_templates.xml
+++ b/addons/web/views/report_templates.xml
@@ -20,6 +20,11 @@