From 95a5307fcd4577666f16f1a8c0e59bf735aaed01 Mon Sep 17 00:00:00 2001 From: Panu Horsmalahti Date: Tue, 18 Jan 2022 17:32:43 +0200 Subject: [PATCH] Properly handle loading custom entity in cluster-frame Signed-off-by: Panu Horsmalahti --- .../extension-loader/extension-loader.ts | 64 +++++++++++++------ src/extensions/lens-extension.ts | 6 +- .../init-cluster-frame.injectable.ts | 11 ++-- .../init-root-frame/init-root-frame.ts | 8 +-- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index e5a0a8681b..f771c9717c 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -300,11 +300,16 @@ export class ExtensionLoader { }); } - protected autoInitExtensions(register: (ext: LensExtension) => Promise) { - const loadingExtensions: ExtensionLoading[] = []; + protected async loadExtensions(installedExtensions: Map, register: (ext: LensExtension) => Promise) { + // Steps of the function: + // 1. require and call .activate for each Extension + // 2. Wait until every extension's onActivate has been resolved + // 3. Call .enable for each extension + // 4. Return ExtensionLoading[] - reaction(() => this.toJSON(), async installedExtensions => { - for (const [extId, extension] of installedExtensions) { + const extensions = [...installedExtensions.entries()] + .map(([extId, extension]) => { + // for (const [extId, extension] of installedExtensions) { const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); if (extension.isCompatible && extension.isEnabled && !alreadyInit) { @@ -313,7 +318,8 @@ export class ExtensionLoader { if (!LensExtensionClass) { this.nonInstancesByName.add(extension.manifest.name); - continue; + + return null; } const instance = this.dependencies.createExtensionInstance( @@ -321,27 +327,49 @@ export class ExtensionLoader { extension, ); - const loaded = instance.enable(register).catch((err) => { - logger.error(`${logModule}: failed to enable`, { ext: extension, err }); - }); - - loadingExtensions.push({ + return { + extId, + instance, isBundled: extension.isBundled, - loaded, - }); - this.instances.set(extId, instance); + activated: instance.activate() + }; } catch (err) { logger.error(`${logModule}: activation extension error`, { ext: extension, err }); } } else if (!extension.isEnabled && alreadyInit) { this.removeInstance(extId); } - } - }, { - fireImmediately: true, - }); - return loadingExtensions; + return null; + }) + // Remove null values + .filter(extension => Boolean(extension)); + + // We first need to wait until each extension's `onActivate` is resolved, + // as this might register new catalog categories. Afterwards we can safely .enable the extension. + await Promise.all(extensions.map(extension => extension.activated)); + + // Return ExtensionLoading[] + return extensions.map(extension => { + const loaded = extension.instance.enable(register).catch((err) => { + logger.error(`${logModule}: failed to enable`, { ext: extension, err }); + }); + + this.instances.set(extension.extId, extension.instance); + + return { + isBundled: extension.isBundled, + loaded, + }; + }); + } + + protected autoInitExtensions(register: (ext: LensExtension) => Promise) { + // Setup reaction to load extensions on JSON changes + reaction(() => this.toJSON(), installedExtensions => this.loadExtensions(installedExtensions, register)); + + // Load initial extensions + return this.loadExtensions(this.toJSON(), register); } protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null { diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index ae42ec37ff..c892464ab8 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -86,7 +86,6 @@ export class LensExtension { } try { - await this.onActivate(); this._isEnabled = true; this[Disposers].push(...await register(this)); @@ -113,6 +112,11 @@ export class LensExtension { } } + @action + activate() { + return this.onActivate(); + } + protected onActivate(): Promise | void { return; } diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index 7553877515..f5e36db1b1 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -12,16 +12,19 @@ import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bu import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable"; const initClusterFrameInjectable = getInjectable({ - instantiate: (di) => - initClusterFrame({ + instantiate: (di) => { + const extensionLoader = di.inject(extensionLoaderInjectable); + + return initClusterFrame({ hostedCluster: di.inject(hostedClusterInjectable), - loadExtensions: di.inject(extensionLoaderInjectable).loadOnClusterRenderer, + loadExtensions: extensionLoader.loadOnClusterRenderer.bind(extensionLoader), catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), frameRoutingId: di.inject(frameRoutingIdInjectable), emitEvent: di.inject(appEventBusInjectable).emit, clusterFrameContext: di.inject(clusterFrameContextInjectable), - }), + }); + }, lifecycle: lifecycleEnum.singleton, }); diff --git a/src/renderer/frames/root-frame/init-root-frame/init-root-frame.ts b/src/renderer/frames/root-frame/init-root-frame/init-root-frame.ts index 61d1d7883e..c166847e2c 100644 --- a/src/renderer/frames/root-frame/init-root-frame/init-root-frame.ts +++ b/src/renderer/frames/root-frame/init-root-frame/init-root-frame.ts @@ -11,15 +11,15 @@ import type { ExtensionLoading } from "../../../../extensions/extension-loader"; import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry"; interface Dependencies { - loadExtensions: () => ExtensionLoading[] + loadExtensions: () => Promise; // TODO: Move usages of third party library behind abstraction - ipcRenderer: { send: (name: string) => void } + ipcRenderer: { send: (name: string) => void }; // TODO: Remove dependencies being here only for correct timing of initialization bindProtocolAddRouteHandlers: () => void; lensProtocolRouterRenderer: { init: () => void }; - catalogEntityRegistry: CatalogEntityRegistry + catalogEntityRegistry: CatalogEntityRegistry; } const logPrefix = "[ROOT-FRAME]:"; @@ -40,7 +40,7 @@ export const initRootFrame = // maximum time to let bundled extensions finish loading const timeout = delay(10000); - const loadingExtensions = loadExtensions(); + const loadingExtensions = await loadExtensions(); const loadingBundledExtensions = loadingExtensions .filter((e) => e.isBundled)