diff --git a/open-lens/package.json b/open-lens/package.json index 838c37f89790..669c0883c2e1 100644 --- a/open-lens/package.json +++ b/open-lens/package.json @@ -189,6 +189,7 @@ "@k8slens/ensure-binaries": "^6.5.0", "@k8slens/error-boundary": "^1.0.0", "@k8slens/event-emitter": "^1.0.0", + "@k8slens/item-store": "^1.0.0", "@k8slens/feature-core": "^6.5.0", "@k8slens/json-api": "^1.0.0-alpha.3", "@k8slens/keyboard-shortcuts": "^1.0.0", diff --git a/package-lock.json b/package-lock.json index eb92446d12ee..058a380d7842 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3759,6 +3759,10 @@ "resolved": "packages/ui-components/icon", "link": true }, + "node_modules/@k8slens/item-store": { + "resolved": "packages/utility-features/item-store", + "link": true + }, "node_modules/@k8slens/jest": { "resolved": "packages/infrastructure/jest", "link": true @@ -3783,6 +3787,10 @@ "resolved": "packages/kube-object", "link": true }, + "node_modules/@k8slens/kube-object-store": { + "resolved": "packages/utility-features/kube-object-store", + "link": true + }, "node_modules/@k8slens/kubectl-versions": { "resolved": "packages/kubectl-versions", "link": true @@ -33981,6 +33989,7 @@ "@k8slens/error-boundary": "^1.0.0", "@k8slens/event-emitter": "^1.0.0", "@k8slens/feature-core": "^6.5.0", + "@k8slens/item-store": "^1.0.0", "@k8slens/json-api": "^1.0.0-alpha.3", "@k8slens/keyboard-shortcuts": "^1.0.0", "@k8slens/kube-api": "^1.0.0-alpha.1", @@ -34327,6 +34336,7 @@ "@k8slens/error-boundary": "^1.0.0-alpha.5", "@k8slens/event-emitter": "^1.0.0-alpha.1", "@k8slens/icon": "^1.0.0-alpha.7", + "@k8slens/item-store": "^1.0.0", "@k8slens/kube-api": "^1.0.0-alpha.1", "@k8slens/kube-api-specifics": "^1.0.0-alpha.1", "@k8slens/kube-object": "^1.0.0-alpha.5", @@ -35784,6 +35794,21 @@ "@k8slens/webpack": "^6.5.0" } }, + "packages/utility-features/item-store": { + "name": "@k8slens/item-store", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2" + }, + "peerDependencies": { + "auto-bind": "^4.0.0", + "lodash": "^4.17.21", + "mobx": "^6.9.0" + } + }, "packages/utility-features/json-api": { "name": "@k8slens/json-api", "version": "1.0.0", @@ -35849,6 +35874,15 @@ "@ogre-tools/injectable-extension-for-auto-registration": "^17.2.0" } }, + "packages/utility-features/kube-object-store": { + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2" + } + }, "packages/utility-features/react-testing-library-discovery": { "name": "@k8slens/react-testing-library-discovery", "version": "1.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 47538264d4dd..0b698f47630a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -231,6 +231,7 @@ "@k8slens/prometheus": "^1.0.0", "@k8slens/react-application": "^1.0.0-alpha.5", "@k8slens/random": "^1.0.0", + "@k8slens/item-store": "^1.0.0", "@k8slens/resizing-anchor": "^1.0.0-alpha.5", "@k8slens/resource-templates": "^1.0.0-alpha.1", "@k8slens/routing": "^1.0.0-alpha.5", diff --git a/packages/core/src/common/k8s-api/kube-object.store.ts b/packages/core/src/common/k8s-api/kube-object.store.ts index 78cf4f7fe2fb..65379255a153 100644 --- a/packages/core/src/common/k8s-api/kube-object.store.ts +++ b/packages/core/src/common/k8s-api/kube-object.store.ts @@ -9,7 +9,7 @@ import { waitUntilDefined, includes, rejectPromiseBy, object } from "@k8slens/ut import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; import { KubeStatus } from "@k8slens/kube-object"; import type { IKubeWatchEvent, KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "@k8slens/kube-api"; -import { ItemStore } from "../item.store"; +import { ItemStore } from "@k8slens/item-store"; import { parseKubeApi } from "@k8slens/kube-api"; import type { RequestInit } from "@k8slens/node-fetch"; import type { Patch } from "rfc6902"; diff --git a/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts b/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts index 431c92dd1aed..7083a288247b 100644 --- a/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts +++ b/packages/core/src/renderer/port-forward/port-forward-store/port-forward-store.ts @@ -4,7 +4,7 @@ */ import { action, makeObservable, observable, reaction } from "mobx"; -import { ItemStore } from "../../../common/item.store"; +import { ItemStore } from "@k8slens/item-store"; import type { StorageLayer } from "../../utils/storage-helper"; import { disposer } from "@k8slens/utilities"; import type { ForwardedPort } from "../port-forward-item"; @@ -66,7 +66,7 @@ export class PortForwardStore extends ItemStore { } loadAll() { - return this.loadItems(() => { + return this.rawLoadItems(async () => { const portForwards = this.getPortForwards(); this.dependencies.storage.set(portForwards); diff --git a/packages/utility-features/item-store/.eslintrc.js b/packages/utility-features/item-store/.eslintrc.js new file mode 100644 index 000000000000..19a14e85a467 --- /dev/null +++ b/packages/utility-features/item-store/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: "@k8slens/eslint-config/eslint", + parserOptions: { + project: "./tsconfig.json", + }, +}; \ No newline at end of file diff --git a/packages/utility-features/item-store/.prettierrc b/packages/utility-features/item-store/.prettierrc new file mode 100644 index 000000000000..edd47b479e93 --- /dev/null +++ b/packages/utility-features/item-store/.prettierrc @@ -0,0 +1 @@ +"@k8slens/eslint-config/prettier" diff --git a/packages/utility-features/item-store/.swcrc b/packages/utility-features/item-store/.swcrc new file mode 100644 index 000000000000..aaf7dd113ad9 --- /dev/null +++ b/packages/utility-features/item-store/.swcrc @@ -0,0 +1,18 @@ +{ + "module": { + "type": "commonjs" + }, + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true, + "decorators": true, + "dynamicImport": false + }, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "target": "es2019" + } +} \ No newline at end of file diff --git a/packages/utility-features/item-store/index.ts b/packages/utility-features/item-store/index.ts new file mode 100644 index 000000000000..5056f4ec6f51 --- /dev/null +++ b/packages/utility-features/item-store/index.ts @@ -0,0 +1 @@ +export * from "./src/item.store"; diff --git a/packages/utility-features/item-store/jest.config.js b/packages/utility-features/item-store/jest.config.js new file mode 100644 index 000000000000..c6074967eb6f --- /dev/null +++ b/packages/utility-features/item-store/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; diff --git a/packages/utility-features/item-store/package.json b/packages/utility-features/item-store/package.json new file mode 100644 index 000000000000..d8ae03c9237d --- /dev/null +++ b/packages/utility-features/item-store/package.json @@ -0,0 +1,44 @@ +{ + "name": "@k8slens/item-store", + "private": false, + "version": "1.0.0", + "description": "Item Store", + "type": "commonjs", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/lensapp/lens.git" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": { + "name": "OpenLens Authors", + "email": "info@k8slens.dev" + }, + "license": "MIT", + "homepage": "https://github.com/lensapp/lens", + "scripts": { + "build": "lens-webpack-build", + "clean": "rimraf dist/", + "dev": "webpack --mode=development --watch", + "test": "jest --coverage --runInBand", + "lint": "lens-lint", + "lint:fix": "lens-lint --fix" + }, + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2" + }, + "peerDependencies": { + "auto-bind": "^4.0.0", + "lodash": "^4.17.21", + "mobx": "^6.9.0" + } +} diff --git a/packages/core/src/common/item.store.ts b/packages/utility-features/item-store/src/item.store.ts similarity index 76% rename from packages/core/src/common/item.store.ts rename to packages/utility-features/item-store/src/item.store.ts index cb9b27ed2e4d..5742b7ff61a6 100644 --- a/packages/core/src/common/item.store.ts +++ b/packages/utility-features/item-store/src/item.store.ts @@ -3,19 +3,26 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ItemObject } from "@k8slens/list-layout"; import autoBind from "auto-bind"; import orderBy from "lodash/orderBy"; import { action, computed, observable, when, makeObservable } from "mobx"; +export interface ItemObject { + getId: () => string; + getName: () => string; +} export abstract class ItemStore { protected defaultSorting = (item: Item) => item.getName(); @observable failedLoading = false; + @observable isLoading = false; + @observable isLoaded = false; + @observable items = observable.array([], { deep: false }); + @observable selectedItemsIds = observable.set(); constructor() { @@ -28,7 +35,7 @@ export abstract class ItemStore { } public pickOnlySelected(items: Item[]): Item[] { - return items.filter(item => this.selectedItemsIds.has(item.getId())); + return items.filter((item) => this.selectedItemsIds.has(item.getId())); } public getItems(): Item[] { @@ -40,11 +47,11 @@ export abstract class ItemStore { } getByName(name: string): Item | undefined { - return this.items.find(item => item.getName() === name); + return this.items.find((item) => item.getName() === name); } getIndexById(id: string): number { - return this.items.findIndex(item => item.getId() === id); + return this.items.findIndex((item) => item.getId() === id); } /** @@ -57,28 +64,14 @@ export abstract class ItemStore { * @param order whether to sort from least to greatest (`"asc"` (default)) or vice-versa (`"desc"`) */ @action - protected sortItems(items: Item[] = this.items, sorting: ((item: Item) => any)[] = [this.defaultSorting], order?: "asc" | "desc"): Item[] { + protected sortItems( + items: Item[] = this.items, + sorting: ((item: Item) => any)[] = [this.defaultSorting], + order?: "asc" | "desc", + ): Item[] { return orderBy(items, sorting, order); } - protected async createItem(...args: any[]): Promise; - @action - protected async createItem(request: () => Promise) { - const newItem = await request(); - const item = this.items.find(item => item.getId() === newItem.getId()); - - if (item) { - return item; - } else { - const items = this.sortItems([...this.items, newItem]); - - this.items.replace(items); - - return newItem; - } - } - - protected async loadItems(...args: any[]): Promise; /** * Load items to this.items * @param request Function to return the items to be loaded. @@ -87,7 +80,7 @@ export abstract class ItemStore { * @returns */ @action - protected async loadItems(request: () => Promise, sortItems = true, concurrency = false) { + protected async rawLoadItems(request: () => Promise, sortItems = true, concurrency = false) { if (this.isLoading) { await when(() => !this.isLoading); @@ -101,7 +94,9 @@ export abstract class ItemStore { try { let items = await request(); - if (sortItems) items = this.sortItems(items); + if (sortItems) { + items = this.sortItems(items); + } this.items.replace(items); this.isLoaded = true; } finally { @@ -114,16 +109,18 @@ export abstract class ItemStore { const item = await Promise.resolve(request()).catch(() => null); if (item) { - const existingItem = this.items.find(el => el.getId() === item.getId()); + const existingItem = this.items.find((el) => el.getId() === item.getId()); if (existingItem) { - const index = this.items.findIndex(item => item === existingItem); + const index = this.items.findIndex((item) => item === existingItem); this.items.splice(index, 1, item); } else { let items = [...this.items, item]; - if (sortItems) items = this.sortItems(items); + if (sortItems) { + items = this.sortItems(items); + } this.items.replace(items); } } @@ -134,7 +131,7 @@ export abstract class ItemStore { @action protected async updateItem(item: Item, request: () => Promise) { const updatedItem = await request(); - const index = this.items.findIndex(i => i.getId() === item.getId()); + const index = this.items.findIndex((i) => i.getId() === item.getId()); this.items.splice(index, 1, updatedItem); @@ -183,7 +180,9 @@ export abstract class ItemStore { } isSelectedAll(visibleItems: Item[] = this.items) { - if (!visibleItems.length) return false; + if (!visibleItems.length) { + return false; + } return visibleItems.every(this.isSelected); } @@ -206,7 +205,7 @@ export abstract class ItemStore { async removeItems?(items: Item[]): Promise; - * [Symbol.iterator]() { + *[Symbol.iterator]() { yield* this.items; } } diff --git a/packages/utility-features/item-store/tsconfig.json b/packages/utility-features/item-store/tsconfig.json new file mode 100644 index 000000000000..1031f883fc75 --- /dev/null +++ b/packages/utility-features/item-store/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@k8slens/typescript/config/base.json", + "include": ["**/*.ts", "../kube-api-specifics/src/token.ts"], + "compilerOptions": { + "moduleResolution": "node" + } +} diff --git a/packages/utility-features/item-store/webpack.config.js b/packages/utility-features/item-store/webpack.config.js new file mode 100644 index 000000000000..b54738d0a7ed --- /dev/null +++ b/packages/utility-features/item-store/webpack.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/webpack").configForReact; \ No newline at end of file