diff --git a/client/src/app/hooks/table-controls/DOCS.md b/client/src/app/hooks/table-controls/DOCS.md index 445539c691..4200788479 100644 --- a/client/src/app/hooks/table-controls/DOCS.md +++ b/client/src/app/hooks/table-controls/DOCS.md @@ -300,16 +300,14 @@ Item details can be expanded, either with a "single expansion" variant where an > ⚠️ TECH DEBT NOTE: `getSingleExpandButtonTdProps` and `getCompoundExpandTdProps` should probably be factored out of `useTableControlProps` into a decoupled `getExpansionProps` helper. -### Active Row +### Active Item -An item can be clicked to mark it as "active", which usually opens a drawer on the page to show more detail. Note that this is distinct from expansion and selection and these features can all be used together. Active row state is simply a single id value (number or string) for the active item, derived from the `idProperty` config argument (see [Unique Identifiers](#unique-identifiers)). +A row can be clicked to mark its item as "active", which usually opens a drawer on the page to show more detail. Note that this is distinct from expansion and selection and these features can all be used together. Active item state is simply a single id value (number or string) for the active item, derived from the `idProperty` config argument (see [Unique Identifiers](#unique-identifiers)). -- The active row feature requires no config arguments. -- Active row state is provided by `useActiveRowState`. -- Active row shorthand functions are provided by `getActiveRowDerivedState`. -- A `useEffect` call which prevents invalid state after an item is deleted is provided by `useActiveRowEffects`. - -> ⚠️ TECH DEBT NOTE: We may want to rename the "active row" feature and code to "active item" to be consistent about using "item" naming rather than "row" naming outside the rendering code (see [Item Objects, Not Row Objects](#item-objects-not-row-objects)). +- The active item feature requires no config arguments. +- Active item state is provided by `useActiveItemState`. +- Active item shorthand functions are provided by `getActiveItemDerivedState`. +- A `useEffect` call which prevents invalid state after an item is deleted is provided by `useActiveItemEffects`. ### Selection diff --git a/client/src/app/hooks/table-controls/active-item/getActiveItemDerivedState.ts b/client/src/app/hooks/table-controls/active-item/getActiveItemDerivedState.ts index 508be877fc..19419db817 100644 --- a/client/src/app/hooks/table-controls/active-item/getActiveItemDerivedState.ts +++ b/client/src/app/hooks/table-controls/active-item/getActiveItemDerivedState.ts @@ -1,32 +1,32 @@ import { KeyWithValueType } from "@app/utils/type-utils"; -import { IActiveRowState } from "./useActiveItemState"; +import { IActiveItemState } from "./useActiveItemState"; -export interface IActiveRowDerivedStateArgs { +export interface IActiveItemDerivedStateArgs { currentPageItems: TItem[]; idProperty: KeyWithValueType; - activeRowState: IActiveRowState; + activeItemState: IActiveItemState; } -export interface IActiveRowDerivedState { - activeRowItem: TItem | null; - setActiveRowItem: (item: TItem | null) => void; - clearActiveRow: () => void; - isActiveRowItem: (item: TItem) => boolean; +export interface IActiveItemDerivedState { + activeItem: TItem | null; + setActiveItem: (item: TItem | null) => void; + clearActiveItem: () => void; + isActiveItem: (item: TItem) => boolean; } -// Note: This is not named `getLocalActiveRowDerivedState` because it is always local, +// Note: This is not named `getLocalActiveItemDerivedState` because it is always local, // and it is still used when working with server-managed tables. -export const getActiveRowDerivedState = ({ +export const getActiveItemDerivedState = ({ currentPageItems, idProperty, - activeRowState: { activeRowId, setActiveRowId }, -}: IActiveRowDerivedStateArgs): IActiveRowDerivedState => ({ - activeRowItem: - currentPageItems.find((item) => item[idProperty] === activeRowId) || null, - setActiveRowItem: (item: TItem | null) => { + activeItemState: { activeItemId, setActiveItemId }, +}: IActiveItemDerivedStateArgs): IActiveItemDerivedState => ({ + activeItem: + currentPageItems.find((item) => item[idProperty] === activeItemId) || null, + setActiveItem: (item: TItem | null) => { const itemId = (item?.[idProperty] ?? null) as string | number | null; // TODO Assertion shouldn't be necessary here but TS isn't fully inferring item[idProperty]? - setActiveRowId(itemId); + setActiveItemId(itemId); }, - clearActiveRow: () => setActiveRowId(null), - isActiveRowItem: (item) => item[idProperty] === activeRowId, + clearActiveItem: () => setActiveItemId(null), + isActiveItem: (item) => item[idProperty] === activeItemId, }); diff --git a/client/src/app/hooks/table-controls/active-item/useActiveItemEffects.ts b/client/src/app/hooks/table-controls/active-item/useActiveItemEffects.ts index d03c2b8f0f..2bc75fce67 100644 --- a/client/src/app/hooks/table-controls/active-item/useActiveItemEffects.ts +++ b/client/src/app/hooks/table-controls/active-item/useActiveItemEffects.ts @@ -1,23 +1,23 @@ import * as React from "react"; -import { getActiveRowDerivedState } from "./getActiveItemDerivedState"; -import { IActiveRowState } from "./useActiveItemState"; +import { getActiveItemDerivedState } from "./getActiveItemDerivedState"; +import { IActiveItemState } from "./useActiveItemState"; -export interface IUseActiveRowEffectsArgs { +export interface IUseActiveItemEffectsArgs { isLoading?: boolean; - activeRowState: IActiveRowState; - activeRowDerivedState: ReturnType>; + activeItemState: IActiveItemState; + activeItemDerivedState: ReturnType>; } -export const useActiveRowEffects = ({ +export const useActiveItemEffects = ({ isLoading, - activeRowState: { activeRowId }, - activeRowDerivedState: { activeRowItem, clearActiveRow }, -}: IUseActiveRowEffectsArgs) => { + activeItemState: { activeItemId }, + activeItemDerivedState: { activeItem, clearActiveItem }, +}: IUseActiveItemEffectsArgs) => { React.useEffect(() => { - // If some state change (e.g. refetch, pagination) causes the active row to disappear, - // remove its id from state so the drawer won't automatically reopen if the row comes back. - if (!isLoading && activeRowId && !activeRowItem) { - clearActiveRow(); + // If some state change (e.g. refetch, pagination) causes the active item to disappear, + // remove its id from state so the drawer won't automatically reopen if the item comes back. + if (!isLoading && activeItemId && !activeItem) { + clearActiveItem(); } - }, [isLoading, activeRowId, activeRowItem]); + }, [isLoading, activeItemId, activeItem]); }; diff --git a/client/src/app/hooks/table-controls/active-item/useActiveItemPropHelpers.ts b/client/src/app/hooks/table-controls/active-item/useActiveItemPropHelpers.ts index 718784f5e2..a346b92771 100644 --- a/client/src/app/hooks/table-controls/active-item/useActiveItemPropHelpers.ts +++ b/client/src/app/hooks/table-controls/active-item/useActiveItemPropHelpers.ts @@ -1,47 +1,47 @@ import { TrProps } from "@patternfly/react-table"; import { - IActiveRowDerivedStateArgs, - getActiveRowDerivedState, + IActiveItemDerivedStateArgs, + getActiveItemDerivedState, } from "./getActiveItemDerivedState"; -import { IActiveRowState } from "./useActiveItemState"; +import { IActiveItemState } from "./useActiveItemState"; import { - IUseActiveRowEffectsArgs, - useActiveRowEffects, + IUseActiveItemEffectsArgs, + useActiveItemEffects, } from "./useActiveItemEffects"; // Args that should be passed into useTableControlProps -export type IActiveRowPropHelpersExternalArgs = - IActiveRowDerivedStateArgs & - Omit, "activeRowDerivedState"> & { +export type IActiveItemPropHelpersExternalArgs = + IActiveItemDerivedStateArgs & + Omit, "activeItemDerivedState"> & { isLoading?: boolean; - activeRowState: IActiveRowState; + activeItemState: IActiveItemState; }; -export const useActiveRowPropHelpers = ( - args: IActiveRowPropHelpersExternalArgs +export const useActiveItemPropHelpers = ( + args: IActiveItemPropHelpersExternalArgs ) => { - const activeRowDerivedState = getActiveRowDerivedState(args); - const { isActiveRowItem, setActiveRowItem, clearActiveRow } = - activeRowDerivedState; + const activeItemDerivedState = getActiveItemDerivedState(args); + const { isActiveItem, setActiveItem, clearActiveItem } = + activeItemDerivedState; - useActiveRowEffects({ ...args, activeRowDerivedState }); + useActiveItemEffects({ ...args, activeItemDerivedState }); - const getActiveRowTrProps = ({ + const getActiveItemTrProps = ({ item, }: { item: TItem; }): Omit => ({ isSelectable: true, isClickable: true, - isRowSelected: item && isActiveRowItem(item), + isRowSelected: item && isActiveItem(item), onRowClick: () => { - if (!isActiveRowItem(item)) { - setActiveRowItem(item); + if (!isActiveItem(item)) { + setActiveItem(item); } else { - clearActiveRow(); + clearActiveItem(); } }, }); - return { activeRowDerivedState, getActiveRowTrProps }; + return { activeItemDerivedState, getActiveItemTrProps }; }; diff --git a/client/src/app/hooks/table-controls/active-item/useActiveItemState.ts b/client/src/app/hooks/table-controls/active-item/useActiveItemState.ts index 4dbcfec409..dab6bd92f1 100644 --- a/client/src/app/hooks/table-controls/active-item/useActiveItemState.ts +++ b/client/src/app/hooks/table-controls/active-item/useActiveItemState.ts @@ -2,29 +2,29 @@ import { parseMaybeNumericString } from "@app/utils/utils"; import { IFeaturePersistenceArgs } from "../types"; import { usePersistentState } from "@app/hooks/usePersistentState"; -export interface IActiveRowState { - activeRowId: string | number | null; - setActiveRowId: (id: string | number | null) => void; +export interface IActiveItemState { + activeItemId: string | number | null; + setActiveItemId: (id: string | number | null) => void; } -export type IActiveRowStateArgs = { isActiveRowEnabled?: boolean }; +export type IActiveItemStateArgs = { isActiveItemEnabled?: boolean }; -export const useActiveRowState = < +export const useActiveItemState = < TPersistenceKeyPrefix extends string = string, >( - args: IActiveRowStateArgs & + args: IActiveItemStateArgs & IFeaturePersistenceArgs = {} -): IActiveRowState => { - const { isActiveRowEnabled, persistTo, persistenceKeyPrefix } = args; +): IActiveItemState => { + const { isActiveItemEnabled, persistTo, persistenceKeyPrefix } = args; // We won't need to pass the latter two type params here if TS adds support for partial inference. // See https://github.com/konveyor/tackle2-ui/issues/1456 - const [activeRowId, setActiveRowId] = usePersistentState< + const [activeItemId, setActiveItemId] = usePersistentState< string | number | null, TPersistenceKeyPrefix, - "activeRow" + "activeItem" >({ - isEnabled: !!isActiveRowEnabled, + isEnabled: !!isActiveItemEnabled, defaultValue: null, persistenceKeyPrefix, // Note: For the discriminated union here to work without TypeScript getting confused @@ -33,18 +33,18 @@ export const useActiveRowState = < ...(persistTo === "urlParams" ? { persistTo, - keys: ["activeRow"], - serialize: (activeRowId) => ({ - activeRow: activeRowId !== null ? String(activeRowId) : null, + keys: ["activeItem"], + serialize: (activeItemId) => ({ + activeItem: activeItemId !== null ? String(activeItemId) : null, }), - deserialize: ({ activeRow }) => parseMaybeNumericString(activeRow), + deserialize: ({ activeItem }) => parseMaybeNumericString(activeItem), } : persistTo === "localStorage" || persistTo === "sessionStorage" ? { persistTo, - key: "activeRow", + key: "activeItem", } : { persistTo }), }); - return { activeRowId, setActiveRowId }; + return { activeItemId, setActiveItemId }; }; diff --git a/client/src/app/hooks/table-controls/types.ts b/client/src/app/hooks/table-controls/types.ts index 2f8f5ce989..2cf0cfd561 100644 --- a/client/src/app/hooks/table-controls/types.ts +++ b/client/src/app/hooks/table-controls/types.ts @@ -25,10 +25,10 @@ import { IExpansionStateArgs, } from "./expansion"; import { - IActiveRowDerivedState, - IActiveRowPropHelpersExternalArgs, - IActiveRowState, - IActiveRowStateArgs, + IActiveItemDerivedState, + IActiveItemPropHelpersExternalArgs, + IActiveItemState, + IActiveItemStateArgs, } from "./active-item"; import { PaginationProps, @@ -52,7 +52,7 @@ export type TableFeature = | "pagination" | "selection" | "expansion" - | "activeRow"; + | "activeItem"; export type PersistTarget = | "state" @@ -101,7 +101,7 @@ export type IUseTableControlStateArgs< IPaginationStateArgs & { isSelectionEnabled?: boolean; // TODO move this into useSelectionState when we move it from lib-ui } & IExpansionStateArgs & - IActiveRowStateArgs & + IActiveItemStateArgs & ITablePersistenceArgs; // Table-level state object @@ -125,7 +125,7 @@ export type ITableControlState< sortState: ISortState; paginationState: IPaginationState; expansionState: IExpansionState; - activeRowState: IActiveRowState; + activeItemState: IActiveItemState; }; // Table-level local derived state args @@ -142,7 +142,7 @@ export type ITableControlLocalDerivedStateArgs< ILocalSortDerivedStateArgs & ILocalPaginationDerivedStateArgs; // There is no ILocalExpansionDerivedStateArgs type because expansion derived state is always local and internal to useTableControlProps -// There is no ILocalActiveRowDerivedStateArgs type because expansion derived state is always local and internal to useTableControlProps +// There is no ILocalActiveItemDerivedStateArgs type because expansion derived state is always local and internal to useTableControlProps // Table-level derived state object // - Used by useTableControlProps @@ -178,7 +178,7 @@ export type IUseTableControlPropsArgs< IPaginationPropHelpersExternalArgs & // ISelectionPropHelpersExternalArgs // TODO when we move selection from lib-ui IExpansionPropHelpersExternalArgs & - IActiveRowPropHelpersExternalArgs & + IActiveItemPropHelpersExternalArgs & ITableControlDerivedState & { isLoading?: boolean; forceNumRenderedColumns?: number; @@ -208,7 +208,7 @@ export type ITableControls< numColumnsAfterData: number; numRenderedColumns: number; expansionDerivedState: IExpansionDerivedState; - activeRowDerivedState: IActiveRowDerivedState; + activeItemDerivedState: IActiveItemDerivedState; propHelpers: { toolbarProps: Omit; tableProps: Omit; diff --git a/client/src/app/hooks/table-controls/useTableControlProps.ts b/client/src/app/hooks/table-controls/useTableControlProps.ts index f6fb2dc0fd..13539d7ca5 100644 --- a/client/src/app/hooks/table-controls/useTableControlProps.ts +++ b/client/src/app/hooks/table-controls/useTableControlProps.ts @@ -6,7 +6,7 @@ import { ITableControls, IUseTableControlPropsArgs } from "./types"; import { useFilterPropHelpers } from "./filtering"; import { useSortPropHelpers } from "./sorting"; import { usePaginationPropHelpers } from "./pagination"; -import { useActiveRowPropHelpers } from "./active-item"; +import { useActiveItemPropHelpers } from "./active-item"; import { useExpansionPropHelpers } from "./expansion"; import { handlePropagatedRowClick } from "./utils"; @@ -62,7 +62,7 @@ export const useTableControlProps = < isSortEnabled, isSelectionEnabled, isExpansionEnabled, - isActiveRowEnabled, + isActiveItemEnabled, } = args; const columnKeys = objectKeys(columnNames); @@ -90,8 +90,8 @@ export const useTableControlProps = < getCompoundExpandTdProps, getExpandedContentTdProps, } = useExpansionPropHelpers({ ...args, columnKeys, numRenderedColumns }); - const { activeRowDerivedState, getActiveRowTrProps } = - useActiveRowPropHelpers(args); + const { activeItemDerivedState, getActiveItemTrProps } = + useActiveItemPropHelpers(args); const toolbarProps: PropHelpers["toolbarProps"] = { className: variant === "compact" ? spacing.pt_0 : "", @@ -120,12 +120,12 @@ export const useTableControlProps = < }); const getTrProps: PropHelpers["getTrProps"] = ({ item, onRowClick }) => { - const activeRowTrProps = getActiveRowTrProps({ item }); + const activeItemTrProps = getActiveItemTrProps({ item }); return { - ...(isActiveRowEnabled && activeRowTrProps), + ...(isActiveItemEnabled && activeItemTrProps), onRowClick: (event) => handlePropagatedRowClick(event, () => { - activeRowTrProps.onRowClick?.(event); + activeItemTrProps.onRowClick?.(event); onRowClick?.(event); }), }; @@ -166,7 +166,7 @@ export const useTableControlProps = < numColumnsAfterData, numRenderedColumns, expansionDerivedState, - activeRowDerivedState, + activeItemDerivedState, propHelpers: { toolbarProps, tableProps, diff --git a/client/src/app/hooks/table-controls/useTableControlState.ts b/client/src/app/hooks/table-controls/useTableControlState.ts index 1f8554f59f..537fbd7979 100644 --- a/client/src/app/hooks/table-controls/useTableControlState.ts +++ b/client/src/app/hooks/table-controls/useTableControlState.ts @@ -7,7 +7,7 @@ import { import { useFilterState } from "./filtering"; import { useSortState } from "./sorting"; import { usePaginationState } from "./pagination"; -import { useActiveRowState } from "./active-item"; +import { useActiveItemState } from "./active-item"; import { useExpansionState } from "./expansion"; export const useTableControlState = < @@ -53,9 +53,9 @@ export const useTableControlState = < ...args, persistTo: getPersistTo("expansion"), }); - const activeRowState = useActiveRowState({ + const activeItemState = useActiveItemState({ ...args, - persistTo: getPersistTo("activeRow"), + persistTo: getPersistTo("activeItem"), }); return { ...args, @@ -63,6 +63,6 @@ export const useTableControlState = < sortState, paginationState, expansionState, - activeRowState, + activeItemState, }; }; diff --git a/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx b/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx index 0d093aa27d..49a6685c59 100644 --- a/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx +++ b/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx @@ -136,7 +136,7 @@ export const ApplicationsTableAnalyze: React.FC = () => { }), variant: "success", }); - clearActiveRow(); + clearActiveItem(); setApplicationsToDelete([]); }; @@ -200,7 +200,7 @@ export const ApplicationsTableAnalyze: React.FC = () => { isSortEnabled: true, isPaginationEnabled: true, isSelectionEnabled: true, - isActiveRowEnabled: true, + isActiveItemEnabled: true, sortableColumns: ["name", "description", "businessService", "tags"], initialSort: { columnKey: "name", direction: "asc" }, getSortValues: (app) => ({ @@ -345,7 +345,7 @@ export const ApplicationsTableAnalyze: React.FC = () => { getTdProps, toolbarBulkSelectorProps, }, - activeRowDerivedState: { activeRowItem, clearActiveRow }, + activeItemDerivedState: { activeItem, clearActiveItem }, selectionState: { selectedItems: selectedRows }, } = tableControls; @@ -754,10 +754,10 @@ export const ApplicationsTableAnalyze: React.FC = () => { paginationProps={paginationProps} /> { }), variant: "success", }); - clearActiveRow(); + clearActiveItem(); setApplicationsToDelete([]); }; @@ -246,7 +246,7 @@ export const ApplicationsTable: React.FC = () => { isFilterEnabled: true, isSortEnabled: true, isPaginationEnabled: true, - isActiveRowEnabled: true, + isActiveItemEnabled: true, sortableColumns: ["name", "description", "businessService", "tags"], initialSort: { columnKey: "name", direction: "asc" }, getSortValues: (app) => ({ @@ -394,7 +394,7 @@ export const ApplicationsTable: React.FC = () => { getTdProps, toolbarBulkSelectorProps, }, - activeRowDerivedState: { activeRowItem, clearActiveRow }, + activeItemDerivedState: { activeItem, clearActiveItem }, selectionState: { selectedItems: selectedRows }, } = tableControls; @@ -738,9 +738,9 @@ export const ApplicationsTable: React.FC = () => { paginationProps={paginationProps} /> { onConfirm={() => { history.push( formatPath(Paths.applicationAssessmentActions, { - applicationId: activeRowItem?.id, + applicationId: activeItem?.id, }) ); setArchetypeRefsToOverride(null); diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index 2a298f897b..0f78ade644 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -117,7 +117,7 @@ const Archetypes: React.FC = () => { isFilterEnabled: true, isSortEnabled: true, isPaginationEnabled: true, - isActiveRowEnabled: true, + isActiveItemEnabled: true, filterCategories: [ { @@ -154,7 +154,7 @@ const Archetypes: React.FC = () => { getTrProps, getTdProps, }, - activeRowDerivedState: { activeRowItem, clearActiveRow }, + activeItemDerivedState: { activeItem, clearActiveItem }, } = tableControls; // TODO: RBAC access checks need to be added. Only Architect (and Administrator) personas @@ -344,8 +344,8 @@ const Archetypes: React.FC = () => { {/* Create modal */} diff --git a/client/src/app/pages/dependencies/dependencies.tsx b/client/src/app/pages/dependencies/dependencies.tsx index 61dce2c656..8add1b5fbe 100644 --- a/client/src/app/pages/dependencies/dependencies.tsx +++ b/client/src/app/pages/dependencies/dependencies.tsx @@ -52,7 +52,7 @@ export const Dependencies: React.FC = () => { isFilterEnabled: true, isSortEnabled: true, isPaginationEnabled: true, - isActiveRowEnabled: true, + isActiveItemEnabled: true, sortableColumns: ["name", "foundIn", "labels"], initialSort: { columnKey: "name", direction: "asc" }, filterCategories: [ @@ -144,7 +144,7 @@ export const Dependencies: React.FC = () => { getTrProps, getTdProps, }, - activeRowDerivedState: { activeRowItem, clearActiveRow, setActiveRowItem }, + activeItemDerivedState: { activeItem, clearActiveItem, setActiveItem }, } = tableControls; return ( @@ -211,13 +211,10 @@ export const Dependencies: React.FC = () => { className={spacing.pl_0} variant="link" onClick={(_) => { - if ( - activeRowItem && - activeRowItem === dependency - ) { - clearActiveRow(); + if (activeItem && activeItem === dependency) { + clearActiveItem(); } else { - setActiveRowItem(dependency); + setActiveItem(dependency); } }} > @@ -261,8 +258,8 @@ export const Dependencies: React.FC = () => { setActiveRowItem(null)} + dependency={activeItem || null} + onCloseClick={() => setActiveItem(null)} > ); diff --git a/client/src/app/pages/issues/affected-applications/affected-applications.tsx b/client/src/app/pages/issues/affected-applications/affected-applications.tsx index 0908662fa2..3ba53b85d9 100644 --- a/client/src/app/pages/issues/affected-applications/affected-applications.tsx +++ b/client/src/app/pages/issues/affected-applications/affected-applications.tsx @@ -65,7 +65,7 @@ export const AffectedApplications: React.FC = () => { isFilterEnabled: true, isSortEnabled: true, isPaginationEnabled: true, - isActiveRowEnabled: true, + isActiveItemEnabled: true, sortableColumns: ["name", "businessService", "effort", "incidents"], initialSort: { columnKey: "name", direction: "asc" }, filterCategories: useSharedAffectedApplicationFilterCategories(), @@ -126,7 +126,7 @@ export const AffectedApplications: React.FC = () => { getTrProps, getTdProps, }, - activeRowDerivedState: { activeRowItem, clearActiveRow }, + activeItemDerivedState: { activeItem, clearActiveItem }, } = tableControls; return ( @@ -238,9 +238,9 @@ export const AffectedApplications: React.FC = () => { );