Mobx-6 migration (#2718)

* mobx-6 migration -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 2 (npx mobx-undecorate --keepDecorators)

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 3 (more fixes)

Signed-off-by: Roman <ixrock@gmail.com>

* unwrap possible observables from IPC-messaging

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- remove @autobind as class-decorator

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: replacing @autobind() as method-decorator to @boundMethod

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: use toJS()-wrapper since monkey-patching require(mobx).toJS doesn't work

Signed-off-by: Roman <ixrock@gmail.com>

* removed `@observable static`

Signed-off-by: Roman <ixrock@gmail.com>

* use {useDefineForClassFields: true} in tsconfig.json

Signed-off-by: Roman <ixrock@gmail.com>

* remove ExtendedObservableMap

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix: removed makeObservable(this) from "terminal-tab.tsx"

Signed-off-by: Roman <ixrock@gmail.com>

* storage-helper refactoring

Signed-off-by: Roman <ixrock@gmail.com>

* normalize usages of #observable-value.toJSON() / attempt to catch the wind

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring, more possible branch fixes + lint

Signed-off-by: Roman <ixrock@gmail.com>

* debugging cluster-view error -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refreshing cluster-view on ready

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to KubeObject.spec.* access from "undefined"
fix: config-map-details crash

Signed-off-by: Roman <ixrock@gmail.com>

* fix: namespace-store refactoring / saving selected-namespaces to external json-file

Signed-off-by: Roman <ixrock@gmail.com>

* fix: don't cache mobx.when(() => this.someObservable) cause might not work as expected due later call of makeObservable(this) in constructor

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash on editing k8s resource

Signed-off-by: Roman <ixrock@gmail.com>

* fix: restore "all namespaces" on page reload

Signed-off-by: Roman <ixrock@gmail.com>

* - fix: persist table-sort params and cluster-view's sidebar state to lens-local-storage
- new-feature: auto-open main-window's devtools in development-mode (yes/no/ugly?)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definition details -> crashing with <AceEditor mode="json"> (added missing mode-file in ace-editor.tsx)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definitions -> groups selector couldn't deselect last selected option

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring: extensions-api exports clarification for "@k8slens/extensions"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to kube-events (events page, some details page, overview, etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* Reverted "use {useDefineForClassFields: true} in tsconfig.json" (various app-crash fixes)
This flag seems to be not possible to use with class-inheritance in some cases.

Example / demo:
`KubeObject` class has initial type definitions for the fields like: "metadata", "kind", etc.
and constructor() has Object.assign(this, data);
Meanwhile child class, e.g. KubeEvent inherited from KubeObject and has it's own extra type definitions for underlying resource, e.g. "involvedObject", "source", etc.

So calling super(data) doesn't work as expected for child class as it's own type definitions overwrites data from parent's constructor with `undefined` at later point.

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* catalog.tsx / catalog-entities.store.ts refactoring & fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog -> Browse all tab

Signed-off-by: Roman <ixrock@gmail.com>

* fix: CommandPalette doesn't appear from global menu by click/hotkey

Signed-off-by: Roman <ixrock@gmail.com>

* - Merging interfaces & classses to avoid overwriting fields from parent's super(data)-call with Object.assign(this, data). Otherwise use "declare" keyword at class field definition.

- Revamping {useDefineForClassFields: true} to avoid issues with non-observable class fields in some cases (from previous commit):

```
@observer
export class CommandContainer extends React.Component<CommandContainerProps> {
  // without some defined initial value "commandComponent" is non-observable for some reasons
  // when tsconfig.ts has {useDefineForClassFields:false}
  @observable.ref commandComponent: React.ReactNode = null;

  constructor(props: CommandContainerProps) {
    super(props);
    makeObservable(this);
  }
```

Signed-off-by: Roman <ixrock@gmail.com>

* update KubeObject class type definition

Signed-off-by: Roman <ixrock@gmail.com>

* clean up / responding to comments

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash when navigating to catalog from active cluster-view, refactoring `catalog-entity-store`

Signed-off-by: Roman <ixrock@gmail.com>

* catalog-pusher clean up, replaced .observe_() to external observe() helper from "mobx"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog's items stale/non-observable (after connection to the cluster status still "disconnected"), lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog is empty after closing main-window and re-opening app from Tray

Signed-off-by: Roman <ixrock@gmail.com>

* fix: HotBar's icon context menu items non-observable (no "disconnect cluster", etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* lint-fix/license check

Signed-off-by: Roman <ixrock@gmail.com>

* fix: redirect to catalog when disconnecting active cluster

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refresh visibility of active cluster-view on switching from hotbar/catalog

Signed-off-by: Roman <ixrock@gmail.com>

* updated package.json for built-in extensions to use "*" version for packages served from main app

Signed-off-by: Roman <ixrock@gmail.com>

* - added missing makeObservable(this) to metrics-settings.tsx
- updated package-lock.json for built-in extensions
- lint fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge clean up fix, updated package-lock.json for built-in extensions after `make clean-extensions && make build-extensions`

Signed-off-by: Roman <ixrock@gmail.com>

* fix unit-tests

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* make lint happy

Signed-off-by: Roman <ixrock@gmail.com>

* reverted some changes, removed auto-opening devtools in dev-mode

Signed-off-by: Roman <ixrock@gmail.com>

* merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge conflict fixes:
- proper handling and navigating into catalog's active category via URL-builder

Signed-off-by: Roman <ixrock@gmail.com>

* reverting splitted params for catalog's page route to "/catalog/:group?/:kind?"

Signed-off-by: Roman <ixrock@gmail.com>

* clean-up: remove app's injecting dependencies from `extensions/kube-object-event-status/package.json`

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fix: added missing makeObservable(this) for extensions.tsx

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog entity context menu stale/unobservable

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
RomanandJari Kolehmainen authored and GitHub committed 2021-05-25 10:24:31 +03:00
1 parent 0c45dd807e
commit 2c3b510997
256 files changed
+2359 -1473

No files matched your search

+2 -1
View File
@@ -19,7 +19,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Extension-api types generation bundle
// Extensions-api types bundle (main + renderer)
// Available for lens-extensions via NPM-package "@k8slens/extensions"
export * from "./core-api";
export * from "./renderer-api";
+13 -6
View File
@@ -23,11 +23,11 @@ import { watch } from "chokidar";
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import fse from "fs-extra";
import { observable, reaction, toJS, when } from "mobx";
import { observable, reaction, when, makeObservable } from "mobx";
import os from "os";
import path from "path";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { Singleton } from "../common/utils";
import { Singleton, toJS } from "../common/utils";
import logger from "../main/logger";
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store";
import { extensionInstaller, PackageJson } from "./extension-installer";
@@ -86,13 +86,22 @@ export class ExtensionDiscovery extends Singleton {
// True if extensions have been loaded from the disk after app startup
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
get whenLoaded() {
return when(() => this.isLoaded);
}
// IPC channel to broadcast changes to extension-discovery from main
protected static readonly extensionDiscoveryChannel = "extension-discovery:main";
public events = new EventEmitter();
constructor() {
super();
makeObservable(this);
}
get localFolderPath(): string {
return path.join(os.homedir(), ".k8slens", "extensions");
}
@@ -374,7 +383,7 @@ export class ExtensionDiscovery extends Singleton {
const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name));
for (const extension of userExtensions) {
if (await fse.pathExists(extension.manifestPath) === false) {
if ((await fse.pathExists(extension.manifestPath)) === false) {
await this.installPackage(extension.absolutePath);
}
}
@@ -468,8 +477,6 @@ export class ExtensionDiscovery extends Singleton {
toJSON(): ExtensionDiscoveryChannelMessage {
return toJS({
isLoaded: this.isLoaded
}, {
recurseEverything: true
});
}
+30 -23
View File
@@ -22,11 +22,11 @@
import { app, ipcRenderer, remote } from "electron";
import { EventEmitter } from "events";
import { isEqual } from "lodash";
import { action, computed, observable, reaction, toJS, when } from "mobx";
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
import path from "path";
import { getHostedCluster } from "../common/cluster-store";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { Singleton } from "../common/utils";
import { Singleton, toJS } from "../common/utils";
import logger from "../main/logger";
import type { InstalledExtension } from "./extension-discovery";
import { ExtensionsStore } from "./extensions-store";
@@ -58,7 +58,16 @@ export class ExtensionLoader extends Singleton {
private events = new EventEmitter();
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
get whenLoaded() {
return when(() => this.isLoaded);
}
constructor() {
super();
makeObservable(this);
}
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.toJSON();
@@ -75,7 +84,7 @@ export class ExtensionLoader extends Singleton {
@computed get userExtensionsByName(): Map<string, LensExtension> {
const extensions = new Map();
for (const [, val] of this.instances.toJS()) {
for (const [, val] of this.instances.toJSON()) {
if (val.isBundled) {
continue;
}
@@ -117,6 +126,11 @@ export class ExtensionLoader extends Singleton {
await Promise.all([this.whenLoaded, ExtensionsStore.getInstance().whenLoaded]);
// broadcasting extensions between main/renderer processes
reaction(() => this.toJSON(), () => this.broadcastExtensions(), {
fireImmediately: true,
});
// save state on change `extension.isEnabled`
reaction(() => this.storeState, extensionsState => {
ExtensionsStore.getInstance().mergeState(extensionsState);
@@ -156,14 +170,10 @@ export class ExtensionLoader extends Singleton {
}
}
protected async initMain() {
protected async initMain() {
this.isLoaded = true;
this.loadOnMain();
reaction(() => this.toJSON(), () => {
this.broadcastExtensions();
});
handleRequest(ExtensionLoader.extensionsMainChannel, () => {
return Array.from(this.toJSON());
});
@@ -173,7 +183,7 @@ export class ExtensionLoader extends Singleton {
});
}
protected async initRenderer() {
protected async initRenderer() {
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true;
this.syncExtensions(extensions);
@@ -188,16 +198,20 @@ export class ExtensionLoader extends Singleton {
});
};
reaction(() => this.toJSON(), () => {
this.broadcastExtensions(false);
});
requestMain(ExtensionLoader.extensionsMainChannel).then(extensionListHandler);
subscribeToBroadcast(ExtensionLoader.extensionsMainChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
extensionListHandler(extensions);
});
}
broadcastExtensions() {
const channel = ipcRenderer
? ExtensionLoader.extensionsRendererChannel
: ExtensionLoader.extensionsMainChannel;
broadcastMessage(channel, Array.from(this.extensions));
}
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
extensions.forEach(([lensExtensionId, extension]) => {
if (!isEqual(this.extensions.get(lensExtensionId), extension)) {
@@ -255,7 +269,7 @@ export class ExtensionLoader extends Singleton {
const cluster = getHostedCluster();
this.autoInitExtensions(async (extension: LensRendererExtension) => {
if (await extension.isEnabledForCluster(cluster) === false) {
if ((await extension.isEnabledForCluster(cluster)) === false) {
return [];
}
@@ -334,13 +348,6 @@ export class ExtensionLoader extends Singleton {
}
toJSON(): Map<LensExtensionId, InstalledExtension> {
return toJS(this.extensions, {
exportMapsAsObjects: false,
recurseEverything: true,
});
}
broadcastExtensions(main = true) {
broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
return toJS(this.extensions);
}
}
+4 -4
View File
@@ -21,7 +21,8 @@
import type { LensExtensionId } from "./lens-extension";
import { BaseStore } from "../common/base-store";
import { action, computed, observable, toJS } from "mobx";
import { action, computed, observable, makeObservable } from "mobx";
import { toJS } from "../common/utils";
export interface LensExtensionsStoreModel {
extensions: Record<LensExtensionId, LensExtensionState>;
@@ -37,6 +38,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
super({
configName: "lens-extensions",
});
makeObservable(this);
}
@computed
@@ -68,9 +70,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
toJSON(): LensExtensionsStoreModel {
return toJS({
extensions: this.state.toJSON(),
}, {
recurseEverything: true
extensions: Object.fromEntries(this.state),
});
}
}
+2 -1
View File
@@ -20,7 +20,7 @@
*/
import type { InstalledExtension } from "./extension-discovery";
import { action, observable, reaction } from "mobx";
import { action, observable, reaction, makeObservable } from "mobx";
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import logger from "../main/logger";
import type { ProtocolHandlerRegistration } from "./registries";
@@ -52,6 +52,7 @@ export class LensExtension {
[Disposers] = disposer();
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
makeObservable(this);
this.id = id;
this.manifest = manifest;
this.manifestPath = manifestPath;
@@ -75,15 +75,22 @@ describe("getPageUrl", () => {
});
it("gets page url with custom params", () => {
const params: PageParams<string> = { test1: "one", test2: "2" };
const params: PageParams = { test1: "one", test2: "2" };
const searchParams = new URLSearchParams(params);
const pageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", params });
const pageUrl = getExtensionPageUrl({
extensionId: ext.name,
pageId: "page-with-params",
params,
});
expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`);
});
it("gets page url with default custom params", () => {
const defaultPageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", });
const defaultPageUrl = getExtensionPageUrl({
extensionId: ext.name,
pageId: "page-with-params",
});
expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`);
});
+5 -1
View File
@@ -20,12 +20,16 @@
*/
// Base class for extensions-api registries
import { action, observable } from "mobx";
import { action, observable, makeObservable } from "mobx";
import { LensExtension } from "../lens-extension";
export class BaseRegistry<T, I = T> {
private items = observable.map<T, I>();
constructor() {
makeObservable(this);
}
getItems(): I[] {
return Array.from(this.items.values());
}
+10 -5
View File
@@ -22,9 +22,9 @@
// Extensions API -> Commands
import { BaseRegistry } from "./base-registry";
import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
import { CatalogEntity } from "../../common/catalog";
import { makeObservable, observable } from "mobx";
import type { LensExtension } from "../lens-extension";
import type { CatalogEntity } from "../../common/catalog";
export type CommandContext = {
entity?: CatalogEntity;
@@ -39,9 +39,14 @@ export interface CommandRegistration {
}
export class CommandRegistry extends BaseRegistry<CommandRegistration> {
@observable activeEntity: CatalogEntity;
@observable.ref activeEntity: CatalogEntity;
constructor() {
super();
makeObservable(this);
}
@action
add(items: CommandRegistration | CommandRegistration[], extension?: LensExtension) {
const itemArray = [items].flat();
@@ -23,9 +23,8 @@
import type { IconProps } from "../../renderer/components/icon";
import type React from "react";
import type { PageTarget, RegisteredPage } from "./page-registry";
import { action } from "mobx";
import type { LensExtension } from "../lens-extension";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
export interface PageMenuRegistration {
target?: PageTarget;
@@ -43,7 +42,6 @@ export interface PageMenuComponents {
}
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
@action
add(items: T[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
menuItem.target = {
+44 -26
View File
@@ -24,9 +24,8 @@
import React from "react";
import { observer } from "mobx-react";
import { BaseRegistry } from "./base-registry";
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
import type { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { createPageParam } from "../../renderer/navigation/helpers";
import { LensExtension, LensExtensionId, sanitizeExtensionName } from "../lens-extension";
import { createPageParam, PageParam, PageParamInit, searchParamsOptions } from "../../renderer/navigation";
export interface PageRegistration {
/**
@@ -34,21 +33,18 @@ export interface PageRegistration {
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
*/
id?: string;
params?: PageParams<string | ExtensionPageParamInit>;
params?: PageParams<string | Omit<PageParamInit<any>, "name" | "prefix">>;
components: PageComponents;
}
// exclude "name" field since provided as key in page.params
export type ExtensionPageParamInit = Omit<PageParamInit, "name" | "isSystem">;
export interface PageComponents {
Page: React.ComponentType<any>;
}
export interface PageTarget<P = PageParams> {
export interface PageTarget {
extensionId?: string;
pageId?: string;
params?: P;
params?: PageParams;
}
export interface PageParams<V = any> {
@@ -83,13 +79,12 @@ export function getExtensionPageUrl(target: PageTarget): string {
if (registeredPage?.params) {
Object.entries(registeredPage.params).forEach(([name, param]) => {
const paramValue = param.stringify(targetParams[name]);
pageUrl.searchParams.delete(name); // first off, clear existing value(s)
if (param.init.skipEmpty && param.isEmpty(paramValue)) {
pageUrl.searchParams.delete(name);
} else {
pageUrl.searchParams.set(name, paramValue);
}
param.stringify(targetParams[name]).forEach(value => {
if (searchParamsOptions.skipEmpty && !value) return;
pageUrl.searchParams.append(name, value);
});
});
}
@@ -100,7 +95,7 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage {
const { id: pageId } = page;
const extensionId = ext.name;
const params = this.normalizeParams(page.params);
const params = this.normalizeParams(extensionId, page.params);
const components = this.normalizeComponents(page.components, params);
const url = getExtensionPageUrl({ extensionId, pageId });
@@ -113,25 +108,48 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
if (params) {
const { Page } = components;
// inject extension's page component props.params
components.Page = observer((props: object) => React.createElement(Page, { params, ...props }));
}
return components;
}
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> | undefined {
if (!params) {
return undefined;
}
Object.entries(params).forEach(([name, value]) => {
const paramInit: PageParamInit = typeof value === "object"
? { name, ...value }
: { name, defaultValue: value };
protected normalizeParams(extensionId: LensExtensionId, params?: PageParams<string | Partial<PageParamInit>>): PageParams<PageParam> {
if (!params) return undefined;
const normalizedParams: PageParams<PageParam> = {};
params[paramInit.name] = createPageParam(paramInit);
Object.entries(params).forEach(([paramName, paramValue]) => {
const paramInit: PageParamInit = {
name: paramName,
prefix: `${extensionId}:`,
defaultValue: paramValue,
};
// handle non-string params
if (typeof paramValue !== "string") {
const { defaultValue: value, parse, stringify } = paramValue;
const notAStringValue = typeof value !== "string" || (
Array.isArray(value) && !value.every(value => typeof value === "string")
);
if (notAStringValue && !(parse || stringify)) {
throw new Error(
`PageRegistry: param's "${paramName}" initialization has failed:
paramInit.parse() and paramInit.stringify() are required for non string | string[] "defaultValue"`
);
}
paramInit.defaultValue = value;
paramInit.parse = parse;
paramInit.stringify = stringify;
}
normalizedParams[paramName] = createPageParam(paramInit);
});
return params as PageParams<PageParam>;
return normalizedParams;
}
getByPageTarget(target: PageTarget): RegisteredPage | null {
+2 -4
View File
@@ -19,15 +19,13 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { navigation } from "../../renderer/navigation";
import { navigation, PageParam, PageParamInit } from "../../renderer/navigation";
export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
export { navigate, isActiveRoute } from "../../renderer/navigation/helpers";
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
export type { IURLParams } from "../../common/utils/buildUrl";
// exporting to extensions-api version of helper without `isSystem` flag
export function createPageParam<V = string>(init: PageParamInit<V>) {
export function createPageParam<V>(init: PageParamInit<V>) {
return new PageParam<V>(init, navigation);
}