diff --git a/addon/utils/resource-descriptors/helpers.js b/addon/utils/resource-descriptors/helpers.js index 3948d4c..ed2319e 100644 --- a/addon/utils/resource-descriptors/helpers.js +++ b/addon/utils/resource-descriptors/helpers.js @@ -162,10 +162,32 @@ export function engineInstalled(owner, engineName = FLEETOPS_ENGINE) { } } +/** + * Whether the console is on one of the engine's own routes. Unknown means yes, so a host + * without a router keeps the previous behaviour. + */ +export function isInsideEngine(manager, engineName = FLEETOPS_ENGINE) { + let current; + let mountPoint; + + try { + current = manager?.applicationInstance?.lookup?.('service:router')?.currentRouteName; + mountPoint = manager?.getEngineMountPoint?.(engineName); + } catch { + return true; + } + + if (typeof current !== 'string' || typeof mountPoint !== 'string' || mountPoint === '') { + return true; + } + + return current === mountPoint.replace(/\.$/, '') || current.startsWith(mountPoint); +} + /** * An opener that loads an engine on demand, then hands the record to one of - * its action services: the context panel when it has one, else its route - * transition. The engine bundle is only fetched when someone actually clicks, + * its action services: the context panel when it has one and the console is inside that + * engine, else its route transition. The engine bundle is only fetched when someone actually clicks, * never when a page that merely shows the resource renders. */ export function engineOpener(owner, serviceName, { engineName = FLEETOPS_ENGINE, mode = 'panel' } = {}) { @@ -190,7 +212,10 @@ export function engineOpener(owner, serviceName, { engineName = FLEETOPS_ENGINE, return false; } - const view = (mode === 'panel' ? (service.panel?.view ?? service.transition?.view) : null) ?? (mode === 'transition' ? service.transition?.view : null); + // A panel renders the engine's own components, which only resolve inside that engine's + // routes; from anywhere else (Storefront, the console home) open the record's page instead. + const panelView = mode === 'panel' && isInsideEngine(manager, engineName) ? service.panel?.view : null; + const view = panelView ?? (mode === 'panel' || mode === 'transition' ? service.transition?.view : null); if (typeof view !== 'function') { return false; diff --git a/tests/unit/utils/resource-descriptors/helpers-test.js b/tests/unit/utils/resource-descriptors/helpers-test.js index 8ccb2b4..dac9e20 100644 --- a/tests/unit/utils/resource-descriptors/helpers-test.js +++ b/tests/unit/utils/resource-descriptors/helpers-test.js @@ -19,6 +19,7 @@ import { extensionManager, engineInstalled, engineOpener, + isInsideEngine, FLEETOPS_ENGINE, } from '@fleetbase/fleetops-data/utils/resource-descriptors/helpers'; import { PLACEHOLDER_IMAGES } from '@fleetbase/fleetops-data/utils/placeholder-images'; @@ -219,5 +220,39 @@ module('Unit | Utility | resource-descriptors/helpers', function (hooks) { ['transition', record], ]); }); + + test("engineOpener opens the record's page instead of a panel outside the engine's routes", async function (assert) { + const record = { id: 'r1' }; + const seen = []; + const service = { panel: { view: () => seen.push('panel') }, transition: { view: () => seen.push('transition') } }; + const at = (routeName) => + ownerWithEngine( + { 'vehicle-actions': service, 'panel-only': { panel: { view: () => seen.push('panel-only') } } }, + { + applicationInstance: { lookup: (name) => (name === 'service:router' ? { currentRouteName: routeName } : null) }, + getEngineMountPoint: () => 'console.fleet-ops.', + } + ); + + assert.true(await engineOpener(at('console.fleet-ops.management.vehicles.index'), 'vehicle-actions')(record)); + assert.true(await engineOpener(at('console.storefront.food-trucks.index'), 'vehicle-actions')(record)); + assert.false(await engineOpener(at('console.storefront.food-trucks.index'), 'panel-only')(record), 'nothing to open outside without a transition'); + assert.deepEqual(seen, ['panel', 'transition'], 'the panel inside Fleet-Ops, the page from Storefront'); + }); + + test('isInsideEngine reads the console route against the engine mount point and assumes yes when it cannot tell', function (assert) { + const manager = (routeName, mountPoint = 'console.fleet-ops.') => ({ + applicationInstance: { lookup: () => ({ currentRouteName: routeName }) }, + getEngineMountPoint: () => mountPoint, + }); + + assert.true(isInsideEngine(manager('console.fleet-ops.operations.orders.index'))); + assert.true(isInsideEngine(manager('console.fleet-ops')), 'the engine root'); + assert.false(isInsideEngine(manager('console.storefront.index'))); + assert.false(isInsideEngine(manager('console.fleet-ops-reports.index')), 'a prefix match is not enough'); + assert.true(isInsideEngine(null), 'no manager'); + assert.true(isInsideEngine(manager(undefined)), 'no route'); + assert.true(isInsideEngine(manager('console.storefront.index', null)), 'no mount point'); + }); }); });