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 @@ @@ -46,6 +51,13 @@ <t t-esc="title or 'Odoo Report'"/> +