1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Switch to using computedInjectMany over filtering injectMany based on enabled extensions for simplicity

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-06-17 10:39:53 +03:00
parent f84ff42fa4
commit 048187f577
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
24 changed files with 177 additions and 270 deletions

View File

@ -42,11 +42,11 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
}); });
describe("given extension with cluster pages and cluster page menus", () => { describe("given extension with cluster pages and cluster page menus", () => {
beforeEach(async () => { beforeEach(() => {
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder); const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
const testExtension = getRendererExtensionFake(extensionStubWithSidebarItems); const testExtension = getRendererExtensionFake(extensionStubWithSidebarItems);
await applicationBuilder.extensions.renderer.enable(testExtension); applicationBuilder.extensions.renderer.enable(testExtension);
}); });
describe("given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered", () => { describe("given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered", () => {

View File

@ -23,7 +23,7 @@ describe("extension special characters in page registrations", () => {
extensionWithPagesHavingSpecialCharacters, extensionWithPagesHavingSpecialCharacters,
); );
await applicationBuilder.extensions.renderer.enable(testExtension); applicationBuilder.extensions.renderer.enable(testExtension);
rendered = await applicationBuilder.render(); rendered = await applicationBuilder.render();
}); });

View File

@ -27,7 +27,7 @@ describe("navigate to extension page", () => {
extensionWithPagesHavingParameters, extensionWithPagesHavingParameters,
); );
await applicationBuilder.extensions.renderer.enable(testExtension); applicationBuilder.extensions.renderer.enable(testExtension);
rendered = await applicationBuilder.render(); rendered = await applicationBuilder.render();

View File

@ -29,7 +29,7 @@ describe("clicking tray menu item originating from extension", () => {
let someExtension: SomeTestExtension; let someExtension: SomeTestExtension;
let clickMock: jest.Mock; let clickMock: jest.Mock;
beforeEach(async () => { beforeEach(() => {
clickMock = jest.fn(); clickMock = jest.fn();
someExtension = new SomeTestExtension({ someExtension = new SomeTestExtension({
@ -37,12 +37,12 @@ describe("clicking tray menu item originating from extension", () => {
trayMenus: [{ label: "some-label", click: clickMock }], trayMenus: [{ label: "some-label", click: clickMock }],
}); });
await applicationBuilder.extensions.main.enable(someExtension); applicationBuilder.extensions.main.enable(someExtension);
}); });
it("when item is clicked, triggers the click handler", () => { it("when item is clicked, triggers the click handler", () => {
applicationBuilder.tray.click( applicationBuilder.tray.click(
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1", "some-label-tray-menu-item-for-extension-some-extension-id",
); );
expect(clickMock).toHaveBeenCalled(); expect(clickMock).toHaveBeenCalled();
@ -55,7 +55,7 @@ describe("clicking tray menu item originating from extension", () => {
}); });
applicationBuilder.tray.click( applicationBuilder.tray.click(
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1", "some-label-tray-menu-item-for-extension-some-extension-id",
); );
}); });
@ -72,7 +72,7 @@ describe("clicking tray menu item originating from extension", () => {
clickMock.mockImplementation(() => Promise.reject("some-rejection")); clickMock.mockImplementation(() => Promise.reject("some-rejection"));
applicationBuilder.tray.click( applicationBuilder.tray.click(
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1", "some-label-tray-menu-item-for-extension-some-extension-id",
); );
}); });
@ -90,11 +90,9 @@ describe("clicking tray menu item originating from extension", () => {
}); });
it("does not have the tray menu item from extension", () => { it("does not have the tray menu item from extension", () => {
applicationBuilder.extensions.main.disable(someExtension);
expect( expect(
applicationBuilder.tray.get( applicationBuilder.tray.get(
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1", "some-label-tray-menu-item-for-extension-some-extension-id",
), ),
).toBeNull(); ).toBeNull();
}); });
@ -105,7 +103,7 @@ describe("clicking tray menu item originating from extension", () => {
expect( expect(
applicationBuilder.tray.get( applicationBuilder.tray.get(
"some-label-tray-menu-item-for-extension-some-extension-id-instance-2", "some-label-tray-menu-item-for-extension-some-extension-id",
), ),
).not.toBeNull(); ).not.toBeNull();
}); });

View File

@ -28,9 +28,9 @@ describe("multiple separators originating from extension", () => {
trayMenus: [{ type: "separator" }, { type: "separator" } ], trayMenus: [{ type: "separator" }, { type: "separator" } ],
}); });
return expect( expect(() => {
applicationBuilder.extensions.main.enable(someExtension), applicationBuilder.extensions.main.enable(someExtension);
).resolves.toBeUndefined(); }).not.toThrow();
}); });
}); });

View File

@ -5,10 +5,11 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { ExtensionLoader } from "./extension-loader"; import { ExtensionLoader } from "./extension-loader";
import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable";
import { extensionRegistratorInjectionToken } from "./extension-registrator-injection-token";
import extensionInstallationCounterInjectable from "./extension-installation-counter.injectable"; import extensionInstallationCounterInjectable from "./extension-installation-counter.injectable";
import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token"; import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token";
import extensionInstancesInjectable from "./extension-instances.injectable"; import extensionInstancesInjectable from "./extension-instances.injectable";
import type { LensExtension } from "../lens-extension";
import extensionInjectable from "./extension/extension.injectable";
const extensionLoaderInjectable = getInjectable({ const extensionLoaderInjectable = getInjectable({
id: "extension-loader", id: "extension-loader",
@ -16,9 +17,9 @@ const extensionLoaderInjectable = getInjectable({
instantiate: (di) => new ExtensionLoader({ instantiate: (di) => new ExtensionLoader({
updateExtensionsState: di.inject(updateExtensionsStateInjectable), updateExtensionsState: di.inject(updateExtensionsStateInjectable),
createExtensionInstance: di.inject(createExtensionInstanceInjectionToken), createExtensionInstance: di.inject(createExtensionInstanceInjectionToken),
extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken),
extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable), extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable),
extensionInstances: di.inject(extensionInstancesInjectable), extensionInstances: di.inject(extensionInstancesInjectable),
getExtension: (instance: LensExtension) => di.inject(extensionInjectable, instance),
}), }),
}); });

View File

@ -23,15 +23,16 @@ import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
import assert from "assert"; import assert from "assert";
import { EventEmitter } from "../../common/event-emitter"; import { EventEmitter } from "../../common/event-emitter";
import type { CreateExtensionInstance } from "./create-extension-instance.token"; import type { CreateExtensionInstance } from "./create-extension-instance.token";
import type { Extension } from "./extension/extension.injectable";
const logModule = "[EXTENSIONS-LOADER]"; const logModule = "[EXTENSIONS-LOADER]";
interface Dependencies { interface Dependencies {
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void; updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void;
createExtensionInstance: CreateExtensionInstance; createExtensionInstance: CreateExtensionInstance;
readonly extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => void)[];
readonly extensionInstallationCounter: Map<string, number>; readonly extensionInstallationCounter: Map<string, number>;
readonly extensionInstances: ObservableMap<LensExtensionId, LensExtension>; readonly extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
getExtension: (instance: LensExtension) => Extension;
} }
export interface ExtensionLoading { export interface ExtensionLoading {
@ -169,6 +170,11 @@ export class ExtensionLoader {
try { try {
instance.disable(); instance.disable();
const registeredExtension = this.dependencies.getExtension(instance);
registeredExtension.deregister();
this.onRemoveExtensionId.emit(instance.id); this.onRemoveExtensionId.emit(instance.id);
this.dependencies.extensionInstances.delete(lensExtensionId); this.dependencies.extensionInstances.delete(lensExtensionId);
this.nonInstancesByName.delete(instance.name); this.nonInstancesByName.delete(instance.name);
@ -354,13 +360,9 @@ export class ExtensionLoader {
); );
extensions.forEach(({ instance }) => { extensions.forEach(({ instance }) => {
const installationCount = (this.dependencies.extensionInstallationCounter.get(instance.sanitizedExtensionId) ?? 0) + 1; const getExtension = this.dependencies.getExtension(instance);
this.dependencies.extensionInstallationCounter.set(instance.sanitizedExtensionId, installationCount); getExtension.register();
this.dependencies.extensionRegistrators.forEach((register) =>
register(instance, installationCount),
);
}); });
// Return ExtensionLoading[] // Return ExtensionLoading[]

View File

@ -2,11 +2,12 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { Injectable } from "@ogre-tools/injectable";
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type { LensExtension } from "../lens-extension"; import type { LensExtension } from "../lens-extension";
export const extensionRegistratorInjectionToken = getInjectionToken< export const extensionRegistratorInjectionToken = getInjectionToken<
(extension: LensExtension, extensionInstallationCount: number) => void (extension: LensExtension) => Injectable<any, any, any>[]
>({ >({
id: "extension-registrator-token", id: "extension-registrator-token",
}); });

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { LensExtension } from "../../lens-extension";
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
export interface Extension {
register: () => void;
deregister: () => void;
}
const extensionInjectable = getInjectable({
id: "extension",
instantiate: (parentDi, instance: LensExtension): Extension => {
const extensionInjectable = getInjectable({
id: `extension-${instance.sanitizedExtensionId}`,
instantiate: (childDi) => {
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
return {
register: () => {
const injectables = extensionRegistrators.flatMap((getInjectablesOfExtension) =>
getInjectablesOfExtension(instance),
);
childDi.register(...injectables);
},
deregister: () => {
parentDi.deregister(extensionInjectable);
},
};
},
});
parentDi.register(extensionInjectable);
return parentDi.inject(extensionInjectable);
},
lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, extension: LensExtension) => extension,
}),
});
export default extensionInjectable;

View File

@ -4,7 +4,6 @@
*/ */
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
export interface TrayMenuItem { export interface TrayMenuItem {
id: string; id: string;
@ -17,7 +16,6 @@ export interface TrayMenuItem {
click?: () => Promise<void> | void; click?: () => Promise<void> | void;
tooltip?: string; tooltip?: string;
separator?: boolean; separator?: boolean;
extension?: LensMainExtension;
} }
export const trayMenuItemInjectionToken = getInjectionToken<TrayMenuItem>({ export const trayMenuItemInjectionToken = getInjectionToken<TrayMenuItem>({

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { flatMap, kebabCase } from "lodash/fp"; import { kebabCase } from "lodash/fp";
import type { Injectable } from "@ogre-tools/injectable"; import type { Injectable } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx"; import { computed } from "mobx";
@ -20,17 +20,13 @@ import getRandomIdInjectable from "../../../common/utils/get-random-id.injectabl
const trayMenuItemRegistratorInjectable = getInjectable({ const trayMenuItemRegistratorInjectable = getInjectable({
id: "tray-menu-item-registrator", id: "tray-menu-item-registrator",
instantiate: (di) => (extension, installationCounter) => { instantiate: (di) => (extension) => {
const mainExtension = extension as LensMainExtension; const mainExtension = extension as LensMainExtension;
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable); const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
const getRandomId = di.inject(getRandomIdInjectable); const getRandomId = di.inject(getRandomIdInjectable);
pipeline( return mainExtension.trayMenus.flatMap(
mainExtension.trayMenus, toItemInjectablesFor(mainExtension, withErrorLoggingFor, getRandomId),
flatMap(toItemInjectablesFor(mainExtension, installationCounter, withErrorLoggingFor, getRandomId)),
(injectables) => di.register(...injectables),
); );
}, },
@ -39,10 +35,10 @@ const trayMenuItemRegistratorInjectable = getInjectable({
export default trayMenuItemRegistratorInjectable; export default trayMenuItemRegistratorInjectable;
const toItemInjectablesFor = (extension: LensMainExtension, installationCounter: number, withErrorLoggingFor: WithErrorLoggingFor, getRandomId: () => string) => { const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor: WithErrorLoggingFor, getRandomId: () => string) => {
const _toItemInjectables = (parentId: string | null) => (registration: TrayMenuRegistration): Injectable<TrayMenuItem, TrayMenuItem, void>[] => { const _toItemInjectables = (parentId: string | null) => (registration: TrayMenuRegistration): Injectable<TrayMenuItem, TrayMenuItem, void>[] => {
const trayItemId = registration.id || kebabCase(registration.label || getRandomId()); const trayItemId = registration.id || kebabCase(registration.label || getRandomId());
const id = `${trayItemId}-tray-menu-item-for-extension-${extension.sanitizedExtensionId}-instance-${installationCounter}`; const id = `${trayItemId}-tray-menu-item-for-extension-${extension.sanitizedExtensionId}`;
const parentInjectable = getInjectable({ const parentInjectable = getInjectable({
id, id,
@ -76,8 +72,6 @@ const toItemInjectablesFor = (extension: LensMainExtension, installationCounter:
enabled: computed(() => registration.enabled ?? true), enabled: computed(() => registration.enabled ?? true),
visible: computed(() => true), visible: computed(() => true),
extension,
}), }),
injectionToken: trayMenuItemInjectionToken, injectionToken: trayMenuItemInjectionToken,

View File

@ -3,47 +3,30 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { computed } from "mobx"; import { computed } from "mobx";
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
import type { TrayMenuItem } from "./tray-menu-item-injection-token";
import { trayMenuItemInjectionToken } from "./tray-menu-item-injection-token"; import { trayMenuItemInjectionToken } from "./tray-menu-item-injection-token";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { filter, overSome, sortBy } from "lodash/fp"; import { filter, sortBy } from "lodash/fp";
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
const trayMenuItemsInjectable = getInjectable({ const trayMenuItemsInjectable = getInjectable({
id: "tray-menu-items", id: "tray-menu-items",
instantiate: (di) => { instantiate: (di) => {
const extensions = di.inject(mainExtensionsInjectable); const computedInjectMany = di.inject(
computedInjectManyInjectable,
);
return computed(() => { const reactiveMenuItems = computedInjectMany(trayMenuItemInjectionToken);
const enabledExtensions = extensions.get();
return pipeline( return computed(() =>
di.injectMany(trayMenuItemInjectionToken), pipeline(
reactiveMenuItems.get(),
filter((item) => filter((item) => item.visible.get()),
overSome([ (items) => sortBy("orderNumber", items),
isNonExtensionItem, ),
isEnabledExtensionItemFor(enabledExtensions), );
])(item),
),
filter(item => item.visible.get()),
items => sortBy("orderNumber", items),
);
});
}, },
}); });
const isNonExtensionItem = (item: TrayMenuItem) => !item.extension;
const isEnabledExtensionItemFor =
(enabledExtensions: LensMainExtension[]) => (item: TrayMenuItem) =>
!!enabledExtensions.find((extension) => extension === item.extension);
export default trayMenuItemsInjectable; export default trayMenuItemsInjectable;

View File

@ -13,10 +13,11 @@ const extensionPreferenceItemRegistratorInjectable = getInjectable({
id: "extension-preference-item-registrator", id: "extension-preference-item-registrator",
instantiate: instantiate:
(di) => () =>
(ext, extensionInstallationCount) => { (ext) => {
const extension = ext as LensRendererExtension; const extension = ext as LensRendererExtension;
const injectables = pipeline(
return pipeline(
extension.appPreferences, extension.appPreferences,
filter( filter(
@ -27,13 +28,12 @@ const extensionPreferenceItemRegistratorInjectable = getInjectable({
const id = `extension-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`; const id = `extension-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`;
return getInjectable({ return getInjectable({
id: `${id}-for-instance-${extensionInstallationCount}`, id,
injectionToken: extensionPreferenceItemInjectionToken, injectionToken: extensionPreferenceItemInjectionToken,
instantiate: () => ({ instantiate: () => ({
id: registration.id || id, id: registration.id || id,
title: registration.title, title: registration.title,
extension,
components: { components: {
Hint: registration.components.Hint, Hint: registration.components.Hint,
@ -43,10 +43,6 @@ const extensionPreferenceItemRegistratorInjectable = getInjectable({
}); });
}), }),
); );
di.register(...injectables);
return;
}, },
injectionToken: extensionRegistratorInjectionToken, injectionToken: extensionRegistratorInjectionToken,

View File

@ -2,19 +2,11 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { pipeline } from "@ogre-tools/fp";
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import { filter, overSome } from "lodash/fp"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { computed } from "mobx";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
interface ExtensionPreferenceItem extends RegisteredAppPreference { export const extensionPreferenceItemInjectionToken = getInjectionToken<RegisteredAppPreference>({
extension: LensRendererExtension;
}
export const extensionPreferenceItemInjectionToken = getInjectionToken<ExtensionPreferenceItem>({
id: "extension-preference-item-injection-token", id: "extension-preference-item-injection-token",
}); });
@ -22,29 +14,10 @@ const extensionsPreferenceItemsInjectable = getInjectable({
id: "extension-preference-items", id: "extension-preference-items",
instantiate: (di) => { instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable); const computedInjectMany = di.inject(computedInjectManyInjectable);
return computed(() => { return computedInjectMany(extensionPreferenceItemInjectionToken);
const enabledExtensions = extensions.get();
return pipeline(
di.injectMany(extensionPreferenceItemInjectionToken),
filter((item) =>
overSome([
isNonExtensionItem,
isEnabledExtensionItemFor(enabledExtensions),
])(item),
),
);
});
}, },
}); });
const isNonExtensionItem = (item: ExtensionPreferenceItem) => !item.extension;
const isEnabledExtensionItemFor =
(enabledExtensions: LensRendererExtension[]) => (item: ExtensionPreferenceItem) =>
!!enabledExtensions.find((extension) => extension === item.extension);
export default extensionsPreferenceItemsInjectable; export default extensionsPreferenceItemsInjectable;

View File

@ -13,10 +13,11 @@ const extensionTelemetryPreferenceItemRegistratorInjectable = getInjectable({
id: "extension-telemetry-preference-item-registrator", id: "extension-telemetry-preference-item-registrator",
instantiate: instantiate:
(di) => () =>
(ext, extensionInstallationCount) => { (ext) => {
const extension = ext as LensRendererExtension; const extension = ext as LensRendererExtension;
const injectables = pipeline(
return pipeline(
extension.appPreferences, extension.appPreferences,
filter( filter(
@ -27,13 +28,12 @@ const extensionTelemetryPreferenceItemRegistratorInjectable = getInjectable({
const id = `telemetry-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`; const id = `telemetry-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`;
return getInjectable({ return getInjectable({
id: `${id}-for-instance-${extensionInstallationCount}`, id,
injectionToken: telemetryPreferenceItemInjectionToken, injectionToken: telemetryPreferenceItemInjectionToken,
instantiate: () => ({ instantiate: () => ({
id: registration.id || id, id: registration.id || id,
title: registration.title, title: registration.title,
extension,
components: { components: {
Hint: registration.components.Hint, Hint: registration.components.Hint,
@ -43,10 +43,6 @@ const extensionTelemetryPreferenceItemRegistratorInjectable = getInjectable({
}); });
}), }),
); );
di.register(...injectables);
return;
}, },
injectionToken: extensionRegistratorInjectionToken, injectionToken: extensionRegistratorInjectionToken,

View File

@ -3,48 +3,22 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { pipeline } from "@ogre-tools/fp";
import { filter, overSome } from "lodash/fp";
import { computed } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration"; import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration";
export interface ExtensionTelemetryPreferenceRegistration extends AppPreferenceRegistration { export const telemetryPreferenceItemInjectionToken = getInjectionToken<AppPreferenceRegistration>({
extension?: LensRendererExtension;
}
export const telemetryPreferenceItemInjectionToken = getInjectionToken<ExtensionTelemetryPreferenceRegistration>({
id: "telemetry-preference-item-injection-token", id: "telemetry-preference-item-injection-token",
}); });
const telemetryPreferenceItemsInjectable = getInjectable({ const telemetryPreferenceItemsInjectable = getInjectable({
id: "telemetry-preference-items", id: "telemetry-preference-items",
instantiate: (di) => { instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable); const computedInjectMany = di.inject(computedInjectManyInjectable);
return computed(() => { return computedInjectMany(telemetryPreferenceItemInjectionToken);
const enabledExtensions = extensions.get();
return pipeline(
di.injectMany(telemetryPreferenceItemInjectionToken),
filter((item) =>
overSome([
isNonExtensionItem,
isEnabledExtensionItemFor(enabledExtensions),
])(item),
),
);
});
}, },
}); });
const isNonExtensionItem = (item: ExtensionTelemetryPreferenceRegistration) => !item.extension;
const isEnabledExtensionItemFor =
(enabledExtensions: LensRendererExtension[]) => (item: ExtensionTelemetryPreferenceRegistration) =>
!!enabledExtensions.find((extension) => extension === item.extension);
export default telemetryPreferenceItemsInjectable; export default telemetryPreferenceItemsInjectable;

View File

@ -11,13 +11,13 @@ import { ExtensionSettings } from "./extension-settings";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import { Preferences } from "./preferences"; import { Preferences } from "./preferences";
import type { ExtensionTelemetryPreferenceRegistration } from "./telemetry-preference-items.injectable";
import telemetryPreferenceItemsInjectable from "./telemetry-preference-items.injectable"; import telemetryPreferenceItemsInjectable from "./telemetry-preference-items.injectable";
import sentryDnsUrlInjectable from "./sentry-dns-url.injectable"; import sentryDnsUrlInjectable from "./sentry-dns-url.injectable";
import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration";
interface Dependencies { interface Dependencies {
telemetryPreferenceItems: IComputedValue<ExtensionTelemetryPreferenceRegistration[]>; telemetryPreferenceItems: IComputedValue<AppPreferenceRegistration[]>;
sentryDnsUrl: string; sentryDnsUrl: string;
userStore: UserStore; userStore: UserStore;
} }

View File

@ -18,17 +18,16 @@ import type { LensRendererExtension } from "../../../extensions/lens-renderer-ex
const extensionSidebarItemRegistratorInjectable = getInjectable({ const extensionSidebarItemRegistratorInjectable = getInjectable({
id: "extension-sidebar-item-registrator", id: "extension-sidebar-item-registrator",
instantiate: (di) => (ext, extensionInstallationCount) => { instantiate: (di) => (ext) => {
const extension = ext as LensRendererExtension; const extension = ext as LensRendererExtension;
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const routes = di.inject(routesInjectable);
const sidebarItemsForExtensionInjectable = getInjectable({ const sidebarItemsForExtensionInjectable = getInjectable({
id: `sidebar-items-for-extension-${extension.sanitizedExtensionId}-instance-${extensionInstallationCount}`, id: `sidebar-items-for-extension-${extension.sanitizedExtensionId}`,
injectionToken: sidebarItemsInjectionToken, injectionToken: sidebarItemsInjectionToken,
instantiate: (di) => { instantiate: (di) => {
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const routes = di.inject(routesInjectable);
return computed(() => { return computed(() => {
const extensionRoutes = routes.get().filter(matches({ extension })); const extensionRoutes = routes.get().filter(matches({ extension }));
@ -44,7 +43,6 @@ const extensionSidebarItemRegistratorInjectable = getInjectable({
const res: SidebarItemRegistration = { const res: SidebarItemRegistration = {
id: `${extension.sanitizedExtensionId}-${registration.id}`, id: `${extension.sanitizedExtensionId}-${registration.id}`,
extension,
orderNumber: 9999, orderNumber: 9999,
parentId: registration.parentId parentId: registration.parentId
@ -73,7 +71,9 @@ const extensionSidebarItemRegistratorInjectable = getInjectable({
}, },
}); });
di.register(sidebarItemsForExtensionInjectable); return [
sidebarItemsForExtensionInjectable,
];
}, },
injectionToken: extensionRegistratorInjectionToken, injectionToken: extensionRegistratorInjectionToken,

View File

@ -6,7 +6,7 @@ import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { computed } from "mobx"; import { computed } from "mobx";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { import {
filter, filter,
flatMap, flatMap,
@ -15,11 +15,8 @@ import {
isEmpty, isEmpty,
map, map,
orderBy, orderBy,
overSome,
some, some,
} from "lodash/fp"; } from "lodash/fp";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
export interface SidebarItemRegistration { export interface SidebarItemRegistration {
id: string; id: string;
@ -30,7 +27,6 @@ export interface SidebarItemRegistration {
isActive?: IComputedValue<boolean>; isActive?: IComputedValue<boolean>;
isVisible?: IComputedValue<boolean>; isVisible?: IComputedValue<boolean>;
orderNumber: number; orderNumber: number;
extension?: LensRendererExtension;
} }
export const sidebarItemsInjectionToken = getInjectionToken< export const sidebarItemsInjectionToken = getInjectionToken<
@ -47,25 +43,14 @@ const sidebarItemsInjectable = getInjectable({
id: "sidebar-items", id: "sidebar-items",
instantiate: (di) => { instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable); const computedInjectMany = di.inject(computedInjectManyInjectable);
const sidebarItemRegistrations = computedInjectMany(sidebarItemsInjectionToken);
return computed((): HierarchicalSidebarItem[] => { return computed((): HierarchicalSidebarItem[] => {
const enabledExtensions = extensions.get();
const sidebarItemRegistrations = di.injectMany(
sidebarItemsInjectionToken,
);
const registrations = pipeline( const registrations = pipeline(
sidebarItemRegistrations, sidebarItemRegistrations.get(),
flatMap(dereference), flatMap(dereference),
filter(
overSome([
isNonExtensionSidebarItem,
isEnabledExtensionSidebarItemFor(enabledExtensions),
]),
),
); );
const getSidebarItemsHierarchy = (registrations: SidebarItemRegistration[]) => { const getSidebarItemsHierarchy = (registrations: SidebarItemRegistration[]) => {
@ -114,14 +99,6 @@ const sidebarItemsInjectable = getInjectable({
}, },
}); });
const isNonExtensionSidebarItem = (sidebarItem: SidebarItemRegistration) =>
!sidebarItem.extension;
const isEnabledExtensionSidebarItemFor =
(enabledExtensions: LensRendererExtension[]) =>
(sidebarItem: SidebarItemRegistration) =>
!!enabledExtensions.find((x) => x === sidebarItem.extension);
const dereference = (items: IComputedValue<SidebarItemRegistration[]>) => const dereference = (items: IComputedValue<SidebarItemRegistration[]>) =>
items.get(); items.get();

View File

@ -5,7 +5,6 @@
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable"; import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
import type { IObservableArray, ObservableSet } from "mobx"; import type { IObservableArray, ObservableSet } from "mobx";
import { computed, observable, runInAction } from "mobx"; import { computed, observable, runInAction } from "mobx";
import { renderFor } from "./renderFor"; import { renderFor } from "./renderFor";
@ -60,9 +59,11 @@ import type { LensMainExtension } from "../../../extensions/lens-main-extension"
import trayMenuItemsInjectable from "../../../main/tray/tray-menu-item/tray-menu-items.injectable"; import trayMenuItemsInjectable from "../../../main/tray/tray-menu-item/tray-menu-items.injectable";
import type { LensExtension } from "../../../extensions/lens-extension"; import type { LensExtension } from "../../../extensions/lens-extension";
import extensionInjectable from "../../../extensions/extension-loader/extension/extension.injectable";
type Callback = (dis: DiContainers) => void | Promise<void>; type Callback = (dis: DiContainers) => void | Promise<void>;
type EnableExtensions<T> = (...extensions: T[]) => Promise<void>; type EnableExtensions<T> = (...extensions: T[]) => void;
type DisableExtensions<T> = (...extensions: T[]) => void; type DisableExtensions<T> = (...extensions: T[]) => void;
export interface ApplicationBuilder { export interface ApplicationBuilder {
@ -222,33 +223,26 @@ export const getApplicationBuilder = () => {
extensionState: T, extensionState: T,
di: DiContainer, di: DiContainer,
) => { ) => {
let index = 0; const getExtension = (extension: LensExtension) =>
di.inject(extensionInjectable, extension);
return async (...extensions: LensExtension[]) => { return (...extensionInstances: LensExtension[]) => {
const extensionRegistrators = di.injectMany( const addAndEnableExtensions = () => {
extensionRegistratorInjectionToken, extensionInstances.forEach(instance => {
); const extension = getExtension(instance);
const addAndEnableExtensions = async () => { extension.register();
index++; });
const registratorPromises = extensions.flatMap((extension) =>
extensionRegistrators.map((registrator) =>
registrator(extension, index),
),
);
await Promise.all(registratorPromises);
runInAction(() => { runInAction(() => {
extensions.forEach((extension) => { extensionInstances.forEach((extension) => {
extensionState.add(extension); extensionState.add(extension);
}); });
}); });
}; };
if (rendered) { if (rendered) {
await addAndEnableExtensions(); addAndEnableExtensions();
} else { } else {
builder.beforeRender(addAndEnableExtensions); builder.beforeRender(addAndEnableExtensions);
} }
@ -407,12 +401,12 @@ export const getApplicationBuilder = () => {
extensions: { extensions: {
renderer: { renderer: {
enable: enableExtensionsFor(rendererExtensionsState, rendererDi), enable: enableExtensionsFor(rendererExtensionsState, rendererDi),
disable: disableExtensionsFor(rendererExtensionsState), disable: disableExtensionsFor(rendererExtensionsState, rendererDi),
}, },
main: { main: {
enable: enableExtensionsFor(mainExtensionsState, mainDi), enable: enableExtensionsFor(mainExtensionsState, mainDi),
disable: disableExtensionsFor(mainExtensionsState), disable: disableExtensionsFor(mainExtensionsState, mainDi),
}, },
}, },
@ -538,13 +532,22 @@ function toFlatChildren(parentId: string | null | undefined): ToFlatChildren {
]; ];
} }
const disableExtensionsFor = const disableExtensionsFor = <T extends ObservableSet>(
<T extends ObservableSet>(extensionState: T) => extensionState: T,
di: DiContainer,
) => {
const getExtension = (instance: LensExtension) =>
di.inject(extensionInjectable, instance);
(...extensions: LensExtension[]) => { return (...extensionInstances: LensExtension[]) => {
extensions.forEach((extension) => { extensionInstances.forEach((instance) => {
runInAction(() => { const extension = getExtension(instance);
extensionState.delete(extension);
}); extension.deregister();
runInAction(() => {
extensionState.delete(extension);
}); });
}; });
};
};

View File

@ -1,40 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { overSome } from "lodash/fp";
import { computed } from "mobx";
import rendererExtensionsInjectable from "../../extensions/renderer-extensions.injectable";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
import type { Route } from "../../common/front-end-routing/front-end-route-injection-token";
import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token";
const allRoutesInjectable = getInjectable({
id: "all-routes",
instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable);
return computed(() => {
const enabledExtensions = extensions.get();
return di
.injectMany(frontEndRouteInjectionToken)
.filter((route) =>
overSome([
isNonExtensionRoute,
isEnabledExtensionRouteFor(enabledExtensions),
])(route),
);
});
},
});
const isNonExtensionRoute = (route: Route<unknown>) => !route.extension;
const isEnabledExtensionRouteFor =
(enabledExtensions: LensRendererExtension[]) => (route: Route<unknown>) =>
!!enabledExtensions.find((x) => x === route.extension);
export default allRoutesInjectable;

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { matches } from "lodash/fp"; import { matches } from "lodash/fp";
import { computed } from "mobx"; import { computed } from "mobx";
import currentRouteInjectable from "./current-route.injectable"; import currentRouteInjectable from "./current-route.injectable";
@ -13,6 +14,8 @@ const currentRouteComponentInjectable = getInjectable({
instantiate: (di) => { instantiate: (di) => {
const route = di.inject(currentRouteInjectable); const route = di.inject(currentRouteInjectable);
const computedInjectMany = di.inject(computedInjectManyInjectable);
const routeComponents = computedInjectMany(routeSpecificComponentInjectionToken);
return computed(() => { return computed(() => {
const currentRoute = route.get(); const currentRoute = route.get();
@ -21,8 +24,8 @@ const currentRouteComponentInjectable = getInjectable({
return undefined; return undefined;
} }
const routeSpecificComponent = di const routeSpecificComponent = routeComponents
.injectMany(routeSpecificComponentInjectionToken) .get()
.find(matches({ route: currentRoute })); .find(matches({ route: currentRoute }));
return routeSpecificComponent?.Component; return routeSpecificComponent?.Component;

View File

@ -22,20 +22,17 @@ const extensionRouteRegistratorInjectable = getInjectable({
id: "extension-route-registrator", id: "extension-route-registrator",
instantiate: (di) => { instantiate: (di) => {
return (ext, extensionInstallationCount) => { return (ext) => {
const extension = ext as LensRendererExtension; const extension = ext as LensRendererExtension;
const toRouteInjectable = toRouteInjectableFor( const toRouteInjectable = toRouteInjectableFor(
di, di,
extension, extension,
extensionInstallationCount,
); );
const routeInjectables = [ return [
...extension.globalPages.map(toRouteInjectable(false)), ...extension.globalPages.map(toRouteInjectable(false)),
...extension.clusterPages.map(toRouteInjectable(true)), ...extension.clusterPages.map(toRouteInjectable(true)),
].flat(); ].flat();
di.register(...routeInjectables);
}; };
}, },
@ -48,12 +45,11 @@ const toRouteInjectableFor =
( (
di: DiContainerForInjection, di: DiContainerForInjection,
extension: LensRendererExtension, extension: LensRendererExtension,
extensionInstallationCount: number,
) => ) =>
(clusterFrame: boolean) => (clusterFrame: boolean) =>
(registration: PageRegistration) => { (registration: PageRegistration) => {
const routeInjectable = getInjectable({ const routeInjectable = getInjectable({
id: `route-${registration.id}-for-extension-${extension.sanitizedExtensionId}-installation-${extensionInstallationCount}`, id: `route-${registration.id}-for-extension-${extension.sanitizedExtensionId}`,
instantiate: () => ({ instantiate: () => ({
path: getExtensionRoutePath(extension, registration.id), path: getExtensionRoutePath(extension, registration.id),
@ -95,7 +91,7 @@ const toRouteInjectableFor =
}; };
const routeSpecificComponentInjectable = getInjectable({ const routeSpecificComponentInjectable = getInjectable({
id: `route-${registration.id}-component-for-extension-${extension.sanitizedExtensionId}-installation-${extensionInstallationCount}`, id: `route-${registration.id}-component-for-extension-${extension.sanitizedExtensionId}`,
instantiate: (di) => ({ instantiate: (di) => ({
route: di.inject(routeInjectable), route: di.inject(routeInjectable),

View File

@ -5,18 +5,20 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx"; import { computed } from "mobx";
import currentlyInClusterFrameInjectable from "./currently-in-cluster-frame.injectable"; import currentlyInClusterFrameInjectable from "./currently-in-cluster-frame.injectable";
import allRoutesInjectable from "./all-routes.injectable";
import { matches } from "lodash/fp"; import { matches } from "lodash/fp";
import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
const routesInjectable = getInjectable({ const routesInjectable = getInjectable({
id: "routes", id: "routes",
instantiate: (di) => { instantiate: (di) => {
const allRoutes = di.inject(allRoutesInjectable); const computedInjectMany = di.inject(computedInjectManyInjectable);
const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable); const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable);
const routes = computedInjectMany(frontEndRouteInjectionToken);
return computed(() => return computed(() =>
allRoutes routes
.get() .get()
.filter(matches({ clusterFrame: currentlyInClusterFrame })) .filter(matches({ clusterFrame: currentlyInClusterFrame }))
.filter((route) => route.isEnabled.get()), .filter((route) => route.isEnabled.get()),