From 151a393fb5b3a7636af769bedb942b0545dedc5b Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 23 May 2023 10:12:18 -0400 Subject: [PATCH 01/16] feat: Improve UX of namespace select filter - Add better support for thousands of namespaces - Add support for mouse only multi-selection Signed-off-by: Sebastian Malton --- .../namespace-select-filter-model.tsx | 150 ++++++------ .../namespaces/namespace-select-filter.scss | 117 +++++----- .../namespaces/namespace-select-filter.tsx | 220 +++++++++++++----- .../renderer/components/namespaces/store.ts | 14 ++ .../window/event-listener.injectable.ts | 4 +- .../utilities/src/type-narrowing.ts | 3 +- 6 files changed, 322 insertions(+), 186 deletions(-) diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx b/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx index c0ad194dfbc1..8a3cac363c63 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx +++ b/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx @@ -2,16 +2,14 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import React from "react"; -import type { IComputedValue } from "mobx"; +import type React from "react"; +import type { IComputedValue, IObservableValue } from "mobx"; import { observable, action, computed, comparer } from "mobx"; import type { NamespaceStore } from "../store"; -import type { ActionMeta, MultiValue } from "react-select"; -import { Icon } from "@k8slens/icon"; -import type { SelectOption } from "../../select"; import { observableCrate } from "@k8slens/utilities"; import type { IsMultiSelectionKey } from "./is-selection-key.injectable"; import type { ClusterContext } from "../../../cluster-frame-context/cluster-frame-context"; +import GlobToRegExp from "glob-to-regexp"; interface Dependencies { context: ClusterContext; @@ -22,22 +20,31 @@ interface Dependencies { export const selectAllNamespaces = Symbol("all-namespaces-selected"); export type SelectAllNamespaces = typeof selectAllNamespaces; -export type NamespaceSelectFilterOption = SelectOption; +export interface NamespaceSelectFilterOption { + value: string | SelectAllNamespaces; + label: string; + id: string | SelectAllNamespaces; +} export interface NamespaceSelectFilterModel { - readonly options: IComputedValue; + readonly options: IComputedValue; + readonly filteredOptions: IComputedValue; + readonly selectedOptions: IComputedValue; readonly menu: { open: () => void; close: () => void; + toggle: () => void; readonly isOpen: IComputedValue; + readonly hasSelectedAll: IComputedValue; + onKeyDown: React.KeyboardEventHandler; + onKeyUp: React.KeyboardEventHandler; }; - onChange: (newValue: MultiValue, actionMeta: ActionMeta) => void; - onClick: () => void; - onKeyDown: React.KeyboardEventHandler; - onKeyUp: React.KeyboardEventHandler; + onClick: (options: NamespaceSelectFilterOption) => void; + deselect: (namespace: string) => void; + select: (namespace: string) => void; + readonly filterText: IObservableValue; reset: () => void; isOptionSelected: (option: NamespaceSelectFilterOption) => boolean; - formatOptionLabel: (option: NamespaceSelectFilterOption) => JSX.Element; } enum SelectMenuState { @@ -45,6 +52,18 @@ enum SelectMenuState { Open = "open", } +const filterBasedOnText = (filterText: string) => { + const regexp = new RegExp(GlobToRegExp(filterText, { extended: true, flags: "gi" })); + + return (options: NamespaceSelectFilterOption) => { + if (options.value === selectAllNamespaces) { + return true; + } + + return Boolean(options.value.match(regexp)); + }; +}; + export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel { const { isMultiSelectionKey, namespaceStore, context } = dependencies; @@ -58,6 +77,7 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names didToggle = false; }, }]); + const filterText = observable.box(""); const selectedNames = computed(() => new Set(context.contextNamespaces), { equals: comparer.structural, }); @@ -74,7 +94,7 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names ? 1 : -1; }; - const options = computed((): readonly NamespaceSelectFilterOption[] => [ + const options = computed((): NamespaceSelectFilterOption[] => [ { value: selectAllNamespaces, label: "All Namespaces", @@ -89,6 +109,8 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names id: namespace, })), ]); + const filteredOptions = computed(() => options.get().filter(filterBasedOnText(filterText.get()))); + const selectedOptions = computed(() => options.get().filter(model.isOptionSelected)); const menuIsOpen = computed(() => menuState.get() === SelectMenuState.Open); const isOptionSelected: NamespaceSelectFilterModel["isOptionSelected"] = (option) => { if (option.value === selectAllNamespaces) { @@ -100,82 +122,66 @@ export function namespaceSelectFilterModelFor(dependencies: Dependencies): Names const model: NamespaceSelectFilterModel = { options, + filteredOptions, + selectedOptions, menu: { close: action(() => { menuState.set(SelectMenuState.Close); + filterText.set(""); }), open: action(() => { menuState.set(SelectMenuState.Open); }), + toggle: () => { + if (menuIsOpen.get()) { + model.menu.close(); + } else { + model.menu.open(); + } + }, isOpen: menuIsOpen, - }, - onChange: (_, action) => { - switch (action.action) { - case "clear": - namespaceStore.selectAll(); - break; - case "deselect-option": - case "select-option": - if (action.option) { - didToggle = true; - - if (action.option.value === selectAllNamespaces) { - namespaceStore.selectAll(); - } else if (isMultiSelection) { - namespaceStore.toggleSingle(action.option.value); - } else { - namespaceStore.selectSingle(action.option.value); - } + hasSelectedAll: computed(() => namespaceStore.areAllSelectedImplicitly), + onKeyDown: (event) => { + if (isMultiSelectionKey(event)) { + isMultiSelection = true; + } else if (event.key === "Escape") { + model.menu.close(); + } + }, + onKeyUp: (event) => { + if (isMultiSelectionKey(event)) { + isMultiSelection = false; + + if (didToggle) { + model.menu.close(); } - break; - } + } + }, }, - onClick: () => { - if (!menuIsOpen.get()) { - model.menu.open(); - } else if (!isMultiSelection) { + onClick: action((option) => { + if (option.value === selectAllNamespaces) { + namespaceStore.selectAll(); + model.menu.close(); + } else if (isMultiSelection) { + didToggle = true; + namespaceStore.toggleSingle(option.value); + } else { + namespaceStore.selectSingle(option.value); model.menu.close(); } - }, - onKeyDown: (event) => { - if (isMultiSelectionKey(event)) { - isMultiSelection = true; - } - }, - onKeyUp: (event) => { - if (isMultiSelectionKey(event)) { - isMultiSelection = false; - - if (didToggle) { - model.menu.close(); - } - } - }, + }), + deselect: action((namespace) => { + namespaceStore.deselectSingle(namespace); + }), + select: action((namespace) => { + namespaceStore.includeSingle(namespace); + }), + filterText, reset: action(() => { isMultiSelection = false; model.menu.close(); }), isOptionSelected, - formatOptionLabel: (option) => { - if (option.value === selectAllNamespaces) { - return <>All Namespaces; - } - - return ( -
- - {option.value} - {isOptionSelected(option) && ( - - )} -
- ); - }, }; return model; diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter.scss b/packages/core/src/renderer/components/namespaces/namespace-select-filter.scss index f227976b46a7..2eead588e132 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter.scss +++ b/packages/core/src/renderer/components/namespaces/namespace-select-filter.scss @@ -4,75 +4,86 @@ } } -.NamespaceSelectFilterParent { - max-width: 300px; -} +.namespace-select-filter { + width: 300px; + position: relative; + + .list-container { + position: absolute; + top: 30px; + z-index: 10; + background: var(--mainBackground); + border-radius: var(--border-radius); -.NamespaceSelectFilter { - --gradientColor: var(--select-menu-bgc); + li.option { + cursor: pointer; + padding: calc(var(--padding) / 2) var(--padding); + list-style: none; - .Select { - &__placeholder { - white-space: nowrap; - overflow: scroll hidden!important; - text-overflow: unset!important; - margin-left: -8px; - padding-left: 8px; - margin-right: -8px; - padding-right: 8px; - line-height: 1.1; + &:hover { + color: white; + } + + .selected-icon:hover { + color: var(--buttonAccentBackground); + } - &::-webkit-scrollbar { - display: none; + .add-selection-icon:hover { + color: var(--colorSuccess); } } + } + + .menu { + width: 300px; + border-radius: var(--border-radius); + border: 1px solid; + border-color: var(--halfGray); + padding: calc(var(--padding) / 2) var(--padding); + display: flex; - &__value-container { + .non-icon { + width: -webkit-fill-available; position: relative; - &::before, &::after { - content: ' '; - position: absolute; - z-index: 20; - display: block; - width: 8px; - height: var(--font-size); + input { + width: 100%; + background: transparent; } - &::before { - left: 0; - background: linear-gradient(to right, var(--gradientColor) 0px, transparent); - } + label { + white-space: nowrap; + overflow: scroll hidden!important; + text-overflow: unset!important; + margin-left: -16px; + padding-left: 8px; + padding-right: 8px; + width: calc(100% + 4px); + position: absolute; + left: 9px; - &::after { - right: 0; - background: linear-gradient(to left, var(--gradientColor) 0px, transparent); + &::-webkit-scrollbar { + display: none; + } } - } - } -} - -.NamespaceSelectFilterMenu { - right: 0; - .Select { - &__menu-list { - max-width: 400px; - } + .gradient { + position: absolute; + width: 10px; + top: 0; + height: 20px; + z-index: 21; - &__option { - white-space: normal; - word-break: break-all; - padding: 4px 8px; - border-radius: 3px; + &.left { + left: -7px; + background: linear-gradient(to right, var(--contentColor) 0%, rgba(255, 255, 255, 0) 100%); + } - &--is-selected:not(&--is-focused) { - background: transparent; + &.right { + right: 2px; + background: linear-gradient(to left, var(--contentColor) 0%, rgba(255, 255, 255, 0) 100%); + } } } } - - .Icon { - margin-right: $margin * 0.5; - } } diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx b/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx index de1c2a8cd663..b22ba43114c0 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx +++ b/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx @@ -5,16 +5,17 @@ import "./namespace-select-filter.scss"; -import React from "react"; +import React, { useEffect, useRef } from "react"; import { observer } from "mobx-react"; -import type { PlaceholderProps } from "react-select"; -import { components } from "react-select"; -import type { NamespaceStore } from "./store"; -import { Select } from "../select"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption, SelectAllNamespaces } from "./namespace-select-filter-model/namespace-select-filter-model"; +import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./namespace-select-filter-model/namespace-select-filter-model"; +import { selectAllNamespaces } from "./namespace-select-filter-model/namespace-select-filter-model"; import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable"; -import namespaceStoreInjectable from "./store.injectable"; +import { VariableSizeList } from "react-window"; +import { Icon } from "../icon"; +import { cssNames, prevDefault } from "@k8slens/utilities"; +import { addWindowEventListener } from "../../window/event-listener.injectable"; +import { TooltipPosition } from "@k8slens/tooltip"; interface NamespaceSelectFilterProps { id: string; @@ -24,72 +25,175 @@ interface Dependencies { model: NamespaceSelectFilterModel; } -const NonInjectedNamespaceSelectFilter = observer(({ model, id }: Dependencies & NamespaceSelectFilterProps) => ( -
- - id={id} - isMulti={true} - isClearable={false} - menuIsOpen={model.menu.isOpen.get()} - components={{ Placeholder }} - closeMenuOnSelect={false} - controlShouldRenderValue={false} - onChange={model.onChange} - onBlur={model.reset} - formatOptionLabel={model.formatOptionLabel} - options={model.options.get()} - className="NamespaceSelect NamespaceSelectFilter" - menuClass="NamespaceSelectFilterMenu" - isOptionSelected={model.isOptionSelected} - hideSelectedOptions={false} - /> -
-)); +const Gradient = ({ type }: { type: "left" | "right" }) => ( +
+); -export const NamespaceSelectFilter = withInjectables(NonInjectedNamespaceSelectFilter, { - getProps: (di, props) => ({ - model: di.inject(namespaceSelectFilterModelInjectable), - ...props, - }), +const NamespaceSelectFilterMenu = observer(({ id, model }: Dependencies & NamespaceSelectFilterProps) => { + const selectedOptions = model.selectedOptions.get(); + const prefix = selectedOptions.length === 1 + ? "Namespace" + : "Namespaces"; + + return ( +
+
+ model.filterText.set(event.target.value)} + onClick={model.menu.open} + /> + + + +
+ +
+ ); }); -export interface CustomPlaceholderProps extends PlaceholderProps {} +const rowHeight = 29; -interface PlaceholderDependencies { - namespaceStore: NamespaceStore; -} +const NonInjectedNamespaceSelectFilter = observer(({ model, id }: Dependencies & NamespaceSelectFilterProps) => { + const divRef = useRef(null); -const NonInjectedPlaceholder = observer(({ namespaceStore, ...props }: CustomPlaceholderProps & PlaceholderDependencies) => { - const getPlaceholder = () => { - const namespaces = namespaceStore.contextNamespaces; + useEffect(() => { + return addWindowEventListener("click", (event) => { + if (!model.menu.isOpen.get()) { + return; + } - if (namespaceStore.areAllSelectedImplicitly || namespaces.length === 0) { - return "All namespaces"; - } + if (divRef.current?.contains(event.target as Node)) { + return; + } - const prefix = namespaces.length === 1 - ? "Namespace" - : "Namespaces"; + model.menu.close(); + }); + }, []); - return `${prefix}: ${namespaces.join(", ")}`; + return ( +
+ + {model.menu.isOpen.get() && ( +
+ rowHeight} + itemCount={model.filteredOptions.get().length} + itemData={{ + items: model.filteredOptions.get(), + model, + }} + overscanCount={5} + innerElementType={"ul"} + > + {NamespaceSelectFilterRow} + +
+ )} +
+ ); +}); + +interface FilterRowProps { + index: number; + style: React.CSSProperties; + data: { + model: NamespaceSelectFilterModel; + items: NamespaceSelectFilterOption[]; }; +} + +const renderSingleOptionIcons = (namespace: string, option: NamespaceSelectFilterOption, model: NamespaceSelectFilterModel) => { + if (model.isOptionSelected(option)) { + return ( + model.deselect(namespace))} + /> + ); + } + + return ( + model.select(namespace))} + /> + ); +}; + +const NamespaceSelectFilterRow = observer(({ index, style, data: { model, items }}: FilterRowProps) => { + const option = items[index]; return ( - - {getPlaceholder()} - +
  • model.onClick(option)} + > + {option.value === selectAllNamespaces + ? All Namespaces + : ( + <> + model.onClick(option))} + tooltip={{ + preferredPositions: TooltipPosition.LEFT, + children: `Select only ${option.value}`, + }} + /> + {option.value} + {renderSingleOptionIcons(option.value, option, model)} + + )} +
  • ); }); -const Placeholder = withInjectables( NonInjectedPlaceholder, { +export const NamespaceSelectFilter = withInjectables(NonInjectedNamespaceSelectFilter, { getProps: (di, props) => ({ - namespaceStore: di.inject(namespaceStoreInjectable), + model: di.inject(namespaceSelectFilterModelInjectable), ...props, }), }); diff --git a/packages/core/src/renderer/components/namespaces/store.ts b/packages/core/src/renderer/components/namespaces/store.ts index 1bbb4446dd23..cce4a2e4c1e5 100644 --- a/packages/core/src/renderer/components/namespaces/store.ts +++ b/packages/core/src/renderer/components/namespaces/store.ts @@ -171,6 +171,20 @@ export class NamespaceStore extends KubeObjectStore { this.dependencies.storage.set([...nextState]); } + deselectSingle(namespace: string) { + const nextState = new Set(this.contextNamespaces); + + nextState.delete(namespace); + this.dependencies.storage.set([...nextState]); + } + + includeSingle(namespace: string) { + const nextState = new Set(this.contextNamespaces); + + nextState.add(namespace); + this.dependencies.storage.set([...nextState]); + } + /** * Makes the given namespace the sole selected namespace */ diff --git a/packages/core/src/renderer/window/event-listener.injectable.ts b/packages/core/src/renderer/window/event-listener.injectable.ts index 631fec116e1f..4be1022424f6 100644 --- a/packages/core/src/renderer/window/event-listener.injectable.ts +++ b/packages/core/src/renderer/window/event-listener.injectable.ts @@ -9,10 +9,10 @@ import type { Disposer } from "@k8slens/utilities"; export type AddWindowEventListener = typeof addWindowEventListener; export type WindowEventListener = (this: Window, ev: WindowEventMap[K]) => any; -function addWindowEventListener(type: K, listener: WindowEventListener, options?: boolean | AddEventListenerOptions): Disposer { +export function addWindowEventListener(type: K, listener: WindowEventListener, options?: boolean | AddEventListenerOptions): Disposer { window.addEventListener(type, listener, options); - return () => void window.removeEventListener(type, listener); + return () => void window.removeEventListener(type, listener, options); } const windowAddEventListenerInjectable = getInjectable({ diff --git a/packages/utility-features/utilities/src/type-narrowing.ts b/packages/utility-features/utilities/src/type-narrowing.ts index 68d526523768..8c545608df25 100644 --- a/packages/utility-features/utilities/src/type-narrowing.ts +++ b/packages/utility-features/utilities/src/type-narrowing.ts @@ -123,7 +123,8 @@ export function isDefined(val: T | undefined | null): val is T { return val != null; } -export function isFunction(val: unknown): val is (...args: unknown[]) => unknown { +// @ts-expect-error 2677 +export function isFunction(val: T): val is Extract extends never ? ((...args: unknown[]) => unknown) : Extract { return typeof val === "function"; } From 5811695e492492af23bae7ef5d0de7bb7c5edecb Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 24 May 2023 12:09:46 -0400 Subject: [PATCH 02/16] chore: Start moving namespace-select-filter to own folder Signed-off-by: Sebastian Malton --- .../src/extensions/renderer-api/components.ts | 2 +- .../config-vertical-pod-autoscalers/vpa.tsx | 4 ++-- .../components/helm-releases/releases.tsx | 2 +- .../kube-object-list-layout.tsx | 2 +- .../is-selection-key.injectable.ts | 2 +- .../namespace-select-filter-model.injectable.ts | 4 ++-- .../namespace-select-filter-model.tsx | 4 ++-- .../namespace-select-filter.scss | 0 .../namespace-select-filter.test.tsx | 4 ++-- .../namespace-select-filter.tsx | 6 +++--- .../filter-by-namespace.injectable.ts | 2 +- .../namespaces/namespace-select-badge.tsx | 17 ++++++----------- .../components/workloads-overview/overview.tsx | 2 +- 13 files changed, 23 insertions(+), 28 deletions(-) rename packages/core/src/renderer/components/{namespaces/namespace-select-filter-model => namespace-select-filter}/is-selection-key.injectable.ts (89%) rename packages/core/src/renderer/components/{namespaces/namespace-select-filter-model => namespace-select-filter}/namespace-select-filter-model.injectable.ts (85%) rename packages/core/src/renderer/components/{namespaces/namespace-select-filter-model => namespace-select-filter}/namespace-select-filter-model.tsx (97%) rename packages/core/src/renderer/components/{namespaces => namespace-select-filter}/namespace-select-filter.scss (100%) rename packages/core/src/renderer/components/{namespaces => namespace-select-filter}/namespace-select-filter.test.tsx (98%) rename packages/core/src/renderer/components/{namespaces => namespace-select-filter}/namespace-select-filter.tsx (97%) rename packages/core/src/renderer/components/namespaces/{namespace-select-filter-model => }/filter-by-namespace.injectable.ts (90%) diff --git a/packages/core/src/extensions/renderer-api/components.ts b/packages/core/src/extensions/renderer-api/components.ts index 36ce3bba0a64..594c2751b991 100644 --- a/packages/core/src/extensions/renderer-api/components.ts +++ b/packages/core/src/extensions/renderer-api/components.ts @@ -94,7 +94,7 @@ export * from "../../renderer/components/stepper"; export * from "../../renderer/components/wizard"; export * from "../../renderer/components/workloads-pods/pod-details-list"; export * from "../../renderer/components/namespaces/namespace-select"; -export * from "../../renderer/components/namespaces/namespace-select-filter"; +export * from "../../renderer/components/namespace-select-filter/namespace-select-filter"; export * from "../../renderer/components/layout/sub-title"; export * from "../../renderer/components/input/search-input"; export * from "../../renderer/components/chart/bar-chart"; diff --git a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa.tsx b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa.tsx index 242c66a7d247..9cffbd95acaf 100644 --- a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa.tsx +++ b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa.tsx @@ -14,9 +14,9 @@ import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; import type { VerticalPodAutoscalerStore } from "./store"; -import type { FilterByNamespace } from "../namespaces/namespace-select-filter-model/filter-by-namespace.injectable"; +import type { FilterByNamespace } from "../namespaces/filter-by-namespace.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; -import filterByNamespaceInjectable from "../namespaces/namespace-select-filter-model/filter-by-namespace.injectable"; +import filterByNamespaceInjectable from "../namespaces/filter-by-namespace.injectable"; import verticalPodAutoscalerStoreInjectable from "./store.injectable"; enum columnId { diff --git a/packages/core/src/renderer/components/helm-releases/releases.tsx b/packages/core/src/renderer/components/helm-releases/releases.tsx index 514e7593e7bb..b6a58155e157 100644 --- a/packages/core/src/renderer/components/helm-releases/releases.tsx +++ b/packages/core/src/renderer/components/helm-releases/releases.tsx @@ -11,7 +11,7 @@ import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-release import { withInjectables } from "@ogre-tools/injectable-react"; import type { ItemListStore } from "../item-object-list"; import { ItemListLayout } from "../item-object-list"; -import { NamespaceSelectFilter } from "../namespaces/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; import { kebabCase } from "lodash/fp"; import { HelmReleaseMenu } from "./release-menu"; import { ReleaseRollbackDialog } from "./dialog/dialog"; diff --git a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index 9280d55ac89e..52c79ccbe249 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -14,7 +14,7 @@ import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; import type { ItemListLayoutProps, ItemListStore } from "../item-object-list/list-layout"; import { ItemListLayout } from "../item-object-list/list-layout"; import { KubeObjectMenu } from "../kube-object-menu"; -import { NamespaceSelectFilter } from "../namespaces/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { Icon } from "@k8slens/icon"; import { TooltipPosition } from "@k8slens/tooltip"; diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/is-selection-key.injectable.ts b/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts similarity index 89% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter-model/is-selection-key.injectable.ts rename to packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts index 3562b8a1665b..28393ddc48fd 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/is-selection-key.injectable.ts +++ b/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type React from "react"; -import isMacInjectable from "../../../../common/vars/is-mac.injectable"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; export type IsMultiSelectionKey = (event: React.KeyboardEvent) => boolean; diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts similarity index 85% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts rename to packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts index d84292410b56..b73b339ff2bd 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.injectable.ts +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts @@ -4,9 +4,9 @@ */ import { namespaceSelectFilterModelFor } from "./namespace-select-filter-model"; import { getInjectable } from "@ogre-tools/injectable"; -import namespaceStoreInjectable from "../store.injectable"; +import namespaceStoreInjectable from "../namespaces/store.injectable"; import isMultiSelectionKeyInjectable from "./is-selection-key.injectable"; -import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const namespaceSelectFilterModelInjectable = getInjectable({ id: "namespace-select-filter-model", diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx similarity index 97% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx rename to packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx index 8a3cac363c63..ae56cdc40a4a 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/namespace-select-filter-model.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx @@ -5,10 +5,10 @@ import type React from "react"; import type { IComputedValue, IObservableValue } from "mobx"; import { observable, action, computed, comparer } from "mobx"; -import type { NamespaceStore } from "../store"; +import type { NamespaceStore } from "../namespaces/store"; import { observableCrate } from "@k8slens/utilities"; import type { IsMultiSelectionKey } from "./is-selection-key.injectable"; -import type { ClusterContext } from "../../../cluster-frame-context/cluster-frame-context"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; import GlobToRegExp from "glob-to-regexp"; interface Dependencies { diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter.scss b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.scss similarity index 100% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter.scss rename to packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.scss diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx similarity index 98% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter.test.tsx rename to packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx index a07bb6c981ac..c5567e2da658 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx @@ -24,8 +24,8 @@ import type { Disposer } from "@k8slens/utilities"; import { disposer } from "@k8slens/utilities"; import { renderFor } from "../test-utils/renderFor"; import { NamespaceSelectFilter } from "./namespace-select-filter"; -import type { NamespaceStore } from "./store"; -import namespaceStoreInjectable from "./store.injectable"; +import type { NamespaceStore } from "../namespaces/store"; +import namespaceStoreInjectable from "../namespaces/store.injectable"; function createNamespace(name: string): Namespace { return new Namespace({ diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx similarity index 97% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx rename to packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx index b22ba43114c0..884188507d53 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx @@ -8,9 +8,9 @@ import "./namespace-select-filter.scss"; import React, { useEffect, useRef } from "react"; import { observer } from "mobx-react"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./namespace-select-filter-model/namespace-select-filter-model"; -import { selectAllNamespaces } from "./namespace-select-filter-model/namespace-select-filter-model"; -import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable"; +import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./namespace-select-filter-model"; +import { selectAllNamespaces } from "./namespace-select-filter-model"; +import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model.injectable"; import { VariableSizeList } from "react-window"; import { Icon } from "../icon"; import { cssNames, prevDefault } from "@k8slens/utilities"; diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts b/packages/core/src/renderer/components/namespaces/filter-by-namespace.injectable.ts similarity index 90% rename from packages/core/src/renderer/components/namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts rename to packages/core/src/renderer/components/namespaces/filter-by-namespace.injectable.ts index fff1c37202f8..4848cb562460 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-filter-model/filter-by-namespace.injectable.ts +++ b/packages/core/src/renderer/components/namespaces/filter-by-namespace.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import namespaceStoreInjectable from "../store.injectable"; +import namespaceStoreInjectable from "./store.injectable"; export type FilterByNamespace = (namespace: string) => void; diff --git a/packages/core/src/renderer/components/namespaces/namespace-select-badge.tsx b/packages/core/src/renderer/components/namespaces/namespace-select-badge.tsx index 088cfe603e97..97020e02887a 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-select-badge.tsx +++ b/packages/core/src/renderer/components/namespaces/namespace-select-badge.tsx @@ -8,11 +8,8 @@ import React from "react"; import { withInjectables } from "@ogre-tools/injectable-react"; import type { BadgeProps } from "../badge"; import { Badge } from "../badge"; -import type { - FilterByNamespace, -} from "./namespace-select-filter-model/filter-by-namespace.injectable"; -import filterByNamespaceInjectable - from "./namespace-select-filter-model/filter-by-namespace.injectable"; +import type { FilterByNamespace } from "./filter-by-namespace.injectable"; +import filterByNamespaceInjectable from "./filter-by-namespace.injectable"; import { prevDefault, cssNames } from "@k8slens/utilities"; export interface NamespaceSelectBadgeProps extends BadgeProps { @@ -49,10 +46,8 @@ export function NamespaceSelectBadgeNonInjected( } export const NamespaceSelectBadge = withInjectables(NamespaceSelectBadgeNonInjected, { - getProps(di, props) { - return { - ...props, - filterByNamespace: di.inject(filterByNamespaceInjectable), - }; - }, + getProps: (di, props) => ({ + ...props, + filterByNamespace: di.inject(filterByNamespaceInjectable), + }), }); diff --git a/packages/core/src/renderer/components/workloads-overview/overview.tsx b/packages/core/src/renderer/components/workloads-overview/overview.tsx index 3ea0a8c2d71a..39d4601fefa4 100644 --- a/packages/core/src/renderer/components/workloads-overview/overview.tsx +++ b/packages/core/src/renderer/components/workloads-overview/overview.tsx @@ -13,7 +13,7 @@ import type { JobStore } from "../workloads-jobs/store"; import type { CronJobStore } from "../workloads-cronjobs/store"; import type { IComputedValue } from "mobx"; import { makeObservable, observable, reaction } from "mobx"; -import { NamespaceSelectFilter } from "../namespaces/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; import { Icon } from "@k8slens/icon"; import { TooltipPosition } from "@k8slens/tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; From 8fc9fc7a6663f527446050da47a71fbed313f285 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 24 May 2023 12:10:27 -0400 Subject: [PATCH 03/16] chore: Refactor isSelectionKey Signed-off-by: Sebastian Malton --- .../namespace-select-filter/is-selection-key.injectable.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts b/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts index 28393ddc48fd..25779bcc521c 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts +++ b/packages/core/src/renderer/components/namespace-select-filter/is-selection-key.injectable.ts @@ -12,10 +12,9 @@ const isMultiSelectionKeyInjectable = getInjectable({ id: "is-multi-selection-key", instantiate: (di): IsMultiSelectionKey => { const isMac = di.inject(isMacInjectable); + const specificKey = isMac ? "Meta" : "Control"; - return isMac - ? ({ key }) => key === "Meta" - : ({ key }) => key === "Control"; + return ({ key }) => key === specificKey; }, }); From b619eb56d8c3e178b78973363613d128e7a656d5 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 24 May 2023 12:13:19 -0400 Subject: [PATCH 04/16] chore: Consolidate namespace-select-filter model into single file Signed-off-by: Sebastian Malton --- .../model.injectable.ts | 191 ++++++++++++++++++ ...amespace-select-filter-model.injectable.ts | 21 -- .../namespace-select-filter-model.tsx | 188 ----------------- .../namespace-select-filter.tsx | 5 +- 4 files changed, 193 insertions(+), 212 deletions(-) create mode 100644 packages/core/src/renderer/components/namespace-select-filter/model.injectable.ts delete mode 100644 packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts delete mode 100644 packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx diff --git a/packages/core/src/renderer/components/namespace-select-filter/model.injectable.ts b/packages/core/src/renderer/components/namespace-select-filter/model.injectable.ts new file mode 100644 index 000000000000..7594de276fe4 --- /dev/null +++ b/packages/core/src/renderer/components/namespace-select-filter/model.injectable.ts @@ -0,0 +1,191 @@ +/** + * 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 namespaceStoreInjectable from "../namespaces/store.injectable"; +import isMultiSelectionKeyInjectable from "./is-selection-key.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; +import type { IComputedValue, IObservableValue } from "mobx"; +import { action, comparer, computed, observable } from "mobx"; +import GlobToRegExp from "glob-to-regexp"; +import { observableCrate } from "@k8slens/utilities"; + + +export const selectAllNamespaces = Symbol("all-namespaces-selected"); + +export type SelectAllNamespaces = typeof selectAllNamespaces; +export interface NamespaceSelectFilterOption { + value: string | SelectAllNamespaces; + label: string; + id: string | SelectAllNamespaces; +} + +export interface NamespaceSelectFilterModel { + readonly options: IComputedValue; + readonly filteredOptions: IComputedValue; + readonly selectedOptions: IComputedValue; + readonly menu: { + open: () => void; + close: () => void; + toggle: () => void; + readonly isOpen: IComputedValue; + readonly hasSelectedAll: IComputedValue; + onKeyDown: React.KeyboardEventHandler; + onKeyUp: React.KeyboardEventHandler; + }; + onClick: (options: NamespaceSelectFilterOption) => void; + deselect: (namespace: string) => void; + select: (namespace: string) => void; + readonly filterText: IObservableValue; + reset: () => void; + isOptionSelected: (option: NamespaceSelectFilterOption) => boolean; +} + +enum SelectMenuState { + Close = "close", + Open = "open", +} + +const filterBasedOnText = (filterText: string) => { + const regexp = new RegExp(GlobToRegExp(filterText, { extended: true, flags: "gi" })); + + return (options: NamespaceSelectFilterOption) => { + if (options.value === selectAllNamespaces) { + return true; + } + + return Boolean(options.value.match(regexp)); + }; +}; + +const namespaceSelectFilterModelInjectable = getInjectable({ + id: "namespace-select-filter-model", + + instantiate: (di) => { + const namespaceStore = di.inject(namespaceStoreInjectable); + const isMultiSelectionKey = di.inject(isMultiSelectionKeyInjectable); + const context = di.inject(clusterFrameContextForNamespacedResourcesInjectable); + + let didToggle = false; + let isMultiSelection = false; + const menuState = observableCrate(SelectMenuState.Close, [{ + from: SelectMenuState.Close, + to: SelectMenuState.Open, + onTransition: () => { + optionsSortingSelected.replace(selectedNames.get()); + didToggle = false; + }, + }]); + const filterText = observable.box(""); + const selectedNames = computed(() => new Set(context.contextNamespaces), { + equals: comparer.structural, + }); + const optionsSortingSelected = observable.set(selectedNames.get()); + const sortNamespacesByIfTheyHaveBeenSelected = (left: string, right: string) => { + const isLeftSelected = optionsSortingSelected.has(left); + const isRightSelected = optionsSortingSelected.has(right); + + if (isLeftSelected === isRightSelected) { + return 0; + } + + return isRightSelected + ? 1 + : -1; + }; + const options = computed((): NamespaceSelectFilterOption[] => [ + { + value: selectAllNamespaces, + label: "All Namespaces", + id: "all-namespaces", + }, + ...context + .allNamespaces + .sort(sortNamespacesByIfTheyHaveBeenSelected) + .map(namespace => ({ + value: namespace, + label: namespace, + id: namespace, + })), + ]); + const filteredOptions = computed(() => options.get().filter(filterBasedOnText(filterText.get()))); + const selectedOptions = computed(() => options.get().filter(model.isOptionSelected)); + const menuIsOpen = computed(() => menuState.get() === SelectMenuState.Open); + const isOptionSelected: NamespaceSelectFilterModel["isOptionSelected"] = (option) => { + if (option.value === selectAllNamespaces) { + return false; + } + + return selectedNames.get().has(option.value); + }; + + const model: NamespaceSelectFilterModel = { + options, + filteredOptions, + selectedOptions, + menu: { + close: action(() => { + menuState.set(SelectMenuState.Close); + filterText.set(""); + }), + open: action(() => { + menuState.set(SelectMenuState.Open); + }), + toggle: () => { + if (menuIsOpen.get()) { + model.menu.close(); + } else { + model.menu.open(); + } + }, + isOpen: menuIsOpen, + hasSelectedAll: computed(() => namespaceStore.areAllSelectedImplicitly), + onKeyDown: (event) => { + if (isMultiSelectionKey(event)) { + isMultiSelection = true; + } else if (event.key === "Escape") { + model.menu.close(); + } + }, + onKeyUp: (event) => { + if (isMultiSelectionKey(event)) { + isMultiSelection = false; + + if (didToggle) { + model.menu.close(); + } + } + }, + }, + onClick: action((option) => { + if (option.value === selectAllNamespaces) { + namespaceStore.selectAll(); + model.menu.close(); + } else if (isMultiSelection) { + didToggle = true; + namespaceStore.toggleSingle(option.value); + } else { + namespaceStore.selectSingle(option.value); + model.menu.close(); + } + }), + deselect: action((namespace) => { + namespaceStore.deselectSingle(namespace); + }), + select: action((namespace) => { + namespaceStore.includeSingle(namespace); + }), + filterText, + reset: action(() => { + isMultiSelection = false; + model.menu.close(); + }), + isOptionSelected, + }; + + return model; + }, +}); + +export default namespaceSelectFilterModelInjectable; diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts deleted file mode 100644 index b73b339ff2bd..000000000000 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { namespaceSelectFilterModelFor } from "./namespace-select-filter-model"; -import { getInjectable } from "@ogre-tools/injectable"; -import namespaceStoreInjectable from "../namespaces/store.injectable"; -import isMultiSelectionKeyInjectable from "./is-selection-key.injectable"; -import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; - -const namespaceSelectFilterModelInjectable = getInjectable({ - id: "namespace-select-filter-model", - - instantiate: (di) => namespaceSelectFilterModelFor({ - namespaceStore: di.inject(namespaceStoreInjectable), - isMultiSelectionKey: di.inject(isMultiSelectionKeyInjectable), - context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), - }), -}); - -export default namespaceSelectFilterModelInjectable; diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx deleted file mode 100644 index ae56cdc40a4a..000000000000 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter-model.tsx +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type React from "react"; -import type { IComputedValue, IObservableValue } from "mobx"; -import { observable, action, computed, comparer } from "mobx"; -import type { NamespaceStore } from "../namespaces/store"; -import { observableCrate } from "@k8slens/utilities"; -import type { IsMultiSelectionKey } from "./is-selection-key.injectable"; -import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; -import GlobToRegExp from "glob-to-regexp"; - -interface Dependencies { - context: ClusterContext; - namespaceStore: NamespaceStore; - isMultiSelectionKey: IsMultiSelectionKey; -} - -export const selectAllNamespaces = Symbol("all-namespaces-selected"); - -export type SelectAllNamespaces = typeof selectAllNamespaces; -export interface NamespaceSelectFilterOption { - value: string | SelectAllNamespaces; - label: string; - id: string | SelectAllNamespaces; -} - -export interface NamespaceSelectFilterModel { - readonly options: IComputedValue; - readonly filteredOptions: IComputedValue; - readonly selectedOptions: IComputedValue; - readonly menu: { - open: () => void; - close: () => void; - toggle: () => void; - readonly isOpen: IComputedValue; - readonly hasSelectedAll: IComputedValue; - onKeyDown: React.KeyboardEventHandler; - onKeyUp: React.KeyboardEventHandler; - }; - onClick: (options: NamespaceSelectFilterOption) => void; - deselect: (namespace: string) => void; - select: (namespace: string) => void; - readonly filterText: IObservableValue; - reset: () => void; - isOptionSelected: (option: NamespaceSelectFilterOption) => boolean; -} - -enum SelectMenuState { - Close = "close", - Open = "open", -} - -const filterBasedOnText = (filterText: string) => { - const regexp = new RegExp(GlobToRegExp(filterText, { extended: true, flags: "gi" })); - - return (options: NamespaceSelectFilterOption) => { - if (options.value === selectAllNamespaces) { - return true; - } - - return Boolean(options.value.match(regexp)); - }; -}; - -export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel { - const { isMultiSelectionKey, namespaceStore, context } = dependencies; - - let didToggle = false; - let isMultiSelection = false; - const menuState = observableCrate(SelectMenuState.Close, [{ - from: SelectMenuState.Close, - to: SelectMenuState.Open, - onTransition: () => { - optionsSortingSelected.replace(selectedNames.get()); - didToggle = false; - }, - }]); - const filterText = observable.box(""); - const selectedNames = computed(() => new Set(context.contextNamespaces), { - equals: comparer.structural, - }); - const optionsSortingSelected = observable.set(selectedNames.get()); - const sortNamespacesByIfTheyHaveBeenSelected = (left: string, right: string) => { - const isLeftSelected = optionsSortingSelected.has(left); - const isRightSelected = optionsSortingSelected.has(right); - - if (isLeftSelected === isRightSelected) { - return 0; - } - - return isRightSelected - ? 1 - : -1; - }; - const options = computed((): NamespaceSelectFilterOption[] => [ - { - value: selectAllNamespaces, - label: "All Namespaces", - id: "all-namespaces", - }, - ...context - .allNamespaces - .sort(sortNamespacesByIfTheyHaveBeenSelected) - .map(namespace => ({ - value: namespace, - label: namespace, - id: namespace, - })), - ]); - const filteredOptions = computed(() => options.get().filter(filterBasedOnText(filterText.get()))); - const selectedOptions = computed(() => options.get().filter(model.isOptionSelected)); - const menuIsOpen = computed(() => menuState.get() === SelectMenuState.Open); - const isOptionSelected: NamespaceSelectFilterModel["isOptionSelected"] = (option) => { - if (option.value === selectAllNamespaces) { - return false; - } - - return selectedNames.get().has(option.value); - }; - - const model: NamespaceSelectFilterModel = { - options, - filteredOptions, - selectedOptions, - menu: { - close: action(() => { - menuState.set(SelectMenuState.Close); - filterText.set(""); - }), - open: action(() => { - menuState.set(SelectMenuState.Open); - }), - toggle: () => { - if (menuIsOpen.get()) { - model.menu.close(); - } else { - model.menu.open(); - } - }, - isOpen: menuIsOpen, - hasSelectedAll: computed(() => namespaceStore.areAllSelectedImplicitly), - onKeyDown: (event) => { - if (isMultiSelectionKey(event)) { - isMultiSelection = true; - } else if (event.key === "Escape") { - model.menu.close(); - } - }, - onKeyUp: (event) => { - if (isMultiSelectionKey(event)) { - isMultiSelection = false; - - if (didToggle) { - model.menu.close(); - } - } - }, - }, - onClick: action((option) => { - if (option.value === selectAllNamespaces) { - namespaceStore.selectAll(); - model.menu.close(); - } else if (isMultiSelection) { - didToggle = true; - namespaceStore.toggleSingle(option.value); - } else { - namespaceStore.selectSingle(option.value); - model.menu.close(); - } - }), - deselect: action((namespace) => { - namespaceStore.deselectSingle(namespace); - }), - select: action((namespace) => { - namespaceStore.includeSingle(namespace); - }), - filterText, - reset: action(() => { - isMultiSelection = false; - model.menu.close(); - }), - isOptionSelected, - }; - - return model; -} diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx index 884188507d53..56c45cea5a40 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx @@ -8,9 +8,8 @@ import "./namespace-select-filter.scss"; import React, { useEffect, useRef } from "react"; import { observer } from "mobx-react"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./namespace-select-filter-model"; -import { selectAllNamespaces } from "./namespace-select-filter-model"; -import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model.injectable"; +import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./model.injectable"; +import namespaceSelectFilterModelInjectable, { selectAllNamespaces } from "./model.injectable"; import { VariableSizeList } from "react-window"; import { Icon } from "../icon"; import { cssNames, prevDefault } from "@k8slens/utilities"; From ce11ef0a2e5fc054c3b856232bb6514447d7d442 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 24 May 2023 12:13:51 -0400 Subject: [PATCH 05/16] chore: Rename namespace-select-filter component file Signed-off-by: Sebastian Malton --- packages/core/src/extensions/renderer-api/components.ts | 2 +- .../core/src/renderer/components/helm-releases/releases.tsx | 2 +- .../kube-object-list-layout/kube-object-list-layout.tsx | 2 +- .../{namespace-select-filter.tsx => component.tsx} | 0 .../namespace-select-filter/namespace-select-filter.test.tsx | 2 +- .../src/renderer/components/workloads-overview/overview.tsx | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/core/src/renderer/components/namespace-select-filter/{namespace-select-filter.tsx => component.tsx} (100%) diff --git a/packages/core/src/extensions/renderer-api/components.ts b/packages/core/src/extensions/renderer-api/components.ts index 594c2751b991..2d1a89112d2b 100644 --- a/packages/core/src/extensions/renderer-api/components.ts +++ b/packages/core/src/extensions/renderer-api/components.ts @@ -94,7 +94,7 @@ export * from "../../renderer/components/stepper"; export * from "../../renderer/components/wizard"; export * from "../../renderer/components/workloads-pods/pod-details-list"; export * from "../../renderer/components/namespaces/namespace-select"; -export * from "../../renderer/components/namespace-select-filter/namespace-select-filter"; +export * from "../../renderer/components/namespace-select-filter/component"; export * from "../../renderer/components/layout/sub-title"; export * from "../../renderer/components/input/search-input"; export * from "../../renderer/components/chart/bar-chart"; diff --git a/packages/core/src/renderer/components/helm-releases/releases.tsx b/packages/core/src/renderer/components/helm-releases/releases.tsx index b6a58155e157..9a31a9e10b77 100644 --- a/packages/core/src/renderer/components/helm-releases/releases.tsx +++ b/packages/core/src/renderer/components/helm-releases/releases.tsx @@ -11,7 +11,7 @@ import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-release import { withInjectables } from "@ogre-tools/injectable-react"; import type { ItemListStore } from "../item-object-list"; import { ItemListLayout } from "../item-object-list"; -import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/component"; import { kebabCase } from "lodash/fp"; import { HelmReleaseMenu } from "./release-menu"; import { ReleaseRollbackDialog } from "./dialog/dialog"; diff --git a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index 52c79ccbe249..72e393fe73b7 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -14,7 +14,7 @@ import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; import type { ItemListLayoutProps, ItemListStore } from "../item-object-list/list-layout"; import { ItemListLayout } from "../item-object-list/list-layout"; import { KubeObjectMenu } from "../kube-object-menu"; -import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/component"; import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { Icon } from "@k8slens/icon"; import { TooltipPosition } from "@k8slens/tooltip"; diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx b/packages/core/src/renderer/components/namespace-select-filter/component.tsx similarity index 100% rename from packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.tsx rename to packages/core/src/renderer/components/namespace-select-filter/component.tsx diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx index c5567e2da658..6f24d416ce59 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx @@ -23,7 +23,7 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create import type { Disposer } from "@k8slens/utilities"; import { disposer } from "@k8slens/utilities"; import { renderFor } from "../test-utils/renderFor"; -import { NamespaceSelectFilter } from "./namespace-select-filter"; +import { NamespaceSelectFilter } from "./component"; import type { NamespaceStore } from "../namespaces/store"; import namespaceStoreInjectable from "../namespaces/store.injectable"; diff --git a/packages/core/src/renderer/components/workloads-overview/overview.tsx b/packages/core/src/renderer/components/workloads-overview/overview.tsx index 39d4601fefa4..69c51d214efc 100644 --- a/packages/core/src/renderer/components/workloads-overview/overview.tsx +++ b/packages/core/src/renderer/components/workloads-overview/overview.tsx @@ -13,7 +13,7 @@ import type { JobStore } from "../workloads-jobs/store"; import type { CronJobStore } from "../workloads-cronjobs/store"; import type { IComputedValue } from "mobx"; import { makeObservable, observable, reaction } from "mobx"; -import { NamespaceSelectFilter } from "../namespace-select-filter/namespace-select-filter"; +import { NamespaceSelectFilter } from "../namespace-select-filter/component"; import { Icon } from "@k8slens/icon"; import { TooltipPosition } from "@k8slens/tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; From fa7bc2ee6bf2ce0c08d0187d00095954abb1e8e9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 24 May 2023 14:07:49 -0400 Subject: [PATCH 06/16] chore: Fixup previous namespace-select-filter unit tests Signed-off-by: Sebastian Malton --- .../namespace-select-filter.test.tsx.snap | 2014 +++++++++++++++++ .../namespace-select-filter/component.tsx | 3 + .../namespace-select-filter.test.tsx | 54 +- .../namespace-select-filter.test.tsx.snap | 1698 -------------- 4 files changed, 2045 insertions(+), 1724 deletions(-) create mode 100644 packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap delete mode 100644 packages/core/src/renderer/components/namespaces/__snapshots__/namespace-select-filter.test.tsx.snap diff --git a/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap new file mode 100644 index 000000000000..b8662b524769 --- /dev/null +++ b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap @@ -0,0 +1,2014 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` once the subscribe resolves renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when 'test-2' is clicked renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = ` + +
    +
    + + +`; diff --git a/packages/core/src/renderer/components/namespace-select-filter/component.tsx b/packages/core/src/renderer/components/namespace-select-filter/component.tsx index 56c45cea5a40..379232b581ed 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/component.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/component.tsx @@ -43,6 +43,7 @@ const NamespaceSelectFilterMenu = observer(({ id, model }: Dependencies & Namesp value={model.filterText.get()} onChange={(event) => model.filterText.set(event.target.value)} onClick={model.menu.open} + data-testid="namespace-select-filter-input" />
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-1
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-10
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-11
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-12
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-13
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-2
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-3
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-4
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-5
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-6
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-7
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-8
    once the subscribe resolves when clicked rend > once the subscribe resolves when clicked rend layers -
    +
    Select only test-9
    once the subscribe resolves when clicked when
    once the subscribe resolves when clicked when
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-2
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-1
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-10
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-11
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-12
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-13
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-3
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-4
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-5
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-6
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-7
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-8
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-9
    once the subscribe resolves when clicked when
    once the subscribe resolves when clicked when
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-1
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-2
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-10
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-11
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-12
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-13
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-3
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-4
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-5
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-6
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-7
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-8
    once the subscribe resolves when clicked when > once the subscribe resolves when clicked when layers -
    +
    Select only test-9
    once the subscribe resolves when clicked when
    `; + +exports[` once the subscribe resolves when clicked when clicked again renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when clicking the remove from selection button for 'test-2' renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when clicking the remove from selection button for 'test-2' when clicking the remove from selection button for 'test-3' renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when clicking the remove from selection button for 'test-2' when clicking the remove from selection button for 'test-3' when clicking the add to selection button for 'test-2' renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when clicking the remove from selection button for 'test-2' when clicking the remove from selection button for 'test-3' when clicking the add to selection button for 'test-2' when clicking the 'select only' button for 'test-5' renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when typing a glob style filter into the filter input renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when clicked when typing in the filter input renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when menu expand icon is clicked renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves when menu expand icon is clicked when menu expand icon is clicked again renders 1`] = ` + +
    +
    + + +`; diff --git a/packages/core/src/renderer/components/namespace-select-filter/component.tsx b/packages/core/src/renderer/components/namespace-select-filter/component.tsx index 379232b581ed..1fa716ce656c 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/component.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/component.tsx @@ -62,6 +62,7 @@ const NamespaceSelectFilterMenu = observer(({ id, model }: Dependencies & Namesp
    @@ -184,6 +185,7 @@ const NamespaceSelectFilterRow = observer(({ index, style, data: { model, items preferredPositions: TooltipPosition.LEFT, children: `Select only ${option.value}`, }} + data-testid={`namespace-select-filter-option-${option.value}-select-only`} /> {option.value} {renderSingleOptionIcons(option.value, option, model)} diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx index 5b1ea870d84f..8772ece9f1c8 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx @@ -26,6 +26,7 @@ import { renderFor } from "../test-utils/renderFor"; import { NamespaceSelectFilter } from "./component"; import type { NamespaceStore } from "../namespaces/store"; import namespaceStoreInjectable from "../namespaces/store.injectable"; +import userEvent from "@testing-library/user-event"; function createNamespace(name: string): Namespace { return new Namespace({ @@ -111,6 +112,34 @@ describe("", () => { expect(result.baseElement).toMatchSnapshot(); }); + describe("when menu expand icon is clicked", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-expand-icon").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + describe("when menu expand icon is clicked again", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-expand-icon").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is closed", () => { + expect(result.queryByTestId("namespace-select-filter-list-container")).not.toBeInTheDocument(); + }); + }); + }); + describe("when clicked", () => { beforeEach(() => { result.getByTestId("namespace-select-filter-input").click(); @@ -121,7 +150,11 @@ describe("", () => { }); it("opens menu", () => { - expect(result.getByTestId("namespace-select-filter-list-container")).not.toBeNull(); + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + it("has all namespaces selected in the store", () => { + expect(namespaceStore.contextNamespaces.length).toBe(13); }); describe("when 'test-2' is clicked", () => { @@ -151,7 +184,7 @@ describe("", () => { }); it("shows 'test-2' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull(); + expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).toBeInTheDocument(); }); it("does not show 'test-1' as selected", () => { @@ -197,7 +230,7 @@ describe("", () => { }); it("keeps menu open", () => { - expect(result.getByTestId("namespace-select-filter-list-container")).not.toBeNull(); + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); }); it("does not show 'kube-system' as selected", () => { @@ -214,7 +247,7 @@ describe("", () => { }); it("keeps menu open", () => { - expect(result.getByTestId("namespace-select-filter-list-container")).not.toBeNull(); + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); }); it("'test-13' is not sorted to the top of the list", () => { @@ -243,7 +276,7 @@ describe("", () => { }); it("keeps menu open", () => { - expect(result.getByTestId("namespace-select-filter-list-container")).not.toBeNull(); + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); }); }); }); @@ -294,6 +327,156 @@ describe("", () => { }); }); }); + + describe("when clicked again", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-input").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + }); + + describe("when typing in the filter input", () => { + beforeEach(() => { + userEvent.type(result.getByTestId("namespace-select-filter-input"), "1"); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + it("shows 'test-1' option", () => { + expect(result.getByTestId("namespace-select-filter-option-test-1")).toBeInTheDocument(); + }); + + it("shows 'test-10' option", () => { + expect(result.getByTestId("namespace-select-filter-option-test-10")).toBeInTheDocument(); + }); + + it("does not show 'test-2' option", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-2")).not.toBeInTheDocument(); + }); + }); + + describe("when typing a glob style filter into the filter input", () => { + beforeEach(() => { + userEvent.type(result.getByTestId("namespace-select-filter-input"), "1*"); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + it("shows 'test-1' option", () => { + expect(result.getByTestId("namespace-select-filter-option-test-1")).toBeInTheDocument(); + }); + + it("shows 'test-10' option", () => { + expect(result.getByTestId("namespace-select-filter-option-test-10")).toBeInTheDocument(); + }); + + it("does not show 'test-2' option", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-2")).not.toBeInTheDocument(); + }); + }); + + describe("when clicking the remove from selection button for 'test-2'", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-option-test-2-selected").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("shows an 'add to selection' button for 'test-2'", () => { + expect(result.getByTestId("namespace-select-filter-option-test-2-add-to-selection")).toBeInTheDocument(); + }); + + it("does not have 'test-2' as selected in the store", () => { + expect(namespaceStore.contextNamespaces.includes("test-2")).toBe(false); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + describe("when clicking the remove from selection button for 'test-3'", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-option-test-3-selected").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("shows an 'add to selection' button for 'test-3'", () => { + expect(result.getByTestId("namespace-select-filter-option-test-3-add-to-selection")).toBeInTheDocument(); + }); + + it("does not have 'test-3' as selected in the store", () => { + expect(namespaceStore.contextNamespaces.includes("test-3")).toBe(false); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + describe("when clicking the add to selection button for 'test-2'", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-option-test-2-add-to-selection").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("shows a 'remove from selection' button for 'test-2'", () => { + expect(result.getByTestId("namespace-select-filter-option-test-2-selected")).toBeInTheDocument(); + }); + + it("does have 'test-2' as selected in the store", () => { + expect(namespaceStore.contextNamespaces.includes("test-2")).toBe(true); + }); + + it("menu is still open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + describe("when clicking the 'select only' button for 'test-5'", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-option-test-5-select-only").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("only has 'test-5' as selected in the store", () => { + expect(namespaceStore.contextNamespaces).toEqual(["test-5"]); + }); + + it("menu is now closed", () => { + expect(result.queryByTestId("namespace-select-filter-list-container")).not.toBeInTheDocument(); + }); + }); + }); + }); + }); }); }); }); From e528c729bdc7c16ffe5ad02d210895c426797e94 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 25 May 2023 11:25:45 -0400 Subject: [PATCH 08/16] chore: Add tests for large number of namespaces Signed-off-by: Sebastian Malton --- .../namespace-select-filter.test.tsx.snap | 853 ++++++++++++++++++ .../namespace-select-filter.test.tsx | 55 +- 2 files changed, 907 insertions(+), 1 deletion(-) diff --git a/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap index b06a45557f27..83f846442ef4 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap +++ b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap @@ -6144,3 +6144,856 @@ exports[` once the subscribe resolves when menu expand
    `; + +exports[` once the subscribe resolves with thousands of namespaces renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves with thousands of namespaces when menu expand icon is clicked renders 1`] = ` + +
    +
    + + +`; + +exports[` once the subscribe resolves with thousands of namespaces when menu expand icon is clicked when menu expand icon is clicked again renders 1`] = ` + +
    +
    + + +`; diff --git a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx index 8772ece9f1c8..4eb2ec4b5392 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/namespace-select-filter.test.tsx @@ -21,7 +21,7 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import type { Disposer } from "@k8slens/utilities"; -import { disposer } from "@k8slens/utilities"; +import { array, disposer } from "@k8slens/utilities"; import { renderFor } from "../test-utils/renderFor"; import { NamespaceSelectFilter } from "./component"; import type { NamespaceStore } from "../namespaces/store"; @@ -479,4 +479,57 @@ describe("", () => { }); }); }); + + describe("once the subscribe resolves with thousands of namespaces", () => { + beforeEach(async () => { + await fetchMock.resolveSpecific([ + "https://127.0.0.1:12345/api-kube/api/v1/namespaces", + ], createMockResponseFromString("https://127.0.0.1:12345/api-kube/api/v1/namespaces", JSON.stringify({ + apiVersion: "v1", + kind: "NamespaceList", + metadata: {}, + items: array.filled(20000, undefined).map((_, i) => createNamespace(`test-${i}`)), + }))); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + describe("when menu expand icon is clicked", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-expand-icon").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is open", () => { + expect(result.getByTestId("namespace-select-filter-list-container")).toBeInTheDocument(); + }); + + it("does not show all items in the DOM", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-1500")).not.toBeInTheDocument(); + }); + + it("does show some items in the DOM", () => { + expect(result.getByTestId("namespace-select-filter-option-test-10")).toBeInTheDocument(); + }); + + describe("when menu expand icon is clicked again", () => { + beforeEach(() => { + result.getByTestId("namespace-select-filter-expand-icon").click(); + }); + + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("menu is closed", () => { + expect(result.queryByTestId("namespace-select-filter-list-container")).not.toBeInTheDocument(); + }); + }); + }); + }); }); From 839fa64f7df366b293fd70262598d4866b05f22f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 26 May 2023 08:41:13 -0400 Subject: [PATCH 09/16] chore: Update snapshots due to new behaviour of namespace-select-filter Signed-off-by: Sebastian Malton --- ...ing-cluster-frame-components.test.tsx.snap | 97 +- ...-and-tab-navigation-for-core.test.tsx.snap | 485 ++--- ...ab-navigation-for-extensions.test.tsx.snap | 485 ++--- .../visibility-of-sidebar-items.test.tsx.snap | 194 +- .../workload-overview.test.tsx.snap | 97 +- ...when-cluster-is-not-relevant.test.tsx.snap | 194 +- ...when-cluster-is-not-relevant.test.tsx.snap | 291 +-- ...when-cluster-is-not-relevant.test.tsx.snap | 291 +-- ...hide-kube-object-detail-item.test.tsx.snap | 194 +- ...e-from-previously-opened-tab.test.tsx.snap | 194 +- .../__snapshots__/pods.test.tsx.snap | 291 +-- ...when-cluster-is-not-relevant.test.tsx.snap | 291 +-- ...lling-helm-chart-from-new-tab.test.ts.snap | 194 +- ...rt-from-previously-opened-tab.test.ts.snap | 194 +- .../upgrade-chart-new-tab.test.ts.snap | 679 ++----- ...wing-details-for-helm-release.test.ts.snap | 1746 ++++++----------- .../__snapshots__/download-logs.test.tsx.snap | 194 +- .../kube-object-list-layout.test.tsx.snap | 97 +- .../__snapshots__/cluster-frame.test.tsx.snap | 97 +- 19 files changed, 2015 insertions(+), 4290 deletions(-) diff --git a/packages/core/src/features/cluster/__snapshots__/legacy-extension-adding-cluster-frame-components.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/legacy-extension-adding-cluster-frame-components.test.tsx.snap index cfcea5deb305..e4a6cba0cbe1 100644 --- a/packages/core/src/features/cluster/__snapshots__/legacy-extension-adding-cluster-frame-components.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/legacy-extension-adding-cluster-frame-components.test.tsx.snap @@ -293,82 +293,47 @@ exports[`legacy extension adding cluster frame components given custom component Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-core.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-core.test.tsx.snap index a27c6874174c..92b2def3c7f0 100644 --- a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-core.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-core.test.tsx.snap @@ -322,82 +322,47 @@ exports[`cluster - sidebar and tab navigation for core given core registrations Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -866,82 +831,47 @@ exports[`cluster - sidebar and tab navigation for core given core registrations Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1430,82 +1360,47 @@ exports[`cluster - sidebar and tab navigation for core given core registrations Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -2857,82 +2752,47 @@ exports[`cluster - sidebar and tab navigation for core given core registrations Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -3401,82 +3261,47 @@ exports[`cluster - sidebar and tab navigation for core given core registrations Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap index a64f9c2aa273..1f7d7bf3feec 100644 --- a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap @@ -322,82 +322,47 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -866,82 +831,47 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1446,82 +1376,47 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -3486,82 +3381,47 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -4030,82 +3890,47 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/__snapshots__/visibility-of-sidebar-items.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/visibility-of-sidebar-items.test.tsx.snap index 85601255c3c7..95428c026a9e 100644 --- a/packages/core/src/features/cluster/__snapshots__/visibility-of-sidebar-items.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/visibility-of-sidebar-items.test.tsx.snap @@ -293,82 +293,47 @@ exports[`cluster - visibility of sidebar items given kube resource for route is Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -848,82 +813,47 @@ exports[`cluster - visibility of sidebar items given kube resource for route is Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/__snapshots__/workload-overview.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/workload-overview.test.tsx.snap index 1a4f905eaacc..422d35b7167a 100644 --- a/packages/core/src/features/cluster/__snapshots__/workload-overview.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/workload-overview.test.tsx.snap @@ -307,82 +307,47 @@ exports[`workload overview when navigating to workload overview renders 1`] = ` Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/extension-api/__snapshots__/disable-cluster-pages-when-cluster-is-not-relevant.test.tsx.snap b/packages/core/src/features/cluster/extension-api/__snapshots__/disable-cluster-pages-when-cluster-is-not-relevant.test.tsx.snap index 9e2aa77d4511..04e2307b3314 100644 --- a/packages/core/src/features/cluster/extension-api/__snapshots__/disable-cluster-pages-when-cluster-is-not-relevant.test.tsx.snap +++ b/packages/core/src/features/cluster/extension-api/__snapshots__/disable-cluster-pages-when-cluster-is-not-relevant.test.tsx.snap @@ -689,82 +689,47 @@ exports[`disable-cluster-pages-when-cluster-is-not-relevant given extension shou Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1206,82 +1171,47 @@ exports[`disable-cluster-pages-when-cluster-is-not-relevant given not yet known Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/extension-api/__snapshots__/disable-sidebar-items-when-cluster-is-not-relevant.test.tsx.snap b/packages/core/src/features/cluster/extension-api/__snapshots__/disable-sidebar-items-when-cluster-is-not-relevant.test.tsx.snap index 62dc47d37c30..3d34f6be8881 100644 --- a/packages/core/src/features/cluster/extension-api/__snapshots__/disable-sidebar-items-when-cluster-is-not-relevant.test.tsx.snap +++ b/packages/core/src/features/cluster/extension-api/__snapshots__/disable-sidebar-items-when-cluster-is-not-relevant.test.tsx.snap @@ -312,82 +312,47 @@ exports[`disable sidebar items when cluster is not relevant given extension shou Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -829,82 +794,47 @@ exports[`disable sidebar items when cluster is not relevant given extension shou Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1346,82 +1276,47 @@ exports[`disable sidebar items when cluster is not relevant given not yet known Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx.snap b/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx.snap index 140d800d9905..a9d55b541616 100644 --- a/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx.snap +++ b/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx.snap @@ -365,82 +365,47 @@ exports[`disable kube object detail items when cluster is not relevant given ext Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -948,82 +913,47 @@ exports[`disable kube object detail items when cluster is not relevant given ext Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1531,82 +1461,47 @@ exports[`disable kube object detail items when cluster is not relevant given not Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/reactively-hide-kube-object-detail-item.test.tsx.snap b/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/reactively-hide-kube-object-detail-item.test.tsx.snap index aa580d82f69e..e9e3fc498d3c 100644 --- a/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/reactively-hide-kube-object-detail-item.test.tsx.snap +++ b/packages/core/src/features/cluster/kube-object-details/extension-api/__snapshots__/reactively-hide-kube-object-detail-item.test.tsx.snap @@ -360,82 +360,47 @@ exports[`reactively hide kube object detail item renders 1`] = ` Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -948,82 +913,47 @@ exports[`reactively hide kube object detail item when the item is shown renders Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/namespaces/__snapshots__/edit-namespace-from-previously-opened-tab.test.tsx.snap b/packages/core/src/features/cluster/namespaces/__snapshots__/edit-namespace-from-previously-opened-tab.test.tsx.snap index af16c7648e53..32da53b4c1b8 100644 --- a/packages/core/src/features/cluster/namespaces/__snapshots__/edit-namespace-from-previously-opened-tab.test.tsx.snap +++ b/packages/core/src/features/cluster/namespaces/__snapshots__/edit-namespace-from-previously-opened-tab.test.tsx.snap @@ -319,82 +319,47 @@ exports[`cluster/namespaces - edit namespaces from previously opened tab given t Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -871,82 +836,47 @@ exports[`cluster/namespaces - edit namespaces from previously opened tab given t Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/cluster/workloads/__snapshots__/pods.test.tsx.snap b/packages/core/src/features/cluster/workloads/__snapshots__/pods.test.tsx.snap index d037610871a9..cc9bedf85fe3 100644 --- a/packages/core/src/features/cluster/workloads/__snapshots__/pods.test.tsx.snap +++ b/packages/core/src/features/cluster/workloads/__snapshots__/pods.test.tsx.snap @@ -311,82 +311,47 @@ exports[`workloads / pods when navigating to workloads / pods view given a names 1 item
    - -
    +
    +
    + Namespace: default +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: default +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: default +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -816,82 +781,47 @@ exports[`disable workloads overview details when cluster is not relevant given e Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1333,82 +1263,47 @@ exports[`disable workloads overview details when cluster is not relevant given n Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/helm-charts/installing-chart/__snapshots__/installing-helm-chart-from-new-tab.test.ts.snap b/packages/core/src/features/helm-charts/installing-chart/__snapshots__/installing-helm-chart-from-new-tab.test.ts.snap index d7ca67d88416..58c699f745cd 100644 --- a/packages/core/src/features/helm-charts/installing-chart/__snapshots__/installing-helm-chart-from-new-tab.test.ts.snap +++ b/packages/core/src/features/helm-charts/installing-chart/__snapshots__/installing-helm-chart-from-new-tab.test.ts.snap @@ -294,82 +294,47 @@ exports[`installing helm chart from new tab given tab for installing chart was n Overview
    - -
    +
    +
    + Namespaces: default, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -10437,82 +10402,47 @@ exports[`installing helm chart from new tab given tab for installing chart was n 0 items
    - -
    +
    +
    + Namespaces: default, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: default, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -821,82 +786,47 @@ exports[`installing helm chart from previously opened tab given tab for installi Overview
    - -
    +
    +
    + Namespaces: default, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/features/helm-charts/upgrade-chart/__snapshots__/upgrade-chart-new-tab.test.ts.snap b/packages/core/src/features/helm-charts/upgrade-chart/__snapshots__/upgrade-chart-new-tab.test.ts.snap index 11789fe6bbc7..f12108a078a9 100644 --- a/packages/core/src/features/helm-charts/upgrade-chart/__snapshots__/upgrade-chart-new-tab.test.ts.snap +++ b/packages/core/src/features/helm-charts/upgrade-chart/__snapshots__/upgrade-chart-new-tab.test.ts.snap @@ -311,82 +311,47 @@ exports[`New Upgrade Helm Chart Dock Tab given a namespace is selected when navi 0 items
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespace: my-second-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: some-namespace, some-other-namespace +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    @@ -1154,82 +1119,47 @@ exports[`download logs options in logs dock tab opening pod logs when logs not a Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/renderer/components/kube-object-list-layout/__snapshots__/kube-object-list-layout.test.tsx.snap b/packages/core/src/renderer/components/kube-object-list-layout/__snapshots__/kube-object-list-layout.test.tsx.snap index 21c6b0c90fc2..1c36192d90e8 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/__snapshots__/kube-object-list-layout.test.tsx.snap +++ b/packages/core/src/renderer/components/kube-object-list-layout/__snapshots__/kube-object-list-layout.test.tsx.snap @@ -21,82 +21,47 @@ exports[`kube-object-list-layout given pod store renders 1`] = ` 0 items
    - -
    +
    +
    + All namespaces +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    diff --git a/packages/core/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap b/packages/core/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap index 3d12aff98752..9dce45f1df07 100644 --- a/packages/core/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap +++ b/packages/core/src/renderer/frames/cluster-frame/__snapshots__/cluster-frame.test.tsx.snap @@ -1323,82 +1323,47 @@ exports[` given cluster without list nodes, but with namespaces Overview
    - -
    +
    +
    + All namespaces +
    - - -
    + class="gradient right" + />
    + + + expand_more + +
    From b2d1adf2e83954f98f5179278092ce0079d27f58 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 26 May 2023 08:48:33 -0400 Subject: [PATCH 10/16] chore: Update smoke test to fix selector due to new namespace-select-filter behaviour Signed-off-by: Sebastian Malton --- open-lens/integration/__tests__/cluster-pages.tests.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/open-lens/integration/__tests__/cluster-pages.tests.ts b/open-lens/integration/__tests__/cluster-pages.tests.ts index 30bbc79ed9a3..0b36193ea848 100644 --- a/open-lens/integration/__tests__/cluster-pages.tests.ts +++ b/open-lens/integration/__tests__/cluster-pages.tests.ts @@ -106,9 +106,7 @@ describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { await navigateToPods(frame); - const namespacesSelector = await frame.waitForSelector( - ".NamespaceSelect", - ); + const namespacesSelector = await frame.waitForSelector(".namespace-select-filter .menu .non-icon label"); await namespacesSelector.click(); await namespacesSelector.type(TEST_NAMESPACE); From 5ae2f7dc8f9e9a57b5b581221c884c303d2e92f9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 31 May 2023 09:47:28 -0400 Subject: [PATCH 11/16] chore: Fixup NamespaceSelectFilter after rebase Signed-off-by: Sebastian Malton --- .../renderer/components/namespace-select-filter/component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/renderer/components/namespace-select-filter/component.tsx b/packages/core/src/renderer/components/namespace-select-filter/component.tsx index 1fa716ce656c..bb1ed2276148 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/component.tsx +++ b/packages/core/src/renderer/components/namespace-select-filter/component.tsx @@ -11,7 +11,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption } from "./model.injectable"; import namespaceSelectFilterModelInjectable, { selectAllNamespaces } from "./model.injectable"; import { VariableSizeList } from "react-window"; -import { Icon } from "../icon"; +import { Icon } from "@k8slens/icon"; import { cssNames, prevDefault } from "@k8slens/utilities"; import { addWindowEventListener } from "../../window/event-listener.injectable"; import { TooltipPosition } from "@k8slens/tooltip"; From 40bca3b14a55576645e428b417db27ad0cac7df9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 31 May 2023 15:07:53 -0400 Subject: [PATCH 12/16] chore: Fix integration test because of new behaviour Signed-off-by: Sebastian Malton --- open-lens/integration/__tests__/cluster-pages.tests.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/open-lens/integration/__tests__/cluster-pages.tests.ts b/open-lens/integration/__tests__/cluster-pages.tests.ts index 0b36193ea848..b5c72446613a 100644 --- a/open-lens/integration/__tests__/cluster-pages.tests.ts +++ b/open-lens/integration/__tests__/cluster-pages.tests.ts @@ -110,8 +110,9 @@ describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { await namespacesSelector.click(); await namespacesSelector.type(TEST_NAMESPACE); - await namespacesSelector.press("Enter"); - await namespacesSelector.click(); + await frame.page().keyboard.press("Tab", { delay: 10 }); + await frame.page().keyboard.press("Tab", { delay: 10 }); + await frame.page().keyboard.press("Enter", { delay: 10 }); await frame.click(".Icon.new-dock-tab"); From 72a48417e47b5f6fa3d20301b784e51cf59e12d1 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 31 May 2023 16:07:48 -0400 Subject: [PATCH 13/16] fix: Filtering text of NamespaceSelectFilter shouldn't look bold Signed-off-by: Sebastian Malton --- .../namespace-select-filter.test.tsx.snap | 18 ++++++++++++++++++ .../namespace-select-filter/component.tsx | 9 ++++++--- .../namespace-select-filter.scss | 6 ------ .../namespace-select-filter.test.tsx | 8 ++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap index 83f846442ef4..b4d72bb7eb47 100644 --- a/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap +++ b/packages/core/src/renderer/components/namespace-select-filter/__snapshots__/namespace-select-filter.test.tsx.snap @@ -25,6 +25,7 @@ exports[` once the subscribe resolves renders 1`] = ` class="gradient left" />
    @@ -4216,82 +3978,48 @@ exports[`cluster - custom resources in sidebar when custom resource exists when Overview
    - -
    +
    +
    + Namespaces: +
    - - -
    + class="gradient right" + />
    + + + expand_more + +