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

add more typing to sorting of tables

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-12-15 13:29:30 -05:00
parent a62e1fc4e5
commit f66d84ae4a
17 changed files with 110 additions and 138 deletions

View File

@ -56,4 +56,4 @@ export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeOb
text: `${event.message}`, text: `${event.message}`,
timestamp: event.metadata.creationTimestamp timestamp: event.metadata.creationTimestamp
}; };
} }

View File

@ -269,7 +269,6 @@ export class ExtensionDiscovery {
// fs.remove won't throw if path is missing // fs.remove won't throw if path is missing
await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json")); await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
try { try {
// Verify write access to static/extensions, which is needed for symlinking // Verify write access to static/extensions, which is needed for symlinking
await fs.access(this.inTreeFolderPath, fs.constants.W_OK); await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
@ -293,19 +292,19 @@ export class ExtensionDiscovery {
this.bundledFolderPath = this.inTreeTargetPath; this.bundledFolderPath = this.inTreeTargetPath;
} }
await fs.ensureDir(this.nodeModulesPath);
await fs.ensureDir(this.localFolderPath); await Promise.all([fs.ensureDir(this.nodeModulesPath), fs.ensureDir(this.localFolderPath)]);
const extensions = await this.loadExtensions(); const extensions = await this.loadExtensions();
this.isLoaded = true; this.isLoaded = true;
return extensions; return extensions;
} }
/** /**
* Returns the symlinked path to the extension folder, * Returns the symlinked path to the extension folder,
* e.g. "/Users/<username>/Library/Application Support/Lens/node_modules/@publisher/extension" * e.g. `/Users/<username>/Library/Application Support/Lens/node_modules/@publisher/extension`
*/ */
protected getInstalledPath(name: string) { protected getInstalledPath(name: string) {
return path.join(this.nodeModulesPath, name); return path.join(this.nodeModulesPath, name);

View File

@ -10,12 +10,12 @@ export class ApiManager {
private apis = observable.map<string, KubeApi>(); private apis = observable.map<string, KubeApi>();
private stores = observable.map<KubeApi, KubeObjectStore<any>>(); private stores = observable.map<KubeApi, KubeObjectStore<any>>();
getApi(pathOrCallback: string | ((api: KubeApi) => boolean) = () => true) { getApi<T extends KubeObject = KubeObject>(pathOrCallback: string | ((api: KubeApi) => boolean) = () => true): KubeApi<T> {
if (typeof pathOrCallback === "string") { if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase); return (this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase)) as KubeApi<T>;
} }
return Array.from(this.apis.values()).find(pathOrCallback); return Array.from(this.apis.values()).find(pathOrCallback) as KubeApi<T>;
} }
registerApi(apiBase: string, api: KubeApi) { registerApi(apiBase: string, api: KubeApi) {

View File

@ -3,31 +3,29 @@ import { KubeObject } from "../kube-object";
import { KubeJsonApiData } from "../kube-json-api"; import { KubeJsonApiData } from "../kube-json-api";
import { apiBase } from "../index"; import { apiBase } from "../index";
import { apiManager } from "../api-manager"; import { apiManager } from "../api-manager";
import { CancelablePromise } from "../../utils/cancelableFetch";
import { filter } from "lodash";
export const resourceApplierApi = { export const resourceApplierApi = {
annotations: [ annotations: [
"kubectl.kubernetes.io/last-applied-configuration" "kubectl.kubernetes.io/last-applied-configuration"
], ],
async update<D extends KubeObject>(resource: object | string): Promise<D> { update<D extends KubeObject>(resource: object | string): CancelablePromise<D[]> {
if (typeof resource === "string") { if (typeof resource === "string") {
resource = jsYaml.safeLoad(resource); resource = jsYaml.safeLoad(resource);
} }
return apiBase return apiBase
.post<KubeJsonApiData[]>("/stack", { data: resource }) .post<KubeJsonApiData[]>("/stack", { data: resource })
.then(data => { .then(data => filter(
const items = data.map(obj => { data.map(obj => {
const api = apiManager.getApi(obj.metadata.selfLink); const api = apiManager.getApi<D>(obj.metadata.selfLink);
if (api) { if (api?.objectConstructor) {
return new api.objectConstructor(obj); return new api.objectConstructor(obj);
} else {
return new KubeObject(obj);
} }
}); })
));
return items.length === 1 ? items[0] : items;
});
} }
}; };

View File

@ -4,11 +4,8 @@ import { KubeObjectStore } from "../../kube-object.store";
import { KubeObject } from "../../api/kube-object"; import { KubeObject } from "../../api/kube-object";
@autobind() @autobind()
export class CRDResourceStore<T extends KubeObject = any> extends KubeObjectStore<T> { export class CRDResourceStore<T extends KubeObject = KubeObject> extends KubeObjectStore<T> {
api: KubeApi; constructor(public api: KubeApi<T>) {
constructor(api: KubeApi<T>) {
super(); super();
this.api = api;
} }
} }

View File

@ -22,7 +22,7 @@ enum sortBy {
age = "age", age = "age",
} }
interface Props extends Partial<KubeObjectListLayoutProps> { interface Props extends Partial<KubeObjectListLayoutProps<KubeEvent>> {
className?: IClassName; className?: IClassName;
compact?: boolean; compact?: boolean;
compactLimit?: number; compactLimit?: number;
@ -45,17 +45,17 @@ export class Events extends React.Component<Props> {
store={eventStore} store={eventStore}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.namespace]: (event: KubeEvent) => event.getNs(), [sortBy.namespace]: event => event.getNs(),
[sortBy.type]: (event: KubeEvent) => event.involvedObject.kind, [sortBy.type]: event => event.involvedObject.kind,
[sortBy.object]: (event: KubeEvent) => event.involvedObject.name, [sortBy.object]: event => event.involvedObject.name,
[sortBy.count]: (event: KubeEvent) => event.count, [sortBy.count]: event => event.count,
[sortBy.age]: (event: KubeEvent) => event.metadata.creationTimestamp, [sortBy.age]: event => event.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(event: KubeEvent) => event.getSearchFields(), event => event.getSearchFields(),
(event: KubeEvent) => event.message, event => event.message,
(event: KubeEvent) => event.getSource(), event => event.getSource(),
(event: KubeEvent) => event.involvedObject.name, event => event.involvedObject.name,
]} ]}
renderHeaderTitle={<Trans>Events</Trans>} renderHeaderTitle={<Trans>Events</Trans>}
customizeHeader={({ title, info }) => ( customizeHeader={({ title, info }) => (
@ -82,7 +82,7 @@ export class Events extends React.Component<Props> {
{ title: <Trans>Count</Trans>, className: "count", sortBy: sortBy.count }, { title: <Trans>Count</Trans>, className: "count", sortBy: sortBy.count },
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age }, { title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
]} ]}
renderTableContents={(event: KubeEvent) => { renderTableContents={event => {
const { involvedObject, type, message } = event; const { involvedObject, type, message } = event;
const { kind, name } = involvedObject; const { kind, name } = involvedObject;
const tooltipId = `message-${event.getId()}`; const tooltipId = `message-${event.getId()}`;

View File

@ -35,16 +35,21 @@ export class ServiceAccountsDetails extends React.Component<Props> {
return; return;
} }
const namespace = serviceAccount.getNs(); const namespace = serviceAccount.getNs();
const secrets = serviceAccount.getSecrets().map(({ name }) => {
return secretsStore.load({ name, namespace });
});
this.secrets = await Promise.all(secrets); this.secrets = await Promise.all(
const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async({ name }) => { serviceAccount
return secretsStore.load({ name, namespace }).catch(() => this.generateDummySecretObject(name)); .getSecrets()
}); .map(({ name }) => secretsStore.load({ name, namespace }))
);
this.imagePullSecrets = await Promise.all(imagePullSecrets); this.imagePullSecrets = await Promise.all(
serviceAccount
.getImagePullSecrets()
.map(({ name }) => (
secretsStore
.load({ name, namespace })
.catch(() => this.generateDummySecretObject(name))
))
);
}); });
renderSecrets() { renderSecrets() {

View File

@ -1,7 +1,7 @@
import "./overview.scss"; import "./overview.scss";
import React from "react"; import React from "react";
import { observable, when } from "mobx"; import { computed, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { OverviewStatuses } from "./overview-statuses"; import { OverviewStatuses } from "./overview-statuses";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
@ -18,60 +18,36 @@ import { Spinner } from "../spinner";
import { Events } from "../+events"; import { Events } from "../+events";
import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectStore } from "../../kube-object.store";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource } from "../../../common/rbac";
import { filter } from "lodash";
interface Props extends RouteComponentProps<IWorkloadsOverviewRouteParams> { interface Props extends RouteComponentProps<IWorkloadsOverviewRouteParams> {
} }
@observer @observer
export class WorkloadsOverview extends React.Component<Props> { export class WorkloadsOverview extends React.Component<Props> {
@observable isReady = false; @observable stores: KubeObjectStore<any>[] = [];
@observable isUnmounting = false; unsubscribeList: (() => void)[] = [];
@computed get isReady() {
return this.stores.every(store => store.isLoaded);
}
async componentDidMount() { async componentDidMount() {
const stores: KubeObjectStore[] = []; this.stores = filter([
isAllowedResource("pods") && podsStore,
if (isAllowedResource("pods")) { isAllowedResource("deployments") && deploymentStore,
stores.push(podsStore); isAllowedResource("daemonsets") && daemonSetStore,
} isAllowedResource("statefulsets") && statefulSetStore,
isAllowedResource("replicasets") && replicaSetStore,
if (isAllowedResource("deployments")) { isAllowedResource("jobs") && jobStore,
stores.push(deploymentStore); isAllowedResource("cronjobs") && cronJobStore,
} isAllowedResource("events") && eventStore,
]);
if (isAllowedResource("daemonsets")) { this.unsubscribeList = this.stores.map(store => store.subscribe());
stores.push(daemonSetStore);
}
if (isAllowedResource("statefulsets")) {
stores.push(statefulSetStore);
}
if (isAllowedResource("replicasets")) {
stores.push(replicaSetStore);
}
if (isAllowedResource("jobs")) {
stores.push(jobStore);
}
if (isAllowedResource("cronjobs")) {
stores.push(cronJobStore);
}
if (isAllowedResource("events")) {
stores.push(eventStore);
}
this.isReady = stores.every(store => store.isLoaded);
await Promise.all(stores.map(store => store.loadAll()));
this.isReady = true;
const unsubscribeList = stores.map(store => store.subscribe());
await when(() => this.isUnmounting);
unsubscribeList.forEach(dispose => dispose());
} }
componentWillUnmount() { componentWillUnmount() {
this.isUnmounting = true; this.unsubscribeList.forEach(dispose => dispose());
} }
renderContents() { renderContents() {

View File

@ -8,7 +8,7 @@ import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { KubeResource } from "../../../common/rbac"; import { KubeResource } from "../../../common/rbac";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = { export const workloadStores: Partial<Record<KubeResource, KubeObjectStore<any>>> = {
"pods": podsStore, "pods": podsStore,
"deployments": deploymentStore, "deployments": deploymentStore,
"daemonsets": daemonSetStore, "daemonsets": daemonSetStore,

View File

@ -46,11 +46,11 @@ export class CreateResource extends React.Component<Props> {
const errors: string[] = []; const errors: string[] = [];
await Promise.all( await Promise.all(
resources.map(data => { resources.map(data => (
return resourceApplierApi.update(data) resourceApplierApi.update(data)
.then(item => createdResources.push(item.getName())) .then(item => createdResources.push(...item.map(item => item.getName())))
.catch((err: JsonApiErrorParsed) => errors.push(err.toString())); .catch((err: JsonApiErrorParsed) => errors.push(err.toString()))
}) ))
); );
if (errors.length) { if (errors.length) {

View File

@ -32,19 +32,19 @@ interface IHeaderPlaceholders {
info: ReactNode; info: ReactNode;
} }
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> { export interface ItemListLayoutProps<Entry extends ItemObject = ItemObject, SortingOption extends string = string> {
className: IClassName; className: IClassName;
store: ItemStore<T>; store: ItemStore<Entry>;
dependentStores?: ItemStore[]; dependentStores?: ItemStore[];
isClusterScoped?: boolean; isClusterScoped?: boolean;
hideFilters?: boolean; hideFilters?: boolean;
searchFilters?: SearchFilter<T>[]; searchFilters?: SearchFilter<Entry>[];
filterItems?: ItemsFilter<T>[]; filterItems?: ItemsFilter<Entry>[];
// header (title, filtering, searching, etc.) // header (title, filtering, searching, etc.)
showHeader?: boolean; showHeader?: boolean;
headerClassName?: IClassName; headerClassName?: IClassName;
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<T>) => ReactNode); renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<Entry>) => ReactNode);
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode; customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
// items list configuration // items list configuration
@ -52,23 +52,23 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isSelectable?: boolean; // show checkbox in rows for selecting items isSelectable?: boolean; // show checkbox in rows for selecting items
isSearchable?: boolean; // apply search-filter & add search-input isSearchable?: boolean; // apply search-filter & add search-input
copyClassNameFromHeadCells?: boolean; copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: TableSortCallback<T> }; sortingCallbacks?: Record<SortingOption, TableSortCallback<Entry>>;
tableProps?: Partial<TableProps<T>>; // low-level table configuration tableProps?: Partial<TableProps<Entry>>; // low-level table configuration
renderTableHeader: TableCellProps[] | null; renderTableHeader: TableCellProps<SortingOption>[] | null;
renderTableContents: (item: T) => (ReactNode | TableCellProps)[]; renderTableContents: (item: Entry) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode; renderItemMenu?: (item: Entry, store: ItemStore<Entry>) => ReactNode;
customizeTableRowProps?: (item: T) => Partial<TableRowProps>; customizeTableRowProps?: (item: Entry) => Partial<TableRowProps>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>; addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean; virtual?: boolean;
// item details view // item details view
hasDetailsView?: boolean; hasDetailsView?: boolean;
detailsItem?: T; detailsItem?: Entry;
onDetails?: (item: T) => void; onDetails?: (item: Entry) => void;
// other // other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>; customizeRemoveDialog?: (selectedItems: Entry[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout<T>) => React.ReactNode; renderFooter?: (parent: ItemListLayout<Entry>) => React.ReactNode;
} }
const defaultProps: Partial<ItemListLayoutProps> = { const defaultProps: Partial<ItemListLayoutProps> = {

View File

@ -13,6 +13,7 @@ import { crdStore } from "../+custom-resources/crd.store";
import { CrdResourceDetails } from "../+custom-resources"; import { CrdResourceDetails } from "../+custom-resources";
import { KubeObjectMenu } from "./kube-object-menu"; import { KubeObjectMenu } from "./kube-object-menu";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { CustomResourceDefinition } from "../../api/endpoints";
export interface KubeObjectDetailsProps<T = KubeObject> { export interface KubeObjectDetailsProps<T = KubeObject> {
className?: string; className?: string;
@ -81,7 +82,7 @@ export class KubeObjectDetails extends React.Component {
}); });
if (isCrdInstance && details.length === 0) { if (isCrdInstance && details.length === 0) {
details.push(<CrdResourceDetails object={object} />); details.push(<CrdResourceDetails object={object as CustomResourceDefinition} />);
} }
} }

View File

@ -9,7 +9,7 @@ import { KubeObjectStore } from "../../kube-object.store";
import { KubeObjectMenu } from "./kube-object-menu"; import { KubeObjectMenu } from "./kube-object-menu";
import { ItemObject } from "../../item.store"; import { ItemObject } from "../../item.store";
export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject> extends ItemListLayoutProps<T> { export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject, SortOrder extends string> extends ItemListLayoutProps<T, SortOrder> {
store: KubeObjectStore<T>; store: KubeObjectStore<T>;
} }
@ -18,7 +18,7 @@ function showItemDetails(item: KubeObject) {
} }
@observer @observer
export class KubeObjectListLayout<T extends ItemObject & KubeObject> extends React.Component<KubeObjectListLayoutProps<T>> { export class KubeObjectListLayout<T extends ItemObject & KubeObject, SortOrder extends string> extends React.Component<KubeObjectListLayoutProps<T, SortOrder>> {
@computed get selectedItem() { @computed get selectedItem() {
return this.props.store.getByPath(getSelectedDetails()); return this.props.store.getByPath(getSelectedDetails());
} }

View File

@ -1,5 +1,5 @@
import "./table-cell.scss"; import "./table-cell.scss";
import type { TableSortBy, TableSortParams } from "./table"; import type { TableSortParams } from "./table";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { autobind, cssNames, displayBooleans } from "../../utils"; import { autobind, cssNames, displayBooleans } from "../../utils";
@ -8,15 +8,15 @@ import { Checkbox } from "../checkbox";
export type TableCellElem = React.ReactElement<TableCellProps>; export type TableCellElem = React.ReactElement<TableCellProps>;
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> { export interface TableCellProps<SortingOption extends string = string> extends React.DOMAttributes<HTMLDivElement> {
className?: string; className?: string;
title?: ReactNode; title?: ReactNode;
checkbox?: boolean; // render cell with a checkbox checkbox?: boolean; // render cell with a checkbox
isChecked?: boolean; // mark checkbox as checked or not isChecked?: boolean; // mark checkbox as checked or not
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/> sortBy?: SortingOption; // column name, must be same as key in sortable object <Table sortable={}/>
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!) _sorting?: Partial<TableSortParams<SortingOption>>; // <Table> sorting state, don't use this prop outside (!)
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!) _sort?(sortBy: SortingOption): void; // <Table> sort function, don't use this prop outside (!)
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!) _nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
} }

View File

@ -14,12 +14,11 @@ import { ItemObject } from "../../item.store";
// todo: refactor + decouple search from location // todo: refactor + decouple search from location
export type TableSortBy = string;
export type TableOrderBy = "asc" | "desc" | string; export type TableOrderBy = "asc" | "desc" | string;
export type TableSortParams = { sortBy: TableSortBy; orderBy: TableOrderBy }; export type TableSortParams<SortingOption extends string> = { sortBy: SortingOption; orderBy: TableOrderBy };
export type TableSortCallback<D> = (data: D) => string | number | (string | number)[]; export type TableSortCallback<D> = (data: D) => string | number | (string | number)[];
export interface TableProps<T> extends React.DOMAttributes<HTMLDivElement> { export interface TableProps<T, SortingOption extends string = string> extends React.DOMAttributes<HTMLDivElement> {
items?: ItemObject[]; // Raw items data items?: ItemObject[]; // Raw items data
className?: string; className?: string;
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0) autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
@ -32,8 +31,8 @@ export interface TableProps<T> extends React.DOMAttributes<HTMLDivElement> {
[sortBy: string]: TableSortCallback<T>; [sortBy: string]: TableSortCallback<T>;
}; };
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
sortByDefault?: Partial<TableSortParams>; // default sorting params sortByDefault?: Partial<TableSortParams<SortingOption>>; // default sorting params
onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url onSort?: (params: TableSortParams<SortingOption>) => void; // callback on sort change, default: global sync with url
noItems?: React.ReactNode; // Show no items state table list is empty noItems?: React.ReactNode; // Show no items state table list is empty
selectedItemId?: string; // Allows to scroll list to selected item selectedItemId?: string; // Allows to scroll list to selected item
virtual?: boolean; // Use virtual list component to render only visible rows virtual?: boolean; // Use virtual list component to render only visible rows

View File

@ -136,12 +136,8 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
return this.load({ name, namespace }); return this.load({ name, namespace });
} }
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {
return this.api.create(params, data);
}
async create(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> { async create(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {
const newItem = await this.createItem(params, data); const newItem = await this.api.create(params, data);
const items = this.sortItems([...this.items, newItem]); const items = this.sortItems([...this.items, newItem]);
this.items.replace(items); this.items.replace(items);
@ -150,7 +146,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
} }
async update(item: T, data: Partial<T>): Promise<T> { async update(item: T, data: Partial<T>): Promise<T> {
const newItem = await item.update<T>(data); const [newItem] = await item.update<T>(data);
const index = this.items.findIndex(item => item.getId() === newItem.getId()); const index = this.items.findIndex(item => item.getId() === newItem.getId());
this.items.splice(index, 1, newItem); this.items.splice(index, 1, newItem);
@ -172,9 +168,11 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
protected eventsBuffer = observable<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false }); protected eventsBuffer = observable<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });
protected bindWatchEventsUpdater(delay = 1000) { protected bindWatchEventsUpdater(delay = 1000) {
return reaction(() => this.eventsBuffer.toJS()[0], this.updateFromEventsBuffer, { return reaction(
delay () => this.eventsBuffer.toJS()[0],
}); this.updateFromEventsBuffer,
{ delay }
);
} }
subscribe(apis = [this.api]) { subscribe(apis = [this.api]) {
@ -196,23 +194,22 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
for (const {type, object} of this.eventsBuffer.clear()) { for (const {type, object} of this.eventsBuffer.clear()) {
const { uid, selfLink } = object.metadata; const { uid, selfLink } = object.metadata;
const index = items.findIndex(item => item.getId() === uid); const index = items.map(item => item.getId()).indexOf(uid);
const item = items[index]; const api = apiManager.getApi<T>(selfLink);
const api = apiManager.getApi(selfLink);
switch (type) { switch (type) {
case "ADDED": case "ADDED":
case "MODIFIED": case "MODIFIED":
const newItem = new api.objectConstructor(object); const newItem = new api.objectConstructor(object);
if (!item) { if (index < 0) {
items.push(newItem); items.push(newItem);
} else { } else {
items.splice(index, 1, newItem); items.splice(index, 1, newItem);
} }
break; break;
case "DELETED": case "DELETED":
if (item) { if (index >= 0) {
items.splice(index, 1); items.splice(index, 1);
} }
break; break;

View File

@ -12,7 +12,7 @@ interface WrappingFunction {
<T>(result: T): T; <T>(result: T): T;
} }
export function cancelableFetch(reqInfo: RequestInfo, reqInit: RequestInit = {}) { export function cancelableFetch(reqInfo: RequestInfo, reqInit: RequestInit = {}): CancelablePromise<any> {
const abortController = new AbortController(); const abortController = new AbortController();
const signal = abortController.signal; const signal = abortController.signal;
const cancel = abortController.abort.bind(abortController); const cancel = abortController.abort.bind(abortController);