Remove BaseStore<T> and replace with composition of dependencies (#7002)

* Rename ExtensionStore -> BaseExtensionStore
- The name was too close to ExtensionsStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move ExtensionsStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move ClusterStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move UserStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup types to remove multiple cast locations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move HotbarStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move WeblinkStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move FileSystemProvisionerStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Clean up impl and rename to better describe intent

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix remaining type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the enabled extensions storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the clusters storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the hotbar storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the weblinks storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the user preferences storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crashing

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests and snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix integration test failures

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Improve typing to prevent errors in the future.

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup @k8slens/messaging and friends

- To fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint issue

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix global override not being complete enough causing tests to fail

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Bump memory for unit tests on CI

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Attempt to fix memory issue on CI again

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fixup test because of new injectables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Upgrade Jest

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix unit tests falling over

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Back out jest config change

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove console log

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshot

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by matching equality instead of snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by forcing specific snapshot style

- Ubuntu CI seems to format arrays in snapshots differently than macOS locally

Signed-off-by: Sebastian Malton <sebastian@malton.name>

---------

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2023-03-28 17:54:25 +03:00
1 parent ba4a283af9
commit 49f0a1af9c
365 files changed
+6733 -3972

No files matched your search

@@ -0,0 +1,18 @@
/**
* 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 { computed } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
const httpsProxyConfigurationInjectable = getInjectable({
id: "https-proxy-configuration",
instantiate: (di) => {
const userStore = di.inject(userPreferencesStateInjectable);
return computed(() => userStore.httpsProxy);
},
});
export default httpsProxyConfigurationInjectable;
@@ -0,0 +1,38 @@
/**
* 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 userPreferencesStateInjectable from "./state.injectable";
/**
* Checks if a column (by ID) for a table (by ID) is configured to be hidden
* @param tableId The ID of the table to be checked against
* @param columnIds The list of IDs the check if one is hidden
* @returns true if at least one column under the table is set to hidden
*/
export type IsTableColumnHidden = (tableId: string, ...columnIds: (string | undefined)[]) => boolean;
const isTableColumnHiddenInjectable = getInjectable({
id: "is-table-column-hidden",
instantiate: (di): IsTableColumnHidden => {
const state = di.inject(userPreferencesStateInjectable);
return (tableId, ...columnIds) => {
if (columnIds.length === 0) {
return false;
}
const config = state.hiddenTableColumns.get(tableId);
if (!config) {
return false;
}
return columnIds.some(columnId => columnId && config.has(columnId));
};
},
});
export default isTableColumnHiddenInjectable;
@@ -0,0 +1,13 @@
/**
* 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 userPreferencesStateInjectable from "./state.injectable";
const kubeconfigSyncsInjectable = getInjectable({
id: "kubeconfig-syncs",
instantiate: (di) => di.inject(userPreferencesStateInjectable).syncKubeconfigEntries,
});
export default kubeconfigSyncsInjectable;
@@ -0,0 +1,37 @@
/**
* 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 { computed } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
export type LensColorThemePreference = {
useSystemTheme: true;
} | {
useSystemTheme: false;
lensThemeId: string;
};
const lensColorThemePreferenceInjectable = getInjectable({
id: "lens-color-theme-preference",
instantiate: (di) => {
const state = di.inject(userPreferencesStateInjectable);
return computed((): LensColorThemePreference => {
// TODO: remove magic strings
if (state.colorTheme === "system") {
return {
useSystemTheme: true,
};
}
return {
useSystemTheme: false,
lensThemeId: state.colorTheme,
};
});
},
});
export default lensColorThemePreferenceInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { MigrationDeclaration } from "../../../common/persistent-storage/migrations.injectable";
export const userPreferencesMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
id: "user-preferences-migration-token",
});
@@ -0,0 +1,143 @@
/**
* 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 { merge } from "lodash";
import type { ObservableMap } from "mobx";
import { observable } from "mobx";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import { defaultThemeId } from "../../../common/vars";
import currentTimezoneInjectable from "../../../common/vars/current-timezone.injectable";
import type { EditorConfiguration, ExtensionRegistry, KubeconfigSyncEntry, KubeconfigSyncValue, TerminalConfig } from "./preferences-helpers";
import { defaultExtensionRegistryUrlLocation, defaultEditorConfig, defaultTerminalConfig, defaultPackageMirror, getPreferenceDescriptor, packageMirrors } from "./preferences-helpers";
export type PreferenceDescriptors = ReturnType<typeof userPreferenceDescriptorsInjectable["instantiate"]>;
const userPreferenceDescriptorsInjectable = getInjectable({
id: "user-preference-descriptors",
instantiate: (di) => {
const currentTimezone = di.inject(currentTimezoneInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
const mainKubeFolderPath = joinPaths(homeDirectoryPath, ".kube");
return ({
httpsProxy: getPreferenceDescriptor<string | undefined>({
fromStore: val => val,
toStore: val => val || undefined,
}),
shell: getPreferenceDescriptor<string | undefined>({
fromStore: val => val,
toStore: val => val || undefined,
}),
colorTheme: getPreferenceDescriptor<string>({
fromStore: val => val || defaultThemeId,
toStore: val => !val || val === defaultThemeId
? undefined
: val,
}),
terminalTheme: getPreferenceDescriptor<string>({
fromStore: val => val || "",
toStore: val => val || undefined,
}),
localeTimezone: getPreferenceDescriptor<string>({
fromStore: val => val || currentTimezone,
toStore: val => !val || val === currentTimezone
? undefined
: val,
}),
allowUntrustedCAs: getPreferenceDescriptor<boolean>({
fromStore: val => val ?? false,
toStore: val => !val
? undefined
: val,
}),
allowErrorReporting: getPreferenceDescriptor<boolean>({
fromStore: val => val ?? true,
toStore: val => val
? undefined
: val,
}),
downloadMirror: getPreferenceDescriptor<string>({
fromStore: val => !val || !packageMirrors.has(val)
? defaultPackageMirror
: val,
toStore: val => val === defaultPackageMirror
? undefined
: val,
}),
downloadKubectlBinaries: getPreferenceDescriptor<boolean>({
fromStore: val => val ?? true,
toStore: val => val
? undefined
: val,
}),
downloadBinariesPath: getPreferenceDescriptor<string | undefined>({
fromStore: val => val,
toStore: val => val || undefined,
}),
kubectlBinariesPath: getPreferenceDescriptor<string | undefined>({
fromStore: val => val,
toStore: val => val || undefined,
}),
openAtLogin: getPreferenceDescriptor<boolean>({
fromStore: val => val ?? false,
toStore: val => !val
? undefined
: val,
}),
terminalCopyOnSelect: getPreferenceDescriptor<boolean>({
fromStore: val => val ?? false,
toStore: val => !val
? undefined
: val,
}),
hiddenTableColumns: getPreferenceDescriptor<[string, string[]][], Map<string, Set<string>>>({
fromStore: (val = []) => new Map(
val.map(([tableId, columnIds]) => [tableId, new Set(columnIds)]),
),
toStore: (val) => {
const res: [string, string[]][] = [];
for (const [table, columns] of val) {
if (columns.size) {
res.push([table, Array.from(columns)]);
}
}
return res.length ? res : undefined;
},
}),
syncKubeconfigEntries: getPreferenceDescriptor<KubeconfigSyncEntry[], ObservableMap<string, KubeconfigSyncValue>>({
fromStore: val => observable.map(
val?.map(({ filePath, ...rest }) => [filePath, rest])
?? [[mainKubeFolderPath, {}]],
),
toStore: val => val.size === 1 && val.has(mainKubeFolderPath)
? undefined
: Array.from(val, ([filePath, rest]) => ({ filePath, ...rest })),
}),
editorConfiguration: getPreferenceDescriptor<Partial<EditorConfiguration>, EditorConfiguration>({
fromStore: val => merge(defaultEditorConfig, val),
toStore: val => val,
}),
terminalConfig: getPreferenceDescriptor<Partial<TerminalConfig>, TerminalConfig>({
fromStore: val => merge(defaultTerminalConfig, val),
toStore: val => val,
}),
extensionRegistryUrl: getPreferenceDescriptor<ExtensionRegistry>({
fromStore: val => val ?? {
location: defaultExtensionRegistryUrlLocation,
},
toStore: val => val.location === defaultExtensionRegistryUrlLocation
? undefined
: val,
}),
}) as const;
},
});
export default userPreferenceDescriptorsInjectable;
@@ -0,0 +1,99 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { editor } from "monaco-editor";
import { defaultFontSize, defaultTerminalFontFamily, defaultEditorFontFamily } from "../../../common/vars";
import type { PreferenceDescriptors } from "./preference-descriptors.injectable";
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
filePath: string;
}
export interface KubeconfigSyncValue {
}
export interface TerminalConfig {
fontSize: number;
fontFamily: string;
}
export const defaultTerminalConfig: TerminalConfig = {
fontSize: defaultFontSize,
fontFamily: defaultTerminalFontFamily,
};
export interface BaseEditorConfiguration extends Required<Pick<editor.IStandaloneEditorConstructionOptions, "minimap" | "tabSize" | "fontSize" | "fontFamily">> {
lineNumbers: NonNullable<Exclude<editor.IStandaloneEditorConstructionOptions["lineNumbers"], Function>>;
}
export type EditorConfiguration = Required<BaseEditorConfiguration>;
export const defaultEditorConfig: EditorConfiguration = {
tabSize: 2,
lineNumbers: "on",
fontSize: defaultFontSize,
fontFamily: defaultEditorFontFamily,
minimap: {
enabled: true,
side: "right",
},
};
export type StoreType<P> = P extends PreferenceDescription<unknown, infer Store>
? Store
: never;
export interface PreferenceDescription<T, R = T> {
fromStore(val: T | undefined): R;
toStore(val: R): T | undefined;
}
export const getPreferenceDescriptor = <T, R = T>(desc: PreferenceDescription<T, R>) => desc;
export interface DownloadMirror {
url: string;
label: string;
platforms: Set<NodeJS.Platform>;
}
export const defaultPackageMirror = "default";
const defaultDownloadMirrorData: DownloadMirror = {
url: "https://storage.googleapis.com/kubernetes-release/release",
label: "Default (Google)",
platforms: new Set(["darwin", "win32", "linux"]),
};
export const packageMirrors = new Map<string, DownloadMirror>([
[defaultPackageMirror, defaultDownloadMirrorData],
["china", {
url: "https://mirror.azure.cn/kubernetes/kubectl",
label: "China (Azure)",
platforms: new Set(["win32", "linux"]),
}],
]);
export type ExtensionRegistryLocation = "default" | "npmrc" | "custom";
export type ExtensionRegistry = {
location: "default" | "npmrc";
customUrl?: undefined;
} | {
location: "custom";
customUrl: string;
};
export const defaultExtensionRegistryUrlLocation = "default";
export const defaultExtensionRegistryUrl = "https://registry.npmjs.org";
type PreferencesModelType<field extends keyof PreferenceDescriptors> = PreferenceDescriptors[field] extends PreferenceDescription<infer T, any> ? T : never;
type UserStoreModelType<field extends keyof PreferenceDescriptors> = PreferenceDescriptors[field] extends PreferenceDescription<any, infer T> ? T : never;
export type UserStoreFlatModel = {
[field in keyof PreferenceDescriptors]: UserStoreModelType<field>;
};
export type UserPreferencesModel = {
[field in keyof PreferenceDescriptors]?: PreferencesModelType<field>;
} & { updateChannel: string };
@@ -0,0 +1,24 @@
/**
* 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 { action } from "mobx";
import userPreferenceDescriptorsInjectable from "./preference-descriptors.injectable";
import userPreferencesStateInjectable from "./state.injectable";
export type ResetTheme = () => void;
const resetThemeInjectable = getInjectable({
id: "reset-theme",
instantiate: (di): ResetTheme => {
const state = di.inject(userPreferencesStateInjectable);
const preferenceDescriptors = di.inject(userPreferenceDescriptorsInjectable);
return action(() => {
state.colorTheme = preferenceDescriptors.colorTheme.fromStore(undefined);
});
},
});
export default resetThemeInjectable;
@@ -0,0 +1,20 @@
/**
* 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 { computed } from "mobx";
import userInfoInjectable from "../../../common/vars/user-info.injectable";
import userPreferencesStateInjectable from "./state.injectable";
const userShellSettingInjectable = getInjectable({
id: "user-shell-setting",
instantiate: (di) => {
const state = di.inject(userPreferencesStateInjectable);
const userInfo = di.inject(userInfoInjectable);
return computed(() => state.shell || userInfo.shell);
},
});
export default userShellSettingInjectable;
@@ -0,0 +1,19 @@
/**
* 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 { observable } from "mobx";
import type { PreferenceDescriptors } from "./preference-descriptors.injectable";
import type { StoreType } from "./preferences-helpers";
export type UserPreferencesState = {
-readonly [Field in keyof PreferenceDescriptors]: StoreType<PreferenceDescriptors[Field]>;
};
const userPreferencesStateInjectable = getInjectable({
id: "user-preferences-state",
instantiate: () => observable.object({} as UserPreferencesState),
});
export default userPreferencesStateInjectable;
@@ -0,0 +1,88 @@
/**
* 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 { action } from "mobx";
import prefixedLoggerInjectable from "../../../common/logger/prefixed-logger.injectable";
import createPersistentStorageInjectable from "../../../common/persistent-storage/create.injectable";
import persistentStorageMigrationsInjectable from "../../../common/persistent-storage/migrations.injectable";
import { userPreferencesMigrationInjectionToken } from "./migrations-token";
import { toJS } from "../../../common/utils";
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
import selectedUpdateChannelInjectable from "../../application-update/common/selected-update-channel/selected-update-channel.injectable";
import type { ReleaseChannel } from "../../application-update/common/update-channels";
import userPreferencesStateInjectable from "./state.injectable";
import userPreferenceDescriptorsInjectable from "./preference-descriptors.injectable";
import type { UserPreferencesModel } from "./preferences-helpers";
export interface UserStoreModel {
preferences: UserPreferencesModel;
}
const userPreferencesPersistentStorageInjectable = getInjectable({
id: "user-preferences-persistent-storage",
instantiate: (di) => {
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const logger = di.inject(prefixedLoggerInjectable, "USER-PREFERENCES");
const descriptors = di.inject(userPreferenceDescriptorsInjectable);
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const state = di.inject(userPreferencesStateInjectable);
return createPersistentStorage<UserStoreModel>({
configName: "lens-user-store",
projectVersion: di.inject(storeMigrationVersionInjectable),
migrations: di.inject(persistentStorageMigrationsInjectable, userPreferencesMigrationInjectionToken),
fromStore: action(({ preferences = {}}) => {
logger.debug("fromStore()", { preferences });
state.allowErrorReporting = descriptors.allowErrorReporting.fromStore(preferences.allowErrorReporting);
state.allowUntrustedCAs = descriptors.allowUntrustedCAs.fromStore(preferences.allowUntrustedCAs);
state.colorTheme = descriptors.colorTheme.fromStore(preferences.colorTheme);
state.downloadBinariesPath = descriptors.downloadBinariesPath.fromStore(preferences.downloadBinariesPath);
state.downloadKubectlBinaries = descriptors.downloadKubectlBinaries.fromStore(preferences.downloadKubectlBinaries);
state.downloadMirror = descriptors.downloadMirror.fromStore(preferences.downloadMirror);
state.editorConfiguration = descriptors.editorConfiguration.fromStore(preferences.editorConfiguration);
state.extensionRegistryUrl = descriptors.extensionRegistryUrl.fromStore(preferences.extensionRegistryUrl);
state.hiddenTableColumns = descriptors.hiddenTableColumns.fromStore(preferences.hiddenTableColumns);
state.httpsProxy = descriptors.httpsProxy.fromStore(preferences.httpsProxy);
state.kubectlBinariesPath = descriptors.kubectlBinariesPath.fromStore(preferences.kubectlBinariesPath);
state.localeTimezone = descriptors.localeTimezone.fromStore(preferences.localeTimezone);
state.openAtLogin = descriptors.openAtLogin.fromStore(preferences.openAtLogin);
state.shell = descriptors.shell.fromStore(preferences.shell);
state.syncKubeconfigEntries = descriptors.syncKubeconfigEntries.fromStore(preferences.syncKubeconfigEntries);
state.terminalConfig = descriptors.terminalConfig.fromStore(preferences.terminalConfig);
state.terminalCopyOnSelect = descriptors.terminalCopyOnSelect.fromStore(preferences.terminalCopyOnSelect);
state.terminalTheme = descriptors.terminalTheme.fromStore(preferences.terminalTheme);
// TODO: Switch to action-based saving instead saving stores by reaction
selectedUpdateChannel.setValue(preferences?.updateChannel as ReleaseChannel);
}),
toJSON: () => toJS({
preferences: {
allowErrorReporting: descriptors.allowErrorReporting.toStore(state.allowErrorReporting),
allowUntrustedCAs: descriptors.allowUntrustedCAs.toStore(state.allowUntrustedCAs),
colorTheme: descriptors.colorTheme.toStore(state.colorTheme),
downloadBinariesPath: descriptors.downloadBinariesPath.toStore(state.downloadBinariesPath),
downloadKubectlBinaries: descriptors.downloadKubectlBinaries.toStore(state.downloadKubectlBinaries),
downloadMirror: descriptors.downloadMirror.toStore(state.downloadMirror),
editorConfiguration: descriptors.editorConfiguration.toStore(state.editorConfiguration),
extensionRegistryUrl: descriptors.extensionRegistryUrl.toStore(state.extensionRegistryUrl),
hiddenTableColumns: descriptors.hiddenTableColumns.toStore(state.hiddenTableColumns),
httpsProxy: descriptors.httpsProxy.toStore(state.httpsProxy),
kubectlBinariesPath: descriptors.kubectlBinariesPath.toStore(state.kubectlBinariesPath),
localeTimezone: descriptors.localeTimezone.toStore(state.localeTimezone),
openAtLogin: descriptors.openAtLogin.toStore(state.openAtLogin),
shell: descriptors.shell.toStore(state.shell),
syncKubeconfigEntries: descriptors.syncKubeconfigEntries.toStore(state.syncKubeconfigEntries),
terminalConfig: descriptors.terminalConfig.toStore(state.terminalConfig),
terminalCopyOnSelect: descriptors.terminalCopyOnSelect.toStore(state.terminalCopyOnSelect),
terminalTheme: descriptors.terminalTheme.toStore(state.terminalTheme),
updateChannel: selectedUpdateChannel.value.get().id,
},
}),
});
},
});
export default userPreferencesPersistentStorageInjectable;
@@ -0,0 +1,18 @@
/**
* 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 { computed, toJS } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
const terminalConfigInjectable = getInjectable({
id: "terminal-config",
instantiate: (di) => {
const state = di.inject(userPreferencesStateInjectable);
return computed(() => toJS(state.terminalConfig));
},
});
export default terminalConfigInjectable;
@@ -0,0 +1,18 @@
/**
* 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 { computed } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
const terminalCopyOnSelectInjectable = getInjectable({
id: "terminal-copy-on-select",
instantiate: (di) => {
const state = di.inject(userPreferencesStateInjectable);
return computed(() => state.terminalCopyOnSelect);
},
});
export default terminalCopyOnSelectInjectable;
@@ -0,0 +1,37 @@
/**
* 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 { computed } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
export type TerminalThemePreference = {
matchLensTheme: true;
} | {
matchLensTheme: false;
themeId: string;
};
const terminalThemePreferenceInjectable = getInjectable({
id: "terminal-theme-preference",
instantiate: (di) => {
const state = di.inject(userPreferencesStateInjectable);
return computed((): TerminalThemePreference => {
// NOTE: remove use of magic strings
if (!state.terminalTheme) {
return {
matchLensTheme: true,
};
}
return {
matchLensTheme: false,
themeId: state.terminalTheme,
};
});
},
});
export default terminalThemePreferenceInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getOrInsertSet, toggle } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import userPreferencesStateInjectable from "./state.injectable";
export type ToggleTableColumnVisibility = (tableId: string, columnId: string) => void;
const toggleTableColumnVisibilityInjectable = getInjectable({
id: "toggle-table-column-visibility",
instantiate: (di): ToggleTableColumnVisibility => {
const state = di.inject(userPreferencesStateInjectable);
return action((tableId, columnId) => {
toggle(getOrInsertSet(state.hiddenTableColumns, tableId), columnId);
});
},
});
export default toggleTableColumnVisibilityInjectable;
@@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Switch representation of hiddenTableColumns in store
import { getInjectable } from "@ogre-tools/injectable";
import { userPreferencesMigrationInjectionToken } from "../common/migrations-token";
interface PreV500Alpha3UserPreferencesModel {
hiddenTableColumns?: Record<string, string[]>;
}
const v500Alpha3UserPreferencesStorageMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.3-preferences-storage-migration",
instantiate: () => ({
version: "5.0.0-alpha.3",
run(store) {
const preferences = (store.get("preferences") ?? {}) as PreV500Alpha3UserPreferencesModel;
const oldHiddenTableColumns = preferences.hiddenTableColumns;
if (!oldHiddenTableColumns) {
return;
}
store.set("preferences", {
...preferences,
hiddenTableColumns: Object.entries(oldHiddenTableColumns),
});
},
}),
injectionToken: userPreferencesMigrationInjectionToken,
});
export default v500Alpha3UserPreferencesStorageMigrationInjectable;
@@ -0,0 +1,91 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isErrnoException } from "@k8slens/utilities";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import isLogicalChildPathInjectable from "../../../common/path/is-logical-child-path.injectable";
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { userPreferencesMigrationInjectionToken } from "../../../features/user-preferences/common/migrations-token";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
import type { ClusterStoreModel } from "../../../features/cluster/storage/common/storage.injectable";
import type { UserPreferencesModel, KubeconfigSyncEntry } from "../common/preferences-helpers";
const v503Beta1UserPreferencesStorageMigrationInjectable = getInjectable({
id: "v5.0.3-beta.1-preferences-storage-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const logger = di.inject(loggerInjectable);
const isLogicalChildPath = di.inject(isLogicalChildPathInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
const pathExistsSync = di.inject(pathExistsSyncInjectable);
return {
version: "5.0.3-beta.1",
run(store) {
try {
const { syncKubeconfigEntries = [], ...preferences } = (store.get("preferences") ?? {}) as UserPreferencesModel;
const { clusters = [] }: ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"), "utf-8") ?? {};
const extensionDataDir = joinPaths(userDataPath, "extension_data");
const syncPaths = new Set(syncKubeconfigEntries.map(s => s.filePath));
syncPaths.add(joinPaths(homeDirectoryPath, ".kube"));
for (const cluster of clusters) {
if (!cluster.kubeConfigPath) {
continue;
}
const dirOfKubeconfig = getDirnameOfPath(cluster.kubeConfigPath);
if (dirOfKubeconfig === kubeConfigsPath) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is under the stored KubeConfig folder`);
continue;
}
if (syncPaths.has(cluster.kubeConfigPath) || syncPaths.has(dirOfKubeconfig)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is already being synced`);
continue;
}
if (isLogicalChildPath(extensionDataDir, cluster.kubeConfigPath)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is placed under an extension_data folder`);
continue;
}
if (!pathExistsSync(cluster.kubeConfigPath)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath no longer exists`);
continue;
}
logger.info(`Adding ${cluster.kubeConfigPath} from ${cluster.id} to sync paths`);
syncPaths.add(cluster.kubeConfigPath);
}
const updatedSyncEntries: KubeconfigSyncEntry[] = [...syncPaths].map(filePath => ({ filePath }));
logger.info("Final list of synced paths", updatedSyncEntries);
store.set("preferences", { ...preferences, syncKubeconfigEntries: updatedSyncEntries });
} catch (error) {
if (isErrnoException(error) && error.code !== "ENOENT") {
// ignore files being missing
throw error;
}
}
},
};
},
injectionToken: userPreferencesMigrationInjectionToken,
});
export default v503Beta1UserPreferencesStorageMigrationInjectable;
@@ -0,0 +1,41 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { isErrnoException } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import fsInjectable from "../../../common/fs/fs.injectable";
export type UserStoreFileNameMigration = () => Promise<void>;
const userPreferencesStorageFileNameMigrationInjectable = getInjectable({
id: "preferences-storage-file-name-migration",
instantiate: (di): UserStoreFileNameMigration => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const configJsonPath = joinPaths(userDataPath, "config.json");
const lensUserStoreJsonPath = joinPaths(userDataPath, "lens-user-store.json");
const { rename, rm } = di.inject(fsInjectable);
return async () => {
try {
await rename(configJsonPath, lensUserStoreJsonPath);
} catch (error) {
if (error instanceof Error && error.message === "dest already exists.") {
await rm(configJsonPath);
} else if (isErrnoException(error) && error.code === "ENOENT" && error.path === configJsonPath) {
// (No such file or directory)
return; // file already moved
} else {
// pass other errors along
throw error;
}
}
};
},
});
export default userPreferencesStorageFileNameMigrationInjectable;
@@ -0,0 +1,26 @@
/**
* 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 { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import initDefaultUpdateChannelInjectable from "../../../main/vars/default-update-channel/init.injectable";
import userPreferencesPersistentStorageInjectable from "../common/storage.injectable";
import userPreferencesStorageFileNameMigrationInjectable from "./file-name-migration.injectable";
const loadUserPreferencesStorageInjectable = getInjectable({
id: "load-user-preferences-storage",
instantiate: (di) => ({
run: async () => {
const storage = di.inject(userPreferencesPersistentStorageInjectable);
const userStoreFileNameMigration = di.inject(userPreferencesStorageFileNameMigrationInjectable);
await userStoreFileNameMigration();
storage.loadAndStartSyncing();
},
runAfter: initDefaultUpdateChannelInjectable,
}),
injectionToken: beforeApplicationIsLoadingInjectionToken,
});
export default loadUserPreferencesStorageInjectable;
@@ -0,0 +1,32 @@
/**
* 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 { reaction } from "mobx";
import setLoginItemSettingsInjectable from "../../../main/electron-app/features/set-login-item-settings.injectable";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import userPreferencesStateInjectable from "../common/state.injectable";
const setupSyncOpenAtLoginWithOsInjectable = getInjectable({
id: "setup-sync-open-at-login-with-os",
instantiate: (di) => ({
run: () => {
const setLoginItemSettings = di.inject(setLoginItemSettingsInjectable);
const state = di.inject(userPreferencesStateInjectable);
reaction(() => state.openAtLogin, openAtLogin => {
setLoginItemSettings({
openAtLogin,
openAsHidden: true,
args: ["--hidden"],
});
}, {
fireImmediately: true,
});
},
}),
injectionToken: onLoadOfApplicationInjectionToken,
});
export default setupSyncOpenAtLoginWithOsInjectable;
@@ -0,0 +1,23 @@
/**
* 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 { beforeFrameStartsSecondInjectionToken } from "../../../renderer/before-frame-starts/tokens";
import initDefaultUpdateChannelInjectable from "../../../renderer/vars/default-update-channel/init.injectable";
import userPreferencesPersistentStorageInjectable from "../common/storage.injectable";
const loadUserPreferencesStorageInjectable = getInjectable({
id: "load-user-preferences-storage",
instantiate: (di) => ({
run: () => {
const storage = di.inject(userPreferencesPersistentStorageInjectable);
return storage.loadAndStartSyncing();
},
runAfter: initDefaultUpdateChannelInjectable,
}),
injectionToken: beforeFrameStartsSecondInjectionToken,
});
export default loadUserPreferencesStorageInjectable;