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,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 { computed } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
const activeHotbarIndexInjectable = getInjectable({
id: "active-hotbar-index",
instantiate: (di) => {
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return computed(() => {
const activeId = activeHotbarId.get();
return (activeId && computeHotbarIndex(activeId)) || 0;
});
},
});
export default activeHotbarIndexInjectable;
@@ -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 { observable } from "mobx";
const activeHotbarIdInjectable = getInjectable({
id: "active-hotbar-id",
instantiate: () => observable.box<string>(),
});
export default activeHotbarIdInjectable;
@@ -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 { computed } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import hotbarsStateInjectable from "./state.injectable";
const activeHotbarInjectable = getInjectable({
id: "active-hotbar",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return computed(() => {
const id = activeHotbarId.get();
return (id && state.get(id)) || undefined;
});
},
});
export default activeHotbarInjectable;
@@ -0,0 +1,33 @@
/**
* 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 type { CreateHotbarData, CreateHotbarOptions } from "./types";
import activeHotbarIdInjectable from "./active-id.injectable";
import hotbarsStateInjectable from "./state.injectable";
import createHotbarInjectable from "./create-hotbar.injectable";
export type AddHotbar = (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void;
const addHotbarInjectable = getInjectable({
id: "add-hotbar",
instantiate: (di): AddHotbar => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
const createHotbar = di.inject(createHotbarInjectable);
return action((data, { setActive = false } = {}) => {
const hotbar = createHotbar(data);
state.set(hotbar.id, hotbar);
if (setActive) {
activeHotbarId.set(hotbar.id);
}
});
},
});
export default addHotbarInjectable;
@@ -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 computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
export type ComputeDisplayIndex = (hotbarId: string) => string;
const computeDisplayIndexInjectable = getInjectable({
id: "compute-display-index",
instantiate: (di): ComputeDisplayIndex => {
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
return (hotbarId) => `${computeHotbarIndex(hotbarId) + 1}`;
},
});
export default computeDisplayIndexInjectable;
@@ -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 type { Hotbar } from "./hotbar";
import computeDisplayIndexInjectable from "./compute-display-index.injectable";
export type ComputeHotbarDisplayLabel = (hotbar: Hotbar) => string;
const computeHotbarDisplayLabelInjectable = getInjectable({
id: "compute-hotbar-display-label",
instantiate: (di): ComputeHotbarDisplayLabel => {
const computeDisplayIndex = di.inject(computeDisplayIndexInjectable);
return (hotbar) => `${computeDisplayIndex(hotbar.id)}: ${hotbar.name}`;
},
});
export default computeHotbarDisplayLabelInjectable;
@@ -0,0 +1,31 @@
/**
* 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 hotbarsStateInjectable from "./state.injectable";
export type ComputeHotbarIndex = (hotbarId: string) => number;
const computeHotbarIndexInjectable = getInjectable({
id: "compute-hotbar-index",
instantiate: (di): ComputeHotbarIndex => {
const state = di.inject(hotbarsStateInjectable);
return (hotbarId) => {
let i = 0;
for (const hotbar of state.values()) {
if (hotbar.id === hotbarId) {
return i;
}
i += 1;
}
return 0;
};
},
});
export default computeHotbarIndexInjectable;
@@ -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 type { CreateHotbarData } from "./types";
import prefixedLoggerInjectable from "../../../../common/logger/prefixed-logger.injectable";
import type { HotbarDependencies } from "./hotbar";
import { Hotbar } from "./hotbar";
export type CreateHotbar = (data: CreateHotbarData) => Hotbar;
const createHotbarInjectable = getInjectable({
id: "create-hotbar",
instantiate: (di): CreateHotbar => {
const deps: HotbarDependencies = {
logger: di.inject(prefixedLoggerInjectable, "HOTBAR"),
};
return (data) => new Hotbar(deps, data);
},
});
export default createHotbarInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type FindHotbarByName = (name: string) => Hotbar | undefined;
const findHotbarByNameInjectable = getInjectable({
id: "find-hotbar-by-name",
instantiate: (di): FindHotbarByName => {
const state = di.inject(hotbarsStateInjectable);
return (name) => iter.find(state.values(), hotbar => hotbar.name.get() === name);
},
});
export default findHotbarByNameInjectable;
@@ -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 type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type GetHotbarById = (id: string) => Hotbar | undefined;
const getHotbarByIdInjectable = getInjectable({
id: "get-hotbar-by-id",
instantiate: (di): GetHotbarById => {
const state = di.inject(hotbarsStateInjectable);
return (id) => state.get(id);
},
});
export default getHotbarByIdInjectable;
@@ -0,0 +1,175 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { type IObservableValue, type IObservableArray, observable, runInAction, toJS } from "mobx";
import type { CatalogEntity } from "../../../../common/catalog";
import { getShortName } from "../../../../common/catalog/helpers";
import type { HotbarItem, CreateHotbarData } from "./types";
import { defaultHotbarCells } from "./types";
import { broadcastMessage } from "../../../../common/ipc";
import { hotbarTooManyItemsChannel } from "../../../../common/ipc/hotbar";
import * as uuid from "uuid";
import type { Logger } from "../../../../common/logger";
import { tuple } from "@k8slens/utilities";
export interface HotbarDependencies {
readonly logger: Logger;
}
export interface HotbarData {
readonly id: string;
readonly name: string;
readonly items: (HotbarItem | null)[];
}
export class Hotbar {
readonly id: string;
readonly name: IObservableValue<string>;
readonly items: IObservableArray<HotbarItem | null>;
constructor(private readonly dependencies: HotbarDependencies, data: CreateHotbarData) {
this.id = data.id ?? uuid.v4();
this.name = observable.box(data.name);
this.items = observable.array(data.items ?? tuple.filled(defaultHotbarCells, null));
}
isFull() {
for (const item of this.items) {
if (!item) {
return false;
}
}
return true;
}
hasEntity(entityId: string) {
return this.items.findIndex(item => item?.entity.uid === entityId) >= 0;
}
private findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
while (this.items[index] != null) {
index += direction;
}
return index;
}
restack(from: number, to: number) {
runInAction(() => {
const source = this.items[from];
const moveDown = from < to;
if (
from < 0 ||
to < 0 ||
from >= this.items.length ||
to >= this.items.length ||
isNaN(from) ||
isNaN(to)
) {
throw new Error("Invalid 'from' or 'to' arguments");
}
if (from == to) {
return;
}
this.items.splice(from, 1, null);
if (this.items[to] == null) {
this.items.splice(to, 1, source);
} else {
// Move cells up or down to closes empty cell
this.items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
this.items.splice(to, 0, source);
}
});
}
toggleEntity(item: CatalogEntity) {
runInAction(() => {
if (this.hasEntity(item.getId())) {
this.removeEntity(item.getId());
} else {
this.addEntity(item);
}
});
}
removeEntity(uid: string) {
runInAction(() => {
const index = this.items.findIndex((item) => item?.entity.uid === uid);
if (index < 0) {
return;
}
this.items[index] = null;
});
}
addEntity(item: CatalogEntity, cellIndex?: number) {
const uid = item.getId();
const name = item.getName();
const shortName = getShortName(item);
if (typeof uid !== "string") {
throw new TypeError("CatalogEntity's ID must be a string");
}
if (typeof name !== "string") {
throw new TypeError("CatalogEntity's NAME must be a string");
}
if (typeof shortName !== "string") {
throw new TypeError("CatalogEntity's SHORT_NAME must be a string");
}
if (this.hasEntity(item.getId())) {
return;
}
const entity = {
uid,
name,
source: item.metadata.source,
shortName,
};
const newItem = { entity };
if (cellIndex === undefined) {
// Add item to empty cell
const emptyCellIndex = this.items.indexOf(null);
if (emptyCellIndex >= 0) {
runInAction(() => {
this.items[emptyCellIndex] = newItem;
});
} else {
broadcastMessage(hotbarTooManyItemsChannel);
}
} else if (0 <= cellIndex && cellIndex < this.items.length) {
runInAction(() => {
this.items[cellIndex] = newItem;
});
} else {
this.dependencies.logger.error(
"cannot pin entity to hotbar outside of index range",
{ entityId: uid, hotbarId: this.id, cellIndex },
);
}
}
toJSON(): HotbarData {
return {
id: this.id,
items: toJS(this.items),
name: this.name.get(),
};
}
}
@@ -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 hotbarsStateInjectable from "./state.injectable";
const hotbarsInjectable = getInjectable({
id: "hotbars",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
return computed(() => [...state.values()]);
},
});
export default hotbarsInjectable;
@@ -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 hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
id: "hotbar-store-migration-token",
});
@@ -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 hotbarsInjectable from "./hotbars.injectable";
export type RemoveEntityFromAllHotbars = (entityId: string) => void;
const removeEntityFromAllHotbarsInjectable = getInjectable({
id: "remove-entity-from-all-hotbars",
instantiate: (di): RemoveEntityFromAllHotbars => {
const hotbars = di.inject(hotbarsInjectable);
return action((entityId) => {
for (const hotbar of hotbars.get()) {
hotbar.removeEntity(entityId);
}
});
},
});
export default removeEntityFromAllHotbarsInjectable;
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { action } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type RemoveHotbar = (hotbar: Hotbar) => void;
const removeHotbarInjectable = getInjectable({
id: "remove-hotbar",
instantiate: (di): RemoveHotbar => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return action((hotbar) => {
assert(state.size >= 2, "Cannot remove the last hotbar");
state.delete(hotbar.id);
if (activeHotbarId.get() === hotbar.id) {
activeHotbarId.set(iter.first(state.values())?.id);
}
});
},
});
export default removeHotbarInjectable;
@@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type SetAsActiveHotbar = (desc: Hotbar | number | string) => void;
const setAsActiveHotbarInjectable = getInjectable({
id: "set-as-active-hotbar",
instantiate: (di): SetAsActiveHotbar => {
const hotbarsState = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return action((desc) => {
if (typeof desc === "number") {
const hotbar = iter.nth(hotbarsState.values(), desc);
if (hotbar) {
activeHotbarId.set(hotbar.id);
}
} else if (typeof desc === "string") {
if (hotbarsState.has(desc)) {
activeHotbarId.set(desc);
}
} else {
if (hotbarsState.has(desc.id)) {
activeHotbarId.set(desc.id);
}
}
});
},
});
export default setAsActiveHotbarInjectable;
@@ -0,0 +1,14 @@
/**
* 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 { Hotbar } from "./hotbar";
const hotbarsStateInjectable = getInjectable({
id: "hotbars-state",
instantiate: () => observable.map<string, Hotbar>(),
});
export default hotbarsStateInjectable;
@@ -0,0 +1,116 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { action, comparer } from "mobx";
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
import { defaultHotbarCells } from "./types";
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable";
import activeHotbarIdInjectable from "./active-id.injectable";
import createHotbarInjectable from "./create-hotbar.injectable";
import type { Hotbar, HotbarData } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export interface HotbarStoreModel {
hotbars: HotbarData[];
activeHotbarId: string | undefined;
}
const hotbarsPersistentStorageInjectable = getInjectable({
id: "hotbars-persistent-storage",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
const createHotbar = di.inject(createHotbarInjectable);
return createPersistentStorage<HotbarStoreModel>({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
projectVersion: di.inject(storeMigrationVersionInjectable),
migrations: di.inject(persistentStorageMigrationsInjectable, hotbarStoreMigrationInjectionToken),
fromStore: action((data) => {
if (!data.hotbars || !data.hotbars.length) {
const hotbar = createHotbar({
name: "Default",
});
const {
metadata: {
uid,
name,
source,
},
} = catalogCatalogEntity;
hotbar.items[0] = {
entity: {
uid,
name,
source,
},
};
state.replace([[hotbar.id, hotbar]]);
} else {
state.replace(data.hotbars.map((hotbar) => [hotbar.id, createHotbar(hotbar)]));
}
for (const hotbar of state.values()) {
ensureExactHotbarItemLength(hotbar);
}
if (data.activeHotbarId) {
activeHotbarId.set(data.activeHotbarId);
}
const firstHotbarId = iter.first(state.values())?.id;
if (!activeHotbarId.get()) {
activeHotbarId.set(firstHotbarId);
} else if (!iter.find(state.values(), hotbar => hotbar.id === activeHotbarId.get())) {
activeHotbarId.set(firstHotbarId);
}
}),
toJSON: () => ({
hotbars: iter.chain(state.values())
.map(hotbar => hotbar.toJSON())
.toArray(),
activeHotbarId: activeHotbarId.get(),
}),
});
},
});
export default hotbarsPersistentStorageInjectable;
/**
* This function ensures that there are always exactly `defaultHotbarCells`
* worth of items in the hotbar.
* @param hotbar The hotbar to modify
*/
function ensureExactHotbarItemLength(hotbar: Hotbar) {
// if there are not enough items
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
// if for some reason the hotbar was overfilled before, remove as many entries
// as needed, but prefer empty slots and items at the end first.
while (hotbar.items.length > defaultHotbarCells) {
const lastNull = hotbar.items.lastIndexOf(null);
if (lastNull >= 0) {
hotbar.items.splice(lastNull, 1);
} else {
hotbar.items.length = defaultHotbarCells;
}
}
}
@@ -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 { action } from "mobx";
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
import hotbarsStateInjectable from "./state.injectable";
export type SwitchToNextHotbar = () => void;
const switchToNextHotbarInjectable = getInjectable({
id: "switch-to-next-hotbar",
instantiate: (di): SwitchToNextHotbar => {
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
const state = di.inject(hotbarsStateInjectable);
return action(() => {
const index = activeHotbarIndex.get() + 1;
if (index >= state.size) {
setAsActiveHotbar(0);
} else {
setAsActiveHotbar(index);
}
});
},
});
export default switchToNextHotbarInjectable;
@@ -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 { action } from "mobx";
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
import hotbarsStateInjectable from "./state.injectable";
export type SwitchToPreviousHotbar = () => void;
const switchToPreviousHotbarInjectable = getInjectable({
id: "switch-to-previous-hotbar",
instantiate: (di): SwitchToPreviousHotbar => {
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
const state = di.inject(hotbarsStateInjectable);
return action(() => {
const index = activeHotbarIndex.get() - 1;
if (index < 0) {
setAsActiveHotbar(state.size - 1);
} else {
setAsActiveHotbar(index);
}
});
},
});
export default switchToPreviousHotbarInjectable;
@@ -0,0 +1,25 @@
/**
* 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 activeHotbarInjectable from "./active.injectable";
import type { Hotbar } from "./hotbar";
export type ActiveHotbarModel = Pick<Hotbar, "hasEntity" | "addEntity" | "removeEntity" | "toggleEntity">;
const activeHotbarModelInjectable = getInjectable({
id: "active-hotbar-model",
instantiate: (di): ActiveHotbarModel => {
const activeHotbar = di.inject(activeHotbarInjectable);
return {
hasEntity: (entityId) => activeHotbar.get()?.hasEntity(entityId) ?? false,
toggleEntity: (entity) => activeHotbar.get()?.toggleEntity(entity),
addEntity: (entity) => activeHotbar.get()?.addEntity(entity),
removeEntity: (entityId) => activeHotbar.get()?.removeEntity(entityId),
};
},
});
export default activeHotbarModelInjectable;
@@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface HotbarItem {
entity: {
uid: string;
name: string;
source?: string;
};
params?: {
[key: string]: string;
};
}
export interface CreateHotbarData {
id?: string;
name: string;
items?: (HotbarItem | null)[];
}
export interface CreateHotbarOptions {
setActive?: boolean;
}
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import createHotbarInjectable from "../common/create-hotbar.injectable";
const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.0-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-alpha.0",
run(store) {
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const createHotbar = di.inject(createHotbarInjectable);
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
store.set("hotbars", [hotbar.toJSON()]);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha0HotbarStoreMigrationInjectable;
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import * as uuid from "uuid";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import type { HotbarData } from "../common/hotbar";
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.2-hotbar-store-migration",
instantiate: () => ({
version: "5.0.0-alpha.2",
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
id: id || uuid.v4(),
...rest,
})));
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha2HotbarStoreMigrationInjectable;
@@ -0,0 +1,168 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import * as uuid from "uuid";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { isDefined, isErrnoException } from "@k8slens/utilities";
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import readJsonSyncInjectable from "../../../../common/fs/read-json-sync.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { generateNewIdFor } from "../../../../common/utils/generate-new-id-for";
import type { ClusterModel } from "../../../../common/cluster-types";
import { defaultHotbarCells } from "../common/types";
import type { HotbarData } from "../common/hotbar";
import createHotbarInjectable from "../common/create-hotbar.injectable";
interface Pre500WorkspaceStoreModel {
workspaces: {
id: string;
name: string;
}[];
}
interface Pre500ClusterModel extends ClusterModel {
workspace?: string;
workspaces?: string[];
}
interface Pre500ClusterStoreModel {
clusters?: Pre500ClusterModel[];
}
const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-beta.10-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-beta.10",
run(store) {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const logger = di.inject(loggerInjectable);
const createHotbar = di.inject(createHotbarInjectable);
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : [];
// Hotbars might be empty, if some of the previous migrations weren't run
if (hotbars.length === 0) {
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.push(hotbar.toJSON());
}
try {
const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"));
const workspaceHotbars = new Map<string, HotbarData>(); // mapping from WorkspaceId to HotBar
for (const { id, name } of workspaceStoreData.workspaces) {
logger.info(`Creating new hotbar for ${name}`);
workspaceHotbars.set(id, {
id: uuid.v4(),
items: [],
name: `Workspace: ${name}`,
});
}
{
// grab the default named hotbar or the first.
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
workspaceHotbars.set("default", {
name,
id,
items: items.filter(isDefined),
});
}
for (const cluster of clusters) {
const uid = generateNewIdFor(cluster);
for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) {
const workspaceHotbar = workspaceHotbars.get(workspaceId);
if (!workspaceHotbar) {
logger.info(`Cluster ${uid} has unknown workspace ID, skipping`);
continue;
}
logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`);
if (workspaceHotbar?.items.length < defaultHotbarCells) {
workspaceHotbar.items.push({
entity: {
uid: generateNewIdFor(cluster),
name: cluster.preferences?.clusterName || cluster.contextName,
},
});
}
}
}
for (const hotbar of workspaceHotbars.values()) {
if (hotbar.items.length === 0) {
logger.info(`Skipping ${hotbar.name} due to it being empty`);
continue;
}
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
hotbars.push(hotbar);
}
/**
* Finally, make sure that the catalog entity hotbar item is in place.
* Just in case something else removed it.
*
* if every hotbar has elements that all not the `catalog-entity` item
*/
if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) {
// note, we will add a new whole hotbar here called "default" if that was previously removed
const defaultHotbarIndex = hotbars.findIndex(hotbar => hotbar.name === "default");
if (defaultHotbarIndex >= 0) {
const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]);
if (defaultHotbar.isFull()) {
// making a new hotbar is less destructive if the first hotbar
// called "default" is full than overriding a hotbar item
const hotbar = createHotbar({ name: "initial" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.unshift(hotbar.toJSON());
} else {
defaultHotbar.addEntity(catalogCatalogEntity);
hotbars[defaultHotbarIndex] = defaultHotbar.toJSON();
}
} else {
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.unshift(hotbar.toJSON());
}
}
} catch (error) {
// ignore files being missing
if (isErrnoException(error) && error.code !== "ENOENT") {
throw error;
}
}
store.set("hotbars", hotbars);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta10HotbarStoreMigrationInjectable;
@@ -0,0 +1,51 @@
/**
* 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 catalogEntityRegistryInjectable from "../../../../main/catalog/entity-registry.injectable";
import type { HotbarData } from "../common/hotbar";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
const v500Beta5HotbarStoreMigrationInjectable = getInjectable({
id: "v500-beta5-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-beta.5",
run(store) {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
for (const hotbar of hotbars) {
for (let i = 0; i < hotbar.items.length; i += 1) {
const item = hotbar.items[i];
if (!item) {
continue;
}
const entity = catalogEntityRegistry.findById(item.entity.uid);
if (!entity) {
// Clear disabled item
hotbar.items[i] = null;
} else {
// Save additional data
item.entity = {
...item.entity,
name: entity.metadata.name,
source: entity.metadata.source,
};
}
}
}
store.set("hotbars", hotbars);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta5HotbarStoreMigrationInjectable;
@@ -3,21 +3,21 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
const initHotbarStoreInjectable = getInjectable({
id: "init-hotbar-store",
const loadHotbarStorageInjectable = getInjectable({
id: "load-hotbar-storage",
instantiate: (di) => ({
run: () => {
const hotbarStore = di.inject(hotbarStoreInjectable);
const storage = di.inject(hotbarsPersistentStorageInjectable);
hotbarStore.load();
storage.loadAndStartSyncing();
},
runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable,
}),
injectionToken: onLoadOfApplicationInjectionToken,
});
export default initHotbarStoreInjectable;
export default loadHotbarStorageInjectable;
@@ -3,21 +3,21 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable";
import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable";
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
const initHotbarStoreInjectable = getInjectable({
id: "init-hotbar-store",
const loadHotbarStorageInjectable = getInjectable({
id: "load-hotbar-storage",
instantiate: (di) => ({
run: () => {
const hotbarStore = di.inject(hotbarStoreInjectable);
const storage = di.inject(hotbarsPersistentStorageInjectable);
hotbarStore.load();
storage.loadAndStartSyncing();
},
runAfter: initClusterStoreInjectable,
}),
injectionToken: beforeFrameStartsSecondInjectionToken,
});
export default initHotbarStoreInjectable;
export default loadHotbarStorageInjectable;
@@ -0,0 +1,373 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { anyObject } from "jest-mock-extended";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../../../common/catalog";
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable";
import catalogEntityRegistryInjectable from "../../../main/catalog/entity-registry.injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import hasCategoryForEntityInjectable from "../../../common/catalog/has-category-for-entity.injectable";
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import type { Logger } from "../../../common/logger";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
import type { SetAsActiveHotbar } from "./common/set-as-active.injectable";
import setAsActiveHotbarInjectable from "./common/set-as-active.injectable";
import hotbarsPersistentStorageInjectable from "./common/storage.injectable";
import type { Hotbar } from "./common/hotbar";
import hotbarsInjectable from "./common/hotbars.injectable";
import activeHotbarInjectable from "./common/active.injectable";
import type { AddHotbar } from "./common/add.injectable";
import type { GetHotbarById } from "./common/get-by-id.injectable";
import getHotbarByIdInjectable from "./common/get-by-id.injectable";
import addHotbarInjectable from "./common/add.injectable";
import { defaultHotbarCells } from "./common/types";
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
getName: jest.fn(() => data.metadata?.name),
getId: jest.fn(() => data.metadata?.uid),
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
isEnabled: jest.fn(() => data.status?.enabled ?? true),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {},
spec: {},
status: {},
...data,
} as CatalogEntity;
}
describe("Hotbars technical tests", () => {
let di: DiContainer;
let testCluster: CatalogEntity;
let minikubeCluster: CatalogEntity;
let awsCluster: CatalogEntity;
let loggerMock: jest.Mocked<Logger>;
let setAsActiveHotbar: SetAsActiveHotbar;
let hotbars: IComputedValue<Hotbar[]>;
let activeHotbar: IComputedValue<Hotbar | undefined>;
let addHotbar: AddHotbar;
let getHotbarById: GetHotbarById;
beforeEach(async () => {
di = getDiForUnitTesting();
testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-test-id",
name: "my-test-cluster",
source: "local",
labels: {},
},
});
minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-minikube-id",
name: "my-minikube-cluster",
source: "local",
labels: {},
},
});
awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-aws-id",
name: "my-aws-cluster",
source: "local",
labels: {},
},
});
di.override(hasCategoryForEntityInjectable, () => () => true);
loggerMock = {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
silly: jest.fn(),
};
di.override(loggerInjectable, () => loggerMock);
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
testCluster,
minikubeCluster,
awsCluster,
catalogCatalogEntity,
]));
setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
hotbars = di.inject(hotbarsInjectable);
activeHotbar = di.inject(activeHotbarInjectable);
addHotbar = di.inject(addHotbarInjectable);
getHotbarById = di.inject(getHotbarByIdInjectable);
});
describe("given no previous data in store, running all migrations", () => {
beforeEach(() => {
di.override(storeMigrationVersionInjectable, () => "9999.0.0");
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
});
describe("load", () => {
it("loads one hotbar by default", () => {
expect(hotbars.get().length).toEqual(1);
});
});
describe("add", () => {
it("adds a hotbar", () => {
addHotbar({ name: "hottest" });
expect(hotbars.get().length).toEqual(2);
});
});
describe("hotbar items", () => {
it("initially creates default number of empty cells", () => {
expect(activeHotbar.get()?.items?.length).toEqual(defaultHotbarCells);
});
it("initially adds catalog entity as first item", () => {
expect(activeHotbar.get()?.items[0]?.entity.name).toEqual("Catalog");
});
it("adds items", () => {
activeHotbar.get()?.addEntity(testCluster);
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items?.length).toEqual(2);
});
it("removes items", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.removeEntity("some-test-id");
activeHotbar.get()?.removeEntity("catalog-entity");
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items).toStrictEqual([]);
});
it("does nothing if removing with invalid uid", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.removeEntity("invalid uid");
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items?.length).toEqual(2);
});
it("moves item to empty cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
expect(activeHotbar.get()?.items[6]).toBeNull();
activeHotbar.get()?.restack(1, 5);
expect(activeHotbar.get()?.items[5]).toBeTruthy();
expect(activeHotbar.get()?.items[5]?.entity.uid).toEqual("some-test-id");
});
it("moves items down", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
// aws -> catalog
activeHotbar.get()?.restack(3, 0);
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
expect(items?.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
});
it("moves items up", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
// test -> aws
activeHotbar.get()?.restack(1, 3);
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
expect(items?.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
});
it("logs an error if cellIndex is out of bounds", () => {
addHotbar({ name: "hottest", id: "hottest" });
setAsActiveHotbar("hottest");
activeHotbar.get()?.addEntity(testCluster, -1);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
activeHotbar.get()?.addEntity(testCluster, 12);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
activeHotbar.get()?.addEntity(testCluster, 13);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
});
it("throws an error if getId is invalid or returns not a string", () => {
expect(() => activeHotbar.get()?.addEntity({} as any)).toThrowError(TypeError);
expect(() => activeHotbar.get()?.addEntity({ getId: () => true } as any)).toThrowError(TypeError);
});
it("throws an error if getName is invalid or returns not a string", () => {
expect(() => activeHotbar.get()?.addEntity({ getId: () => "" } as any)).toThrowError(TypeError);
expect(() => activeHotbar.get()?.addEntity({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
});
it("does nothing when item moved to same cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.restack(1, 1);
expect(activeHotbar.get()?.items[1]?.entity.uid).toEqual("some-test-id");
});
it("new items takes first empty cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(awsCluster);
activeHotbar.get()?.restack(0, 3);
activeHotbar.get()?.addEntity(minikubeCluster);
expect(activeHotbar.get()?.items[0]?.entity.uid).toEqual("some-minikube-id");
});
it("throws if invalid arguments provided", () => {
activeHotbar.get()?.addEntity(testCluster);
expect(() => activeHotbar.get()?.restack(-5, 0)).toThrow();
expect(() => activeHotbar.get()?.restack(2, -1)).toThrow();
expect(() => activeHotbar.get()?.restack(14, 1)).toThrow();
expect(() => activeHotbar.get()?.restack(11, 112)).toThrow();
});
it("checks if entity already pinned to hotbar", () => {
activeHotbar.get()?.addEntity(testCluster);
expect(activeHotbar.get()?.hasEntity(testCluster.getId())).toBeTruthy();
expect(activeHotbar.get()?.hasEntity(awsCluster.getId())).toBeFalsy();
});
});
});
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(() => {
const writeJsonSync = di.inject(writeJsonSyncInjectable);
writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", {
__internal__: {
migrations: {
version: "5.0.0-beta.3",
},
},
hotbars: [
{
id: "3caac17f-aec2-4723-9694-ad204465d935",
name: "myhotbar",
items: [
{
entity: {
uid: "some-aws-id",
},
},
{
entity: {
uid: "55b42c3c7ba3b04193416cda405269a5",
},
},
{
entity: {
uid: "176fd331968660832f62283219d7eb6e",
},
},
{
entity: {
uid: "61c4fb45528840ebad1badc25da41d14",
name: "user1-context",
source: "local",
},
},
{
entity: {
uid: "27d6f99fe9e7548a6e306760bfe19969",
name: "foo2",
source: "local",
},
},
null,
{
entity: {
uid: "c0b20040646849bb4dcf773e43a0bf27",
name: "multinode-demo",
source: "local",
},
},
null,
null,
null,
null,
null,
],
},
],
});
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
});
it("allows to retrieve a hotbar", () => {
const hotbar = getHotbarById("3caac17f-aec2-4723-9694-ad204465d935");
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
});
it("clears cells without entity", () => {
const items = hotbars.get()[0].items;
expect(items[2]).toBeNull();
});
it("adds extra data to cells with according entity", () => {
const items = hotbars.get()[0].items;
expect(items[0]).toEqual({
entity: {
name: "my-aws-cluster",
source: "local",
uid: "some-aws-id",
},
});
});
});
});