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

Only group delete visible items (#4798)

This commit is contained in:
Sebastian Malton 2022-02-07 10:40:20 -05:00 committed by GitHub
parent 06ec1b39a4
commit 4abe0d6c20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 45 deletions

View File

@ -21,7 +21,7 @@ export abstract class ItemStore<Item extends ItemObject> {
@observable isLoading = false; @observable isLoading = false;
@observable isLoaded = false; @observable isLoaded = false;
@observable items = observable.array<Item>([], { deep: false }); @observable items = observable.array<Item>([], { deep: false });
@observable selectedItemsIds = observable.map<string, boolean>(); @observable selectedItemsIds = observable.set<string>();
constructor() { constructor() {
makeObservable(this); makeObservable(this);
@ -29,7 +29,11 @@ export abstract class ItemStore<Item extends ItemObject> {
} }
@computed get selectedItems(): Item[] { @computed get selectedItems(): Item[] {
return this.items.filter(item => this.selectedItemsIds.get(item.getId())); return this.pickOnlySelected(this.items);
}
public pickOnlySelected(items: Item[]): Item[] {
return items.filter(item => this.selectedItemsIds.has(item.getId()));
} }
public getItems(): Item[] { public getItems(): Item[] {
@ -152,12 +156,12 @@ export abstract class ItemStore<Item extends ItemObject> {
} }
isSelected(item: Item) { isSelected(item: Item) {
return !!this.selectedItemsIds.get(item.getId()); return this.selectedItemsIds.has(item.getId());
} }
@action @action
select(item: Item) { select(item: Item) {
this.selectedItemsIds.set(item.getId(), true); this.selectedItemsIds.add(item.getId());
} }
@action @action
@ -207,6 +211,8 @@ export abstract class ItemStore<Item extends ItemObject> {
async removeSelectedItems?(): Promise<any>; async removeSelectedItems?(): Promise<any>;
async removeItems?(items: Item[]): Promise<void>;
* [Symbol.iterator]() { * [Symbol.iterator]() {
yield* this.items; yield* this.items;
} }

View File

@ -369,6 +369,10 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
return Promise.all(this.selectedItems.map(this.remove)); return Promise.all(this.selectedItems.map(this.remove));
} }
async removeItems(items: T[]) {
await Promise.all(items.map(this.remove));
}
// collect items from watch-api events to avoid UI blowing up with huge streams of data // collect items from watch-api events to avoid UI blowing up with huge streams of data
protected eventsBuffer = observable.array<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false }); protected eventsBuffer = observable.array<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });

View File

@ -134,10 +134,12 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
return releases.get().filter((release) => release.isSelected); return releases.get().filter((release) => release.isSelected);
}, },
removeSelectedItems() { pickOnlySelected: (releases) => {
return Promise.all( return releases.filter(release => release.isSelected);
releases.get().filter((release) => release.isSelected).map((release) => release.delete()), },
);
removeItems: async (releases) => {
await Promise.all(releases.map(release => release.delete()));
}, },
} as ItemStore<RemovableHelmRelease>; } as ItemStore<RemovableHelmRelease>;

View File

@ -136,19 +136,27 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
} }
@boundMethod @boundMethod
removeItemsDialog() { removeItemsDialog(selectedItems: I[]) {
const { customizeRemoveDialog, store } = this.props; const { customizeRemoveDialog, store } = this.props;
const { selectedItems, removeSelectedItems } = store;
const visibleMaxNamesCount = 5; const visibleMaxNamesCount = 5;
const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", "); const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", ");
const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {}; const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {};
const selectedCount = selectedItems.length; const selectedCount = selectedItems.length;
const tailCount = selectedCount > visibleMaxNamesCount ? selectedCount - visibleMaxNamesCount : 0; const tailCount = selectedCount > visibleMaxNamesCount
const tail = tailCount > 0 ? <>, and <b>{tailCount}</b> more</> : null; ? selectedCount - visibleMaxNamesCount
const message = selectedCount <= 1 ? <p>Remove item <b>{selectedNames}</b>?</p> : <p>Remove <b>{selectedCount}</b> items <b>{selectedNames}</b>{tail}?</p>; : 0;
const tail = tailCount > 0
? <>, and <b>{tailCount}</b> more</>
: null;
const message = selectedCount <= 1
? <p>Remove item <b>{selectedNames}</b>?</p>
: <p>Remove <b>{selectedCount}</b> items <b>{selectedNames}</b>{tail}?</p>;
const onConfirm = store.removeItems
? () => store.removeItems(selectedItems)
: store.removeSelectedItems;
ConfirmDialog.open({ ConfirmDialog.open({
ok: removeSelectedItems, ok: onConfirm,
labelOk: "Remove", labelOk: "Remove",
message, message,
...dialogCustomProps, ...dialogCustomProps,
@ -225,10 +233,12 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
render() { render() {
const { const {
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks,
detailsItem, className, tableProps = {}, tableId, detailsItem, className, tableProps = {}, tableId, getItems,
} = this.props; } = this.props;
const selectedItemId = detailsItem && detailsItem.getId(); const selectedItemId = detailsItem && detailsItem.getId();
const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type); const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type);
const items = getItems();
const selectedItems = store.pickOnlySelected(items);
return ( return (
<div className="items box grow flex column"> <div className="items box grow flex column">
@ -238,7 +248,7 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
selectable={hasDetailsView} selectable={hasDetailsView}
sortable={sortingCallbacks} sortable={sortingCallbacks}
getTableRow={this.getRow} getTableRow={this.getRow}
items={this.props.getItems()} items={items}
selectedItemId={selectedItemId} selectedItemId={selectedItemId}
noItems={this.renderNoItems()} noItems={this.renderNoItems()}
className={classNames} className={classNames}
@ -252,9 +262,11 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
{() => ( {() => (
<AddRemoveButtons <AddRemoveButtons
onRemove={ onRemove={
store.selectedItems.length ? this.removeItemsDialog : null (store.removeItems || store.removeSelectedItems) && selectedItems.length > 0
? () => this.removeItemsDialog(selectedItems)
: null
} }
removeTooltip={`Remove selected items (${store.selectedItems.length})`} removeTooltip={`Remove selected items (${selectedItems.length})`}
{...addRemoveButtons} {...addRemoveButtons}
/> />
)} )}

View File

@ -13,7 +13,7 @@ import { boundMethod, cssNames, IClassName, noop, StorageHelper } from "../../ut
import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import type { ItemObject, ItemStore } from "../../../common/item.store"; import type { ItemObject, ItemStore } from "../../../common/item.store";
import type { SearchInputUrlProps } from "../input"; import type { SearchInputUrlProps } from "../input";
import { Filter, FilterType, pageFilters } from "./page-filters.store"; import { FilterType, pageFilters } from "./page-filters.store";
import { PageFiltersList } from "./page-filters-list"; import { PageFiltersList } from "./page-filters-list";
import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store"; import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
@ -202,19 +202,18 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
}; };
@computed get items() { @computed get items() {
const filterGroups = groupBy<Filter>(this.filters, ({ type }) => type); const filterGroups = groupBy(this.filters, ({ type }) => type);
const filterItems: ItemsFilter<I>[] = []; const filterItems: ItemsFilter<I>[] = [];
Object.entries(filterGroups).forEach(([type, filtersGroup]) => { for (const [type, filtersGroup] of Object.entries(filterGroups)) {
const filterCallback = this.filterCallbacks[type] ?? this.props.filterCallbacks?.[type]; const filterCallback = this.filterCallbacks[type] ?? this.props.filterCallbacks?.[type];
if (filterCallback && filtersGroup.length > 0) { if (filterCallback && filtersGroup.length > 0) {
filterItems.push(filterCallback); filterItems.push(filterCallback);
} }
}); }
const items = this.props.getItems ? this.props.getItems() : (this.props.items ?? this.props.store.items); const items = this.props.getItems?.() ?? this.props.items ?? this.props.store.items;
return applyFilters(filterItems.concat(this.props.filterItems), items); return applyFilters(filterItems.concat(this.props.filterItems), items);
} }

View File

@ -138,25 +138,14 @@ class NonInjectedKubeObjectListLayout<K extends KubeObject> extends React.Compon
} }
} }
const InjectedKubeObjectListLayout = withInjectables< const InjectedKubeObjectListLayout = withInjectables<Dependencies, KubeObjectListLayoutProps<KubeObject>>(NonInjectedKubeObjectListLayout, {
Dependencies,
KubeObjectListLayoutProps<KubeObject>
>(
NonInjectedKubeObjectListLayout,
{
getProps: (di, props) => ({ getProps: (di, props) => ({
clusterFrameContext: di.inject(clusterFrameContextInjectable), clusterFrameContext: di.inject(clusterFrameContextInjectable),
subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores,
...props, ...props,
}), }),
}, });
);
export function KubeObjectListLayout<K extends KubeObject>(
props: KubeObjectListLayoutProps<K>,
) {
export function KubeObjectListLayout<K extends KubeObject>(props: KubeObjectListLayoutProps<K>) {
return <InjectedKubeObjectListLayout {...props} />; return <InjectedKubeObjectListLayout {...props} />;
} }

View File

@ -73,8 +73,8 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
}); });
} }
async removeSelectedItems() { async removeItems(items: PortForwardItem[]) {
return Promise.all(this.selectedItems.map(this.remove)); await Promise.all(items.map(this.remove));
} }
getById(id: string) { getById(id: string) {