From 68179b893b4a064cbce519275b2846c4fbdb64da Mon Sep 17 00:00:00 2001 From: farodin91 Date: Sat, 14 Dec 2024 22:54:47 +0100 Subject: [PATCH] frontend: refactor row actions and header actions Signed-off-by: farodin91 --- .../components/common/Resource/EditButton.tsx | 2 +- .../MainInfoSection/MainInfoSectionHeader.tsx | 101 +------------- .../common/Resource/ResourceTable.tsx | 58 +------- .../common/Resource/generateHeaderActions.tsx | 130 ++++++++++++++++++ .../components/common/Resource/index.test.ts | 1 + frontend/src/redux/actionButtonsSlice.ts | 6 +- 6 files changed, 144 insertions(+), 154 deletions(-) create mode 100644 frontend/src/components/common/Resource/generateHeaderActions.tsx diff --git a/frontend/src/components/common/Resource/EditButton.tsx b/frontend/src/components/common/Resource/EditButton.tsx index 0e9cfc15ba..a11b3a8f77 100644 --- a/frontend/src/components/common/Resource/EditButton.tsx +++ b/frontend/src/components/common/Resource/EditButton.tsx @@ -87,7 +87,7 @@ export default function EditButton(props: EditButtonProps) { } if (isReadOnly) { - return ; + return ; } return ( diff --git a/frontend/src/components/common/Resource/MainInfoSection/MainInfoSectionHeader.tsx b/frontend/src/components/common/Resource/MainInfoSection/MainInfoSectionHeader.tsx index 2a354093b8..6fe536aa47 100644 --- a/frontend/src/components/common/Resource/MainInfoSection/MainInfoSectionHeader.tsx +++ b/frontend/src/components/common/Resource/MainInfoSection/MainInfoSectionHeader.tsx @@ -1,19 +1,9 @@ -import { has } from 'lodash'; -import React, { isValidElement } from 'react'; +import React from 'react'; import { useLocation } from 'react-router-dom'; import { KubeObject } from '../../../../lib/k8s/KubeObject'; -import { - DefaultHeaderAction, - HeaderAction, - HeaderActionType, -} from '../../../../redux/actionButtonsSlice'; -import { useTypedSelector } from '../../../../redux/reducers/reducers'; -import ErrorBoundary from '../../ErrorBoundary'; +import { HeaderAction } from '../../../../redux/actionButtonsSlice'; import SectionHeader, { HeaderStyle } from '../../SectionHeader'; -import DeleteButton from '../DeleteButton'; -import EditButton from '../EditButton'; -import { RestartButton } from '../RestartButton'; -import ScaleButton from '../ScaleButton'; +import { generateActions } from '../generateHeaderActions'; export interface MainInfoHeaderProps { resource: T | null; @@ -32,90 +22,7 @@ export interface MainInfoHeaderProps { export function MainInfoHeader(props: MainInfoHeaderProps) { const { resource, title, actions = [], headerStyle = 'main', noDefaultActions = false } = props; - const headerActions = useTypedSelector(state => state.actionButtons.headerActions); - const headerActionsProcessors = useTypedSelector( - state => state.actionButtons.headerActionsProcessors - ); - function setupAction(headerAction: HeaderAction) { - let Action = has(headerAction, 'action') ? (headerAction as any).action : headerAction; - - if (!noDefaultActions && has(headerAction, 'id')) { - switch ((headerAction as HeaderAction).id) { - case DefaultHeaderAction.RESTART: - Action = RestartButton; - break; - case DefaultHeaderAction.SCALE: - Action = ScaleButton; - break; - case DefaultHeaderAction.EDIT: - Action = EditButton; - break; - case DefaultHeaderAction.DELETE: - Action = DeleteButton; - break; - default: - break; - } - } - - if (!Action || (headerAction as unknown as HeaderAction).action === null) { - return null; - } - - if (isValidElement(Action)) { - return {Action}; - } else if (Action === null) { - return null; - } else if (typeof Action === 'function') { - return ( - - - - ); - } - } - - const defaultActions = [ - { - id: DefaultHeaderAction.RESTART, - }, - { - id: DefaultHeaderAction.SCALE, - }, - { - id: DefaultHeaderAction.EDIT, - }, - { - id: DefaultHeaderAction.DELETE, - }, - ]; - - let hAccs: HeaderAction[] = []; - const accs = typeof actions === 'function' ? actions(resource) || [] : actions; - if (accs !== null) { - hAccs = [...accs].map((action, i): HeaderAction => { - if ((action as HeaderAction)?.id !== undefined) { - return action as HeaderAction; - } else { - return { id: `gen-${i}`, action: action as HeaderActionType }; - } - }); - } - - let actionsProcessed = [...headerActions, ...hAccs, ...defaultActions]; - if (headerActionsProcessors.length > 0) { - for (const headerProcessor of headerActionsProcessors) { - actionsProcessed = headerProcessor.processor(resource, actionsProcessed); - } - } - - const allActions = React.Children.toArray( - (function propsActions() { - const pluginAddedActions = actionsProcessed.map(setupAction); - return React.Children.toArray(pluginAddedActions); - })() - ); - + const allActions = generateActions(resource, 'action', actions, noDefaultActions); return ( = { /** Unique id for the column, not required but recommended */ @@ -87,7 +83,7 @@ export interface ResourceTableProps { enableRowActions?: boolean; /** Show or hide row selections and actions @default false*/ enableRowSelection?: boolean; - actions?: null | RowAction[]; + actions?: null | HeaderAction[]; /** Provide a list of columns that won't be shown and cannot be turned on */ hideColumns?: string[] | null; /** ID for the table. Will be used by plugins to identify this table. @@ -416,52 +412,12 @@ function ResourceTableContent(props: ResourceTablePr tableSettings, ]); - const defaultActions: RowAction[] = [ - { - id: DefaultHeaderAction.RESTART, - action: ({ item }) => , - }, - { - id: DefaultHeaderAction.SCALE, - action: ({ item }) => , - }, - { - id: DefaultHeaderAction.EDIT, - action: ({ item, closeMenu }) => ( - - ), - }, - { - id: DefaultHeaderAction.VIEW, - action: ({ item }) => , - }, - { - id: DefaultHeaderAction.DELETE, - action: ({ item, closeMenu }) => ( - - ), - }, - ]; - let hAccs: RowAction[] = []; - if (actions !== undefined && actions !== null) { - hAccs = actions; - } - - const actionsProcessed: RowAction[] = [...hAccs, ...defaultActions]; - const renderRowActionMenuItems = useMemo(() => { - if (actionsProcessed.length === 0) { + if (!enableRowActions) { return null; } - return ({ closeMenu, row }: { closeMenu: () => void; row: MRT_Row> }) => { - return actionsProcessed.map(action => { - if (action.action === undefined || action.action === null) { - return ; - } - return action.action({ item: row.original, closeMenu }); - }); - }; - }, [actionsProcessed]); + return generateRowActionsMenu(actions); + }, [actions, enableRowActions]); const wrappedEnableRowSelection = useMemo(() => { if (import.meta.env.REACT_APP_HEADLAMP_ENABLE_ROW_SELECTION === 'false') { diff --git a/frontend/src/components/common/Resource/generateHeaderActions.tsx b/frontend/src/components/common/Resource/generateHeaderActions.tsx new file mode 100644 index 0000000000..37a30e3d4e --- /dev/null +++ b/frontend/src/components/common/Resource/generateHeaderActions.tsx @@ -0,0 +1,130 @@ +import { has } from 'lodash'; +import { MRT_Row } from 'material-react-table'; +import { isValidElement } from 'react'; +import React from 'react'; +import { KubeObject } from '../../../lib/k8s/KubeObject'; +import { + DefaultHeaderAction, + HeaderAction, + HeaderActionType, +} from '../../../redux/actionButtonsSlice'; +import { useTypedSelector } from '../../../redux/reducers/reducers'; +import { ButtonStyle } from '../ActionButton/ActionButton'; +import ErrorBoundary from '../ErrorBoundary'; +import DeleteButton from './DeleteButton'; +import EditButton from './EditButton'; +import { RestartButton } from './RestartButton'; +import ScaleButton from './ScaleButton'; + +export function generateActions( + resource: T | null, + buttonStyle: ButtonStyle, + actions: + | ((resource: T | null) => React.ReactNode[] | HeaderAction[] | null) + | React.ReactNode[] + | null + | HeaderAction[], + noDefaultActions?: boolean, + closeMenu?: () => void +): React.ReactNode[] { + const headerActions = useTypedSelector(state => state.actionButtons.headerActions); + const headerActionsProcessors = useTypedSelector( + state => state.actionButtons.headerActionsProcessors + ); + function setupAction(headerAction: HeaderAction) { + let Action = has(headerAction, 'action') ? (headerAction as any).action : headerAction; + + if (!noDefaultActions && has(headerAction, 'id')) { + switch ((headerAction as HeaderAction).id) { + case DefaultHeaderAction.RESTART: + Action = RestartButton; + break; + case DefaultHeaderAction.SCALE: + Action = ScaleButton; + break; + case DefaultHeaderAction.EDIT: + Action = EditButton; + break; + case DefaultHeaderAction.DELETE: + Action = DeleteButton; + break; + default: + break; + } + } + + if (!Action || (headerAction as unknown as HeaderAction).action === null) { + return null; + } + + if (isValidElement(Action)) { + return {Action}; + } else if (Action === null) { + return null; + } else if (typeof Action === 'function') { + return ( + + + + ); + } + } + + const defaultActions = [ + { + id: DefaultHeaderAction.RESTART, + }, + { + id: DefaultHeaderAction.SCALE, + }, + { + id: DefaultHeaderAction.EDIT, + }, + { + id: DefaultHeaderAction.DELETE, + }, + ]; + + let hAccs: HeaderAction[] = []; + const accs = typeof actions === 'function' ? actions(resource) || [] : actions; + if (accs !== null) { + hAccs = [...accs].map((action, i): HeaderAction => { + if ((action as HeaderAction)?.id !== undefined) { + return action as HeaderAction; + } else { + return { id: `gen-${i}`, action: action as HeaderActionType }; + } + }); + } + + let actionsProcessed = [...headerActions, ...hAccs, ...defaultActions]; + if (headerActionsProcessors.length > 0) { + for (const headerProcessor of headerActionsProcessors) { + actionsProcessed = headerProcessor.processor(resource, actionsProcessed); + } + } + + const allActions = React.Children.toArray( + (function propsActions() { + const pluginAddedActions = actionsProcessed.map(setupAction); + return React.Children.toArray(pluginAddedActions); + })() + ); + return allActions; +} + +export default function generateRowActionsMenu(actions: HeaderAction[] | null | undefined) { + return ({ closeMenu, row }: { closeMenu: () => void; row: MRT_Row> }) => { + const actionsProcessed = generateActions( + row.original as any, + 'menu', + actions || [], + false, + closeMenu + ); + if (actionsProcessed.length === 0) { + return null; + } + return actionsProcessed; + }; +} diff --git a/frontend/src/components/common/Resource/index.test.ts b/frontend/src/components/common/Resource/index.test.ts index ae4c85ad8c..4ec6b80934 100644 --- a/frontend/src/components/common/Resource/index.test.ts +++ b/frontend/src/components/common/Resource/index.test.ts @@ -35,6 +35,7 @@ const checkExports = [ 'SimpleEditor', 'ViewButton', 'AuthVisible', + 'generateHeaderActions', ]; function getFilesToVerify() { diff --git a/frontend/src/redux/actionButtonsSlice.ts b/frontend/src/redux/actionButtonsSlice.ts index cf96080970..dff637395d 100644 --- a/frontend/src/redux/actionButtonsSlice.ts +++ b/frontend/src/redux/actionButtonsSlice.ts @@ -7,17 +7,13 @@ export type HeaderActionType = ((...args: any[]) => ReactNode) | null | ReactEle export type DetailsViewFunc = HeaderActionType; export type AppBarActionType = ((...args: any[]) => ReactNode) | null | ReactElement | ReactNode; -export type RowActionType = ((item: any) => JSX.Element | null | ReactNode) | null; export type HeaderAction = { id: string; action?: HeaderActionType; }; -export type RowAction = { - id: string; - action?: RowActionType; -}; +export type RowAction = HeaderAction; export type AppBarAction = { id: string;