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

refactoring, detaching NamespaceStore from KubeObjectStore

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-02-05 14:28:46 +02:00
parent 5b86ae2f5d
commit 77e3a7fb83
18 changed files with 117 additions and 115 deletions

View File

@ -1,14 +1,14 @@
// Kubernetes watch-api client // Kubernetes watch-api client
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
import type { Cluster } from "../../main/cluster";
import type { IKubeWatchEvent, IKubeWatchEventStreamEnd, IWatchRoutePayload } from "../../main/routes/watch-route"; import type { IKubeWatchEvent, IKubeWatchEventStreamEnd, IWatchRoutePayload } from "../../main/routes/watch-route";
import type { KubeObject } from "./kube-object"; import type { KubeObject } from "./kube-object";
import type { KubeObjectStore } from "../kube-object.store"; import type { KubeObjectStore } from "../kube-object.store";
import type { ClusterContext } from "../components/context";
import plimit from "p-limit"; import plimit from "p-limit";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { autorun, comparer, computed, IReactionDisposer, observable, reaction } from "mobx"; import { comparer, computed, IReactionDisposer, observable, reaction, when } from "mobx";
import { autobind, EventEmitter, noop } from "../utils"; import { autobind, EventEmitter, noop } from "../utils";
import { ensureObjectSelfLink, KubeApi, parseKubeApi } from "./kube-api"; import { ensureObjectSelfLink, KubeApi, parseKubeApi } from "./kube-api";
import { KubeJsonApiData, KubeJsonApiError } from "./kube-json-api"; import { KubeJsonApiData, KubeJsonApiError } from "./kube-json-api";
@ -48,21 +48,18 @@ export class KubeWatchApi {
private reader: ReadableStreamReader<string>; private reader: ReadableStreamReader<string>;
public onMessage = new EventEmitter<[IKubeWatchMessage]>(); public onMessage = new EventEmitter<[IKubeWatchMessage]>();
@observable.ref private cluster: Cluster; @observable context: ClusterContext = null;
@observable.ref private namespaces: string[] = [];
@observable subscribers = observable.map<KubeApi, number>(); @observable subscribers = observable.map<KubeApi, number>();
@observable isConnected = false; @observable isConnected = false;
@computed get isReady(): boolean { contextReady = when(() => Boolean(this.context));
return Boolean(this.cluster && this.namespaces);
}
@computed get isActive(): boolean { @computed get isActive(): boolean {
return this.apis.length > 0; return this.apis.length > 0;
} }
@computed get apis(): string[] { @computed get apis(): string[] {
if (!this.isReady) { if (!this.context) {
return []; return [];
} }
@ -72,22 +69,20 @@ export class KubeWatchApi {
} }
// TODO: optimize - check when all namespaces are selected and then request all in one // TODO: optimize - check when all namespaces are selected and then request all in one
if (api.isNamespaced && !this.cluster.isGlobalWatchEnabled) { if (api.isNamespaced && !this.context.cluster.isGlobalWatchEnabled) {
return this.namespaces.map(namespace => api.getWatchUrl(namespace)); return this.context.contextNamespaces.map(namespace => api.getWatchUrl(namespace));
} }
return api.getWatchUrl(); return api.getWatchUrl();
}).flat(); }).flat();
} }
async init({ getCluster, getNamespaces }: { constructor() {
getCluster: () => Cluster, this.init();
getNamespaces: () => string[], }
}): Promise<void> {
autorun(() => { private async init() {
this.cluster = getCluster(); await this.contextReady;
this.namespaces = getNamespaces();
});
this.bindAutoConnect(); this.bindAutoConnect();
} }
@ -109,7 +104,7 @@ export class KubeWatchApi {
} }
isAllowedApi(api: KubeApi): boolean { isAllowedApi(api: KubeApi): boolean {
return Boolean(this?.cluster.isAllowedResource(api.kind)); return Boolean(this.context?.cluster.isAllowedResource(api.kind));
} }
subscribeApi(api: KubeApi | KubeApi[]): () => void { subscribeApi(api: KubeApi | KubeApi[]): () => void {
@ -130,15 +125,15 @@ export class KubeWatchApi {
}; };
} }
preloadStores(stores: KubeObjectStore[], { loadOnce = false } = {}) { preloadStores(stores: KubeObjectStore[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) {
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
const preloading: Promise<any>[] = []; const preloading: Promise<any>[] = [];
for (const store of stores) { for (const store of stores) {
preloading.push(limitRequests(async () => { preloading.push(limitRequests(async () => {
if (store.isLoaded && loadOnce) return; // skip if (store.isLoaded && opts.loadOnce) return; // skip
return store.loadAll(this.namespaces); return store.loadAll({ namespaces: opts.namespaces });
})); }));
} }
@ -154,7 +149,7 @@ export class KubeWatchApi {
const unsubscribeList: (() => void)[] = []; const unsubscribeList: (() => void)[] = [];
let isUnsubscribed = false; let isUnsubscribed = false;
const load = () => this.preloadStores(stores, { loadOnce }); const load = (namespaces?: string[]) => this.preloadStores(stores, { namespaces, loadOnce });
let preloading = preload && load(); let preloading = preload && load();
let cancelReloading: IReactionDisposer = noop; let cancelReloading: IReactionDisposer = noop;
@ -175,10 +170,10 @@ export class KubeWatchApi {
subscribe(); subscribe();
} }
// reload when context namespaces changes // partial reload only selected namespaces
cancelReloading = reaction(() => this.namespaces, () => { cancelReloading = reaction(() => this.context.contextNamespaces, namespaces => {
preloading?.cancelLoading(); preloading?.cancelLoading();
preloading = load(); preloading = load(namespaces);
}, { }, {
equals: comparer.shallow, equals: comparer.shallow,
}); });
@ -291,7 +286,7 @@ export class KubeWatchApi {
} }
// skip updates from non-watching resources context // skip updates from non-watching resources context
if (!namespace || this.namespaces.includes(namespace)) { if (!namespace || this.context?.contextNamespaces.includes(namespace)) {
this.onMessage.emit(message); this.onMessage.emit(message);
} }
} catch (error) { } catch (error) {

View File

@ -30,7 +30,7 @@ export class CrdResources extends React.Component<Props> {
const { store } = this; const { store } = this;
if (store && !store.isLoading && !store.isLoaded) { if (store && !store.isLoading && !store.isLoaded) {
store.loadAllFromContextNamespaces(); store.reloadAll();
} }
}) })
]); ]);
@ -97,7 +97,7 @@ export class CrdResources extends React.Component<Props> {
...extraColumns.map((column) => { ...extraColumns.map((column) => {
let value = jsonPath.value(crdInstance, parseJsonPath(column.jsonPath.slice(1))); let value = jsonPath.value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
if (Array.isArray(value) || typeof value === "object") { if (Array.isArray(value) || typeof value === "object") {
value = JSON.stringify(value); value = JSON.stringify(value);
} }

View File

@ -14,7 +14,7 @@ export interface KubeEventDetailsProps {
@observer @observer
export class KubeEventDetails extends React.Component<KubeEventDetailsProps> { export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
async componentDidMount() { async componentDidMount() {
eventStore.loadAllFromContextNamespaces(); eventStore.reloadAll();
} }
render() { render() {

View File

@ -32,8 +32,8 @@ export class NamespaceDetails extends React.Component<Props> {
} }
componentDidMount() { componentDidMount() {
resourceQuotaStore.loadAllFromContextNamespaces(); resourceQuotaStore.reloadAll();
limitRangeStore.loadAllFromContextNamespaces(); limitRangeStore.reloadAll();
} }
render() { render() {

View File

@ -41,8 +41,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
} }
private async init() { private async init() {
await this.resolveCluster(); if (!this.context) return; // skip for non-cluster context window
if (!this.cluster) return; // skip for non-cluster context window
this.setContext(this.initialNamespaces); this.setContext(this.initialNamespaces);
this.autoLoadAllowedNamespaces(); this.autoLoadAllowedNamespaces();
@ -66,7 +65,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
} }
private autoLoadAllowedNamespaces(): IReactionDisposer { private autoLoadAllowedNamespaces(): IReactionDisposer {
return reaction(() => this.allowedNamespaces, namespaces => this.loadAll(namespaces), { return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), {
fireImmediately: true, fireImmediately: true,
equals: comparer.shallow, equals: comparer.shallow,
}); });
@ -94,8 +93,8 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
@computed get allowedNamespaces(): string[] { @computed get allowedNamespaces(): string[] {
return Array.from(new Set([ return Array.from(new Set([
...(this.cluster?.allowedNamespaces ?? []), // loaded names from main, updating every 30s and thus might be stale ...(this.context?.allNamespaces ?? []), // allowed namespaces from cluster (main), updating every 30s
...this.items.map(item => item.getName()), // loaded names from hosted cluster ...this.items.map(item => item.getName()), // loaded namespaces from k8s api
].flat())); ].flat()));
} }
@ -111,7 +110,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
getSubscribeApis() { getSubscribeApis() {
// if user has given static list of namespaces let's not start watches because watch adds stuff that's not wanted // if user has given static list of namespaces let's not start watches because watch adds stuff that's not wanted
if (this.cluster?.accessibleNamespaces.length > 0) { if (this.context?.cluster.accessibleNamespaces.length > 0) {
return []; return [];
} }

View File

@ -29,7 +29,7 @@ export class NodeDetails extends React.Component<Props> {
}); });
async componentDidMount() { async componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -7,7 +7,7 @@ import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard"; import { Wizard, WizardStep } from "../wizard";
import { Select, SelectOption } from "../select"; import { Select, SelectOption } from "../select";
import { SubTitle } from "../layout/sub-title"; import { SubTitle } from "../layout/sub-title";
import { IRoleBindingSubject, RoleBinding, ServiceAccount, Role } from "../../api/endpoints"; import { IRoleBindingSubject, Role, RoleBinding, ServiceAccount } from "../../api/endpoints";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Input } from "../input"; import { Input } from "../input";
import { NamespaceSelect } from "../+namespaces/namespace-select"; import { NamespaceSelect } from "../+namespaces/namespace-select";
@ -19,6 +19,7 @@ import { namespaceStore } from "../+namespaces/namespace.store";
import { serviceAccountsStore } from "../+user-management-service-accounts/service-accounts.store"; import { serviceAccountsStore } from "../+user-management-service-accounts/service-accounts.store";
import { roleBindingsStore } from "./role-bindings.store"; import { roleBindingsStore } from "./role-bindings.store";
import { showDetails } from "../kube-object"; import { showDetails } from "../kube-object";
import { KubeObjectStore } from "../../kube-object.store";
interface BindingSelectOption extends SelectOption { interface BindingSelectOption extends SelectOption {
value: string; // binding name value: string; // binding name
@ -73,14 +74,14 @@ export class AddRoleBindingDialog extends React.Component<Props> {
}; };
async loadData() { async loadData() {
const stores = [ const stores: KubeObjectStore[] = [
namespaceStore, namespaceStore,
rolesStore, rolesStore,
serviceAccountsStore, serviceAccountsStore,
]; ];
this.isLoading = true; this.isLoading = true;
await Promise.all(stores.map(store => store.loadAllFromContextNamespaces())); await Promise.all(stores.map(store => store.reloadAll()));
this.isLoading = false; this.isLoading = false;
} }
@ -136,8 +137,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
roleBinding: this.roleBinding, roleBinding: this.roleBinding,
addSubjects: subjects, addSubjects: subjects,
}); });
} } else {
else {
const name = useRoleForBindingName ? selectedRole.getName() : bindingName; const name = useRoleForBindingName ? selectedRole.getName() : bindingName;
roleBinding = await roleBindingsStore.create({ name, namespace }, { roleBinding = await roleBindingsStore.create({ name, namespace }, {
@ -265,7 +265,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
</h5> </h5>
); );
const disableNext = this.isLoading || !selectedRole || !selectedBindings.length; const disableNext = this.isLoading || !selectedRole || !selectedBindings.length;
const nextLabel = isEditing ? "Update" : "Create"; const nextLabel = isEditing ? "Update" : "Create";
return ( return (
<Dialog <Dialog

View File

@ -20,7 +20,7 @@ interface Props extends KubeObjectDetailsProps<CronJob> {
@observer @observer
export class CronJobDetails extends React.Component<Props> { export class CronJobDetails extends React.Component<Props> {
async componentDidMount() { async componentDidMount() {
jobStore.loadAllFromContextNamespaces(); jobStore.reloadAll();
} }
render() { render() {

View File

@ -30,7 +30,7 @@ export class DaemonSetDetails extends React.Component<Props> {
}); });
componentDidMount() { componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -31,7 +31,7 @@ export class DeploymentDetails extends React.Component<Props> {
}); });
componentDidMount() { componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -25,7 +25,7 @@ interface Props extends KubeObjectDetailsProps<Job> {
@observer @observer
export class JobDetails extends React.Component<Props> { export class JobDetails extends React.Component<Props> {
async componentDidMount() { async componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
render() { render() {

View File

@ -29,7 +29,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
}); });
async componentDidMount() { async componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -30,7 +30,7 @@ export class StatefulSetDetails extends React.Component<Props> {
}); });
componentDidMount() { componentDidMount() {
podsStore.loadAllFromContextNamespaces(); podsStore.reloadAll();
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -43,12 +43,13 @@ import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../exte
import { TabLayout, TabLayoutRoute } from "./layout/tab-layout"; import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog"; import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog";
import { eventStore } from "./+events/event.store"; import { eventStore } from "./+events/event.store";
import { namespaceStore } from "./+namespaces/namespace.store";
import { nodesStore } from "./+nodes/nodes.store"; import { nodesStore } from "./+nodes/nodes.store";
import { podsStore } from "./+workloads-pods/pods.store"; import { podsStore } from "./+workloads-pods/pods.store";
import { kubeWatchApi } from "../api/kube-watch-api"; import { kubeWatchApi } from "../api/kube-watch-api";
import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog"; import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog";
import { CommandContainer } from "./command-palette/command-container"; import { CommandContainer } from "./command-palette/command-container";
import { KubeObjectStore } from "../kube-object.store";
import { clusterContext } from "./context";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
@ -76,10 +77,9 @@ export class App extends React.Component {
}); });
whatInput.ask(); // Start to monitor user input device whatInput.ask(); // Start to monitor user input device
await kubeWatchApi.init({ // Setup hosted cluster context
getCluster: () => getHostedCluster(), KubeObjectStore.defaultContext = clusterContext;
getNamespaces: () => namespaceStore.contextNamespaces, kubeWatchApi.context = clusterContext;
});
} }
componentDidMount() { componentDidMount() {
@ -162,9 +162,9 @@ export class App extends React.Component {
const tabRoutes = this.getTabLayoutRoutes(menu); const tabRoutes = this.getTabLayoutRoutes(menu);
if (tabRoutes.length > 0) { if (tabRoutes.length > 0) {
const pageComponent = () => <TabLayout tabs={tabRoutes} />; const pageComponent = () => <TabLayout tabs={tabRoutes}/>;
route = <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)} />; route = <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)}/>;
this.extensionRoutes.set(menu, route); this.extensionRoutes.set(menu, route);
} else { } else {
const page = clusterPageRegistry.getByPageTarget(menu.target); const page = clusterPageRegistry.getByPageTarget(menu.target);
@ -228,7 +228,7 @@ export class App extends React.Component {
<StatefulSetScaleDialog/> <StatefulSetScaleDialog/>
<ReplicaSetScaleDialog/> <ReplicaSetScaleDialog/>
<CronJobTriggerDialog/> <CronJobTriggerDialog/>
<CommandContainer cluster={cluster} /> <CommandContainer cluster={cluster}/>
</ErrorBoundary> </ErrorBoundary>
</Router> </Router>
); );

View File

@ -0,0 +1,23 @@
import type { Cluster } from "../../main/cluster";
import { getHostedCluster } from "../../common/cluster-store";
import { namespaceStore } from "./+namespaces/namespace.store";
export interface ClusterContext {
cluster?: Cluster;
allNamespaces?: string[]; // available / allowed namespaces from cluster.ts
contextNamespaces?: string[]; // selected by user (see: namespace-select.tsx)
}
export const clusterContext: ClusterContext = {
get cluster(): Cluster | null {
return getHostedCluster();
},
get allNamespaces(): string[] {
return this.cluster?.allowedNamespaces ?? [];
},
get contextNamespaces(): string[] {
return namespaceStore.contextNamespaces ?? [];
},
};

View File

@ -40,7 +40,7 @@ interface Props {
@observer @observer
export class Sidebar extends React.Component<Props> { export class Sidebar extends React.Component<Props> {
async componentDidMount() { async componentDidMount() {
crdStore.loadAllFromContextNamespaces(); crdStore.reloadAll();
} }
renderCustomResources() { renderCustomResources() {

View File

@ -9,7 +9,7 @@ export interface ItemObject {
@autobind() @autobind()
export abstract class ItemStore<T extends ItemObject = ItemObject> { export abstract class ItemStore<T extends ItemObject = ItemObject> {
abstract loadAll(...args: any[]): Promise<void>; abstract loadAll(...args: any[]): Promise<void | T[]>;
protected defaultSorting = (item: T) => item.getName(); protected defaultSorting = (item: T) => item.getName();

View File

@ -1,7 +1,6 @@
import type { Cluster } from "../main/cluster"; import type { ClusterContext } from "./components/context";
import type { NamespaceStore } from "./components/+namespaces/namespace.store";
import { action, computed, observable, reaction } from "mobx"; import { action, observable, reaction, when } from "mobx";
import { autobind } from "./utils"; import { autobind } from "./utils";
import { KubeObject } from "./api/kube-object"; import { KubeObject } from "./api/kube-object";
import { IKubeWatchEvent, IKubeWatchMessage, kubeWatchApi } from "./api/kube-watch-api"; import { IKubeWatchEvent, IKubeWatchMessage, kubeWatchApi } from "./api/kube-watch-api";
@ -17,44 +16,23 @@ export interface KubeObjectStoreLoadingParams {
@autobind() @autobind()
export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> { export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> {
@observable static defaultContext: ClusterContext; // TODO: support multiple cluster contexts
abstract api: KubeApi<T>; abstract api: KubeApi<T>;
public readonly limit?: number; public readonly limit?: number;
public readonly bufferSize: number = 50000; public readonly bufferSize: number = 50000;
@observable.ref protected cluster: Cluster;
contextReady = when(() => Boolean(this.context));
get context(): ClusterContext {
return KubeObjectStore.defaultContext;
}
constructor() { constructor() {
super(); super();
this.bindWatchEventsUpdater(); this.bindWatchEventsUpdater();
} }
// TODO: detach / remove circular dependency
@observable.ref private namespaceStore: NamespaceStore;
protected async resolveNamespaceStore(): Promise<NamespaceStore> {
const { namespaceStore } = await import("./components/+namespaces/namespace.store");
this.namespaceStore = namespaceStore;
return namespaceStore;
}
protected async resolveCluster(): Promise<Cluster> {
const { getHostedCluster, clusterStore } = await import("../common/cluster-store");
await clusterStore.whenLoaded;
this.cluster = getHostedCluster();
await this.cluster.whenReady;
return this.cluster;
}
// TODO: figure out how to transparently replace with this.items
@computed get contextItems(): T[] {
const contextNamespaces = this.namespaceStore?.contextNamespaces ?? []; // not loaded
return this.items.filter((item: T) => !item.getNs() || contextNamespaces.includes(item.getId()));
}
get query(): IKubeApiQueryParams { get query(): IKubeApiQueryParams {
const { limit } = this; const { limit } = this;
@ -111,9 +89,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise<T[]> { protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise<T[]> {
const cluster = await this.resolveCluster(); if (this.context?.cluster.isAllowedResource(api.kind)) {
if (cluster.isAllowedResource(api.kind)) {
if (api.isNamespaced) { if (api.isNamespaced) {
return Promise return Promise
.all(namespaces.map(namespace => api.list({ namespace }))) .all(namespaces.map(namespace => api.list({ namespace })))
@ -131,21 +107,24 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
@action @action
async loadAll(namespaces?: string[], { replace = false /*partial update*/ } = {}): Promise<void> { async loadAll({ namespaces = [], merge = true } = {}): Promise<void | T[]> {
await this.contextReady;
this.isLoading = true; this.isLoading = true;
try { try {
// load all available namespaces by default if (!namespaces.length) {
if (!namespaces?.length) { namespaces = this.context.allNamespaces; // load all available namespaces by default
const namespaceStore = await this.resolveNamespaceStore();
namespaces = namespaceStore.allowedNamespaces; // load all by default if list not provided
} }
const items = await this.loadItems({ namespaces, api: this.api }); const items = await this.loadItems({ namespaces, api: this.api });
this.mergeItems(items, { replace });
this.isLoaded = true; this.isLoaded = true;
if (merge) {
this.mergeItems(items);
} else {
return items;
}
} catch (error) { } catch (error) {
console.error("Loading store items failed", { error, store: this }); console.error("Loading store items failed", { error, store: this });
this.resetOnError(error); this.resetOnError(error);
@ -155,18 +134,28 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
@action @action
mergeItems(partialItems: T[], { replace = false, updateStore = true, sort = true, filter = true } = {}): T[] { reloadAll(opts: { namespaces?: string[], merge?: boolean, force?: boolean } = {}) {
const { force = false, ...loadingOptions } = opts;
if (this.isLoading || (this.isLoaded && !force)) {
return;
}
return this.loadAll(loadingOptions);
}
@action
mergeItems(partialItems: T[], { replace = true, updateStore = true, sort = true, filter = true } = {}): T[] {
let items = partialItems; let items = partialItems;
// update existing items
if (!replace) { if (!replace) {
items = this.items.toJS(); const partialIds = partialItems.map(item => item.getId());
partialItems.forEach(item => { items = [
const index = items.findIndex(i => i.getId() === item.getId()); ...this.items.filter(existingItem => !partialIds.includes(existingItem.getId())),
...partialItems,
if (index < 0) items.push(item); // add ];
else items[index] = item; // update
});
} }
if (filter) items = this.filterItemsOnLoad(items); if (filter) items = this.filterItemsOnLoad(items);
@ -176,10 +165,6 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return items; return items;
} }
async loadAllFromContextNamespaces(): Promise<void> {
return this.loadAll(this.namespaceStore?.contextNamespaces);
}
protected resetOnError(error: any) { protected resetOnError(error: any) {
if (error) this.reset(); if (error) this.reset();
} }