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

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

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-05-06 17:42:53 +03:00
parent 4a9178e8ea
commit 5d09d7a126
21 changed files with 95 additions and 79 deletions

View File

@ -50,9 +50,9 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
}
toJSON(): ExamplePreferencesModel {
return toJS({
enabled: this.enabled
});
return {
enabled: toJS(this.enabled)
};
}
}
```

View File

@ -1,5 +1,6 @@
import { action, computed, observable, toJS, makeObservable } from "mobx";
import { action, computed, observable, makeObservable } from "mobx";
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import { toJS } from "../utils";
export class CatalogCategoryRegistry {
@observable protected categories: CatalogCategory[] = [];

View File

@ -1,7 +1,7 @@
import path from "path";
import { app, ipcMain, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra";
import { action, comparer, computed, observable, reaction, toJS, makeObservable } from "mobx";
import { action, comparer, computed, observable, reaction, makeObservable } from "mobx";
import { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store";
@ -12,7 +12,7 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
import { KubeConfig } from "@kubernetes/client-node";
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
import { disposer, noop } from "./utils";
import { disposer, noop, toJS } from "./utils";
export interface ClusterIconUpload {
clusterId: string;

View File

@ -2,24 +2,6 @@
import * as Mobx from "mobx";
import * as Immer from "immer";
const { isObservable, toJS, observable } = Mobx;
/**
* Patch-fixing mobx@6.toJS() to support partially observable objects as data-input.
* Otherwise it won't be recursively converted to corresponding non-observable plain JS-structure.
* @example
* data = {one: 1, two: observable.array([2])}; // "data" itself is non-observable
*/
Object.defineProperty(Mobx, "toJS", {
value(data: any) {
if (typeof data === "object" && !isObservable(data)) {
return toJS(observable.box(data).get());
}
return toJS(data);
}
});
export default function configurePackages() {
// Docs: https://mobx.js.org/configuration.html
Mobx.configure({
@ -33,7 +15,7 @@ export default function configurePackages() {
});
// Docs: https://immerjs.github.io/immer/
// Required for `storage-helper.ts`
// Required in `utils/storage-helper.ts`
Immer.setAutoFreeze(false); // allow to merge mobx observables
Immer.enableMapSet(); // allow to merge maps and sets
}

View File

@ -1,9 +1,10 @@
import { action, comparer, observable, toJS, makeObservable } from "mobx";
import { action, comparer, observable, makeObservable } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import * as uuid from "uuid";
import { CatalogEntityItem } from "../renderer/components/+catalog/catalog-entity.store";
import isNull from "lodash/isNull";
import { toJS } from "./utils";
export interface HotbarItem {
entity: {

View File

@ -3,32 +3,33 @@
// https://www.electronjs.org/docs/api/ipc-renderer
import { ipcMain, ipcRenderer, remote, webContents } from "electron";
import { toJS } from "mobx";
import { toJS } from "../utils/toJS";
import logger from "../../main/logger";
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
const subFramesChannel = "ipc:get-sub-frames";
export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
ipcMain.handle(channel, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => {
args = toJS(args); // unwrap possibly leaking observable values
ipcMain.handle(channel, async (event, ...args) => {
const payload = await listener(event, ...args);
return listener(event, ...args);
return sanitizePayload(payload);
});
}
export async function requestMain(channel: string, ...args: any[]) {
return ipcRenderer.invoke(channel, ...toJS(args));
return ipcRenderer.invoke(channel, ...args.map(sanitizePayload));
}
function getSubFrames(): ClusterFrameInfo[] {
return toJS(Array.from(clusterFrameMap.values()));
return Array.from(clusterFrameMap.values());
}
export async function broadcastMessage(channel: string, ...args: any[]) {
const views = (webContents || remote?.webContents)?.getAllWebContents();
if (!views) return;
args = args.map(sanitizePayload);
if (ipcRenderer) {
ipcRenderer.send(channel, ...args);
@ -83,7 +84,13 @@ export function unsubscribeAllFromBroadcast(channel: string) {
}
export function bindBroadcastHandlers() {
handleRequest(subFramesChannel, () => {
return getSubFrames();
});
handleRequest(subFramesChannel, () => getSubFrames());
}
/**
* Sanitizing data for IPC-messaging before send.
* Removes possible observable values to avoid exceptions like "can't clone object".
*/
function sanitizePayload<T>(data: any): T {
return toJS(data);
}

View File

@ -2,7 +2,7 @@ import type { ThemeId } from "../renderer/theme.store";
import { app, remote } from "electron";
import semver from "semver";
import { readFile } from "fs-extra";
import { action, computed, observable, reaction, toJS, makeObservable } from "mobx";
import { action, computed, observable, reaction, makeObservable } from "mobx";
import moment from "moment-timezone";
import { BaseStore } from "./base-store";
import migrations from "../migrations/user-store";
@ -13,7 +13,7 @@ import logger from "../main/logger";
import path from "path";
import os from "os";
import { fileNameMigration } from "../migrations/user-store";
import { ObservableToggleSet } from "../renderer/utils";
import { ObservableToggleSet, toJS } from "../renderer/utils";
export interface UserStoreModel {
kubeConfigPath: string;

View File

@ -0,0 +1,25 @@
import { isObservable, observable } from "mobx";
import { toJS } from "../toJS";
describe("utils/toJS(data: any)", () => {
const y = { y: 2 };
const data = observable({ x: 1, y }, {}, {
deep: false, // this will keep ref to "y"
});
const data2 = {
x: 1, // partially observable
y: observable(y),
};
test("converts mobx-observable to corresponding js struct with links preserving", () => {
expect(toJS(data).y).toBe(y);
expect(isObservable(toJS(data).y)).toBeFalsy();
});
test("converts partially observable js struct", () => {
expect(toJS(data2).y).not.toBe(y);
expect(toJS(data2).y).toEqual(y);
expect(isObservable(toJS(data2).y)).toBeFalsy();
});
});

View File

@ -6,6 +6,7 @@ export * from "./app-version";
export * from "./autobind";
export * from "./base64";
export * from "./camelCase";
export * from "./toJS";
export * from "./cloneJson";
export * from "./debouncePromise";
export * from "./defineGlobal";

18
src/common/utils/toJS.ts Normal file
View File

@ -0,0 +1,18 @@
/**
* Wrapper for mobx.toJS() to support partially observable objects as data-input (>= mobx6).
* Otherwise, output result won't be recursively converted to corresponding plain JS-structure.
*
* @example
* mobx.toJS({one: 1, two: observable.array([2])}); // "data.two" == ObservableArray<number>
*/
import * as mobx from "mobx";
import { isObservable, observable } from "mobx";
export function toJS<T>(data: T): T {
// make data observable for recursive toJS()-output
if (typeof data === "object" && !isObservable(data)) {
return mobx.toJS(observable.box(data).get());
}
return mobx.toJS(data);
}

View File

@ -1,4 +1,4 @@
import { action, IEnhancer, IObservableSetInitialValues, makeObservable, ObservableSet } from "mobx";
import { ObservableSet } from "mobx";
export class ToggleSet<T> extends Set<T> {
public toggle(value: T): void {
@ -10,13 +10,6 @@ export class ToggleSet<T> extends Set<T> {
}
export class ObservableToggleSet<T> extends ObservableSet<T> {
constructor(data?: IObservableSetInitialValues<T>, enhancer?: IEnhancer<T>) {
super(data, enhancer);
makeObservable(this);
}
@action
public toggle(value: T): void {
if (!this.delete(value)) {
// Set.prototype.delete returns false if `value` was not in the set

View File

@ -2,11 +2,11 @@ import { watch } from "chokidar";
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import fse from "fs-extra";
import { observable, reaction, toJS, when, makeObservable } 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";

View File

@ -1,6 +1,7 @@
import type { LensExtensionId } from "./lens-extension";
import { BaseStore } from "../common/base-store";
import { action, computed, observable, toJS, makeObservable } from "mobx";
import { action, computed, observable, makeObservable } from "mobx";
import { toJS } from "../common/utils";
export interface LensExtensionsStoreModel {
extensions: Record<LensExtensionId, LensExtensionState>;

View File

@ -1,8 +1,8 @@
import { reaction, toJS } from "mobx";
import { reaction } from "mobx";
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "../common/ipc";
import { CatalogEntityRegistry} from "../common/catalog";
import "../common/catalog-entities/kubernetes-cluster";
import { Disposer } from "../common/utils";
import { Disposer, toJS } from "../common/utils";
export class CatalogPusher {
static init(catalog: CatalogEntityRegistry) {

View File

@ -1,12 +1,12 @@
import "../common/cluster-ipc";
import type http from "http";
import { ipcMain } from "electron";
import { action, autorun, reaction, toJS, makeObservable } from "mobx";
import { action, autorun, makeObservable, reaction } from "mobx";
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
import { Cluster } from "./cluster";
import logger from "./logger";
import { apiKubePrefix } from "../common/vars";
import { Singleton } from "../common/utils";
import { Singleton, toJS } from "../common/utils";
import { catalogEntityRegistry } from "../common/catalog";
import { KubernetesCluster } from "../common/catalog-entities/kubernetes-cluster";
@ -24,7 +24,6 @@ export class ClusterManager extends Singleton {
this.syncClustersFromCatalog(entities);
});
// auto-stop removed clusters
autorun(() => {
const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values());
@ -143,9 +142,7 @@ export class ClusterManager extends Singleton {
}
export function catalogEntityFromCluster(cluster: Cluster) {
return new KubernetesCluster(toJS({
apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster",
return new KubernetesCluster({
metadata: {
uid: cluster.id,
name: cluster.name,
@ -164,5 +161,5 @@ export function catalogEntityFromCluster(cluster: Cluster) {
message: "",
active: !cluster.disconnected
}
}));
});
}

View File

@ -1,15 +1,6 @@
import { ipcMain } from "electron";
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-store";
import {
action,
comparer,
computed,
observable,
reaction,
toJS,
when,
makeObservable,
} from "mobx";
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
import { ContextHandler } from "./context-handler";
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
@ -21,6 +12,7 @@ import logger from "./logger";
import { VersionDetector } from "./cluster-detectors/version-detector";
import { detectorRegistry } from "./cluster-detectors/detector-registry";
import plimit from "p-limit";
import { toJS } from "../common/utils";
export enum ClusterStatus {
AccessGranted = 2,

View File

@ -2,10 +2,11 @@ import { randomBytes } from "crypto";
import { SHA256 } from "crypto-js";
import { app, remote } from "electron";
import fse from "fs-extra";
import { action, observable, toJS, makeObservable } from "mobx";
import { action, observable, makeObservable } from "mobx";
import path from "path";
import { BaseStore } from "../common/base-store";
import { LensExtensionId } from "../extensions/lens-extension";
import { toJS } from "../common/utils";
interface FSProvisionModel {
extensions: Record<string, string>; // extension names to paths

View File

@ -4,11 +4,11 @@ import React from "react";
import kebabCase from "lodash/kebabCase";
import { disposeOnUnmount, observer } from "mobx-react";
import { Link } from "react-router-dom";
import { autorun, observable, reaction, toJS, makeObservable } from "mobx";
import { autorun, observable, reaction, makeObservable } from "mobx";
import { IPodMetrics, nodesApi, Pod, pvcApi, configMapApi } from "../../api/endpoints";
import { DrawerItem, DrawerTitle } from "../drawer";
import { Badge } from "../badge";
import { boundMethod, cssNames, interval } from "../../utils";
import { boundMethod, cssNames, interval, toJS } from "../../utils";
import { PodDetailsContainer } from "./pod-details-container";
import { PodDetailsAffinities } from "./pod-details-affinities";
import { PodDetailsTolerations } from "./pod-details-tolerations";

View File

@ -1,6 +1,5 @@
import { Select } from "../select";
import { computed, observable, toJS, makeObservable } from "mobx";
import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { commandRegistry } from "../../../extensions/registries/command-registry";
@ -52,13 +51,11 @@ export class CommandDialog extends React.Component {
return;
}
const action = toJS(command.action);
try {
CommandOverlay.close();
if (command.scope === "global") {
action({
command.action({
entity: commandRegistry.activeEntity
});
} else if(commandRegistry.activeEntity) {

View File

@ -1,5 +1,5 @@
import { autorun, observable, reaction, toJS } from "mobx";
import { autoBind, createStorage, StorageHelper } from "../../utils";
import { autorun, observable, reaction } from "mobx";
import { autoBind, createStorage, StorageHelper, toJS } from "../../utils";
import { dockStore, TabId } from "./dock.store";
export interface DockTabStoreOptions {

View File

@ -1,11 +1,11 @@
import debounce from "lodash/debounce";
import { reaction, toJS } from "mobx";
import { reaction } from "mobx";
import { Terminal as XTerm } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { dockStore, TabId } from "./dock.store";
import { TerminalApi } from "../../api/terminal-api";
import { ThemeStore } from "../../theme.store";
import { boundMethod } from "../../utils";
import { boundMethod, toJS } from "../../utils";
import { isMac } from "../../../common/vars";
import { camelCase } from "lodash";