From 32f6820d1f36fc45c8137c87a1fdff7090f8bd4b Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 14:29:11 +0800 Subject: [PATCH 01/12] Refactoring, decomposition and TS upgrades. --- .../Brokers/Broker/Configs/Configs.styled.ts | 30 ------ .../Brokers/Broker/Configs/Configs.tsx | 102 ++++-------------- .../Brokers/Broker/Configs/InputCell.tsx | 91 ---------------- .../ConfigSourceHeader.styled.ts | 12 +++ .../ConfigSourceHeader/ConfigSourceHeader.tsx | 27 +++++ .../InputCell/InputCellEditMode.tsx | 53 +++++++++ .../InputCell/InputCellViewMode.tsx | 31 ++++++ .../TableComponents/InputCell/index.tsx | 49 +++++++++ .../TableComponents/InputCell/styled.ts | 23 ++++ .../Broker/Configs/TableComponents/index.ts | 2 + .../Brokers/Broker/Configs/lib/constants.ts | 11 ++ .../Brokers/Broker/Configs/lib/types.ts | 11 ++ .../Brokers/Broker/Configs/lib/utils.tsx | 55 ++++++++++ 13 files changed, 297 insertions(+), 200 deletions(-) delete mode 100644 frontend/src/components/Brokers/Broker/Configs/InputCell.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.styled.ts create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellEditMode.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/index.ts create mode 100644 frontend/src/components/Brokers/Broker/Configs/lib/constants.ts create mode 100644 frontend/src/components/Brokers/Broker/Configs/lib/types.ts create mode 100644 frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx diff --git a/frontend/src/components/Brokers/Broker/Configs/Configs.styled.ts b/frontend/src/components/Brokers/Broker/Configs/Configs.styled.ts index cced53373..77768dc99 100644 --- a/frontend/src/components/Brokers/Broker/Configs/Configs.styled.ts +++ b/frontend/src/components/Brokers/Broker/Configs/Configs.styled.ts @@ -1,36 +1,6 @@ import styled from 'styled-components'; -export const ValueWrapper = styled.div` - display: flex; - justify-content: space-between; - button { - margin: 0 10px; - } -`; - -export const Value = styled.span` - line-height: 24px; - margin-right: 10px; - text-overflow: ellipsis; - max-width: 400px; - overflow: hidden; - white-space: nowrap; -`; - -export const ButtonsWrapper = styled.div` - display: flex; -`; export const SearchWrapper = styled.div` margin: 10px; width: 21%; `; - -export const Source = styled.div` - display: flex; - align-content: center; - svg { - margin-left: 10px; - vertical-align: middle; - cursor: pointer; - } -`; diff --git a/frontend/src/components/Brokers/Broker/Configs/Configs.tsx b/frontend/src/components/Brokers/Broker/Configs/Configs.tsx index 5b05b7cc5..c098c53c6 100644 --- a/frontend/src/components/Brokers/Broker/Configs/Configs.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/Configs.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { CellContext, ColumnDef } from '@tanstack/react-table'; +import React, { type FC, useMemo, useState } from 'react'; import { ClusterBrokerParam } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; import { @@ -7,91 +6,36 @@ import { useUpdateBrokerConfigByName, } from 'lib/hooks/api/brokers'; import Table from 'components/common/NewTable'; -import { BrokerConfig, ConfigSource } from 'generated-sources'; +import type { BrokerConfig } from 'generated-sources'; import Search from 'components/common/Search/Search'; -import Tooltip from 'components/common/Tooltip/Tooltip'; -import InfoIcon from 'components/common/Icons/InfoIcon'; +import { + getBrokerConfigsTableColumns, + getConfigTableData, +} from 'components/Brokers/Broker/Configs/lib/utils'; -import InputCell from './InputCell'; import * as S from './Configs.styled'; -const tooltipContent = `DYNAMIC_TOPIC_CONFIG = dynamic topic config that is configured for a specific topic -DYNAMIC_BROKER_LOGGER_CONFIG = dynamic broker logger config that is configured for a specific broker -DYNAMIC_BROKER_CONFIG = dynamic broker config that is configured for a specific broker -DYNAMIC_DEFAULT_BROKER_CONFIG = dynamic broker config that is configured as default for all brokers in the cluster -STATIC_BROKER_CONFIG = static broker config provided as broker properties at start up (e.g. server.properties file) -DEFAULT_CONFIG = built-in default configuration for configs that have a default value -UNKNOWN = source unknown e.g. in the ConfigEntry used for alter requests where source is not set`; - -const Configs: React.FC = () => { - const [keyword, setKeyword] = React.useState(''); +const Configs: FC = () => { + const [searchQuery, setSearchQuery] = useState(''); const { clusterName, brokerId } = useAppParams(); - const { data = [] } = useBrokerConfig(clusterName, Number(brokerId)); - const stateMutation = useUpdateBrokerConfigByName( + const { data: configs = [] } = useBrokerConfig(clusterName, Number(brokerId)); + const { mutateAsync } = useUpdateBrokerConfigByName( clusterName, Number(brokerId) ); - const getData = () => { - return data - .filter((item) => { - const nameMatch = item.name - .toLocaleLowerCase() - .includes(keyword.toLocaleLowerCase()); - return nameMatch - ? true - : item.value && - item.value - .toLocaleLowerCase() - .includes(keyword.toLocaleLowerCase()); // try to match the keyword on any of the item.value elements when nameMatch fails but item.value exists - }) - .sort((a, b) => { - if (a.source === b.source) return 0; - return a.source === ConfigSource.DYNAMIC_BROKER_CONFIG ? -1 : 1; - }); - }; - - const dataSource = React.useMemo(() => getData(), [data, keyword]); - - const renderCell = (props: CellContext) => ( - { - stateMutation.mutateAsync({ - name, - brokerConfigItem: { - value, - }, - }); - }} - /> + const tableData = useMemo( + () => getConfigTableData(configs, searchQuery), + [configs, searchQuery] ); - const columns = React.useMemo[]>( - () => [ - { header: 'Key', accessorKey: 'name' }, - { - header: 'Value', - accessorKey: 'value', - cell: renderCell, - }, - { - // eslint-disable-next-line react/no-unstable-nested-components - header: () => { - return ( - - Source - } - content={tooltipContent} - placement="top-end" - /> - - ); - }, - accessorKey: 'source', - }, - ], + const onUpdateInputCell = async ( + name: BrokerConfig['name'], + value: BrokerConfig['value'] + ) => mutateAsync({ name, brokerConfigItem: { value } }); + + const columns = useMemo( + () => getBrokerConfigsTableColumns(onUpdateInputCell), [] ); @@ -99,12 +43,12 @@ const Configs: React.FC = () => { <> - +
); }; diff --git a/frontend/src/components/Brokers/Broker/Configs/InputCell.tsx b/frontend/src/components/Brokers/Broker/Configs/InputCell.tsx deleted file mode 100644 index bf54c45c6..000000000 --- a/frontend/src/components/Brokers/Broker/Configs/InputCell.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useEffect } from 'react'; -import { CellContext } from '@tanstack/react-table'; -import CheckmarkIcon from 'components/common/Icons/CheckmarkIcon'; -import EditIcon from 'components/common/Icons/EditIcon'; -import CancelIcon from 'components/common/Icons/CancelIcon'; -import { useConfirm } from 'lib/hooks/useConfirm'; -import { Action, BrokerConfig, ResourceType } from 'generated-sources'; -import { Button } from 'components/common/Button/Button'; -import Input from 'components/common/Input/Input'; -import { ActionButton } from 'components/common/ActionComponent'; - -import * as S from './Configs.styled'; - -interface InputCellProps extends CellContext { - onUpdate: (name: string, value?: string) => void; -} - -const InputCell: React.FC = ({ row, getValue, onUpdate }) => { - const initialValue = `${getValue()}`; - const [isEdit, setIsEdit] = React.useState(false); - const [value, setValue] = React.useState(initialValue); - - const confirm = useConfirm(); - - const onSave = () => { - if (value !== initialValue) { - confirm('Are you sure you want to change the value?', async () => { - onUpdate(row?.original?.name, value); - }); - } - setIsEdit(false); - }; - - useEffect(() => { - setValue(initialValue); - }, [initialValue]); - - return isEdit ? ( - - setValue(target?.value)} - /> - - - - - - ) : ( - - {initialValue} - setIsEdit(true)} - permission={{ - resource: ResourceType.CLUSTERCONFIG, - action: Action.EDIT, - }} - > - Edit - - - ); -}; - -export default InputCell; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.styled.ts b/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.styled.ts new file mode 100644 index 000000000..3856932b1 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.styled.ts @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +export const Source = styled.div` + display: flex; + align-content: center; + + svg { + margin-left: 10px; + vertical-align: middle; + cursor: pointer; + } +`; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.tsx new file mode 100644 index 000000000..e521218e8 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/ConfigSourceHeader/ConfigSourceHeader.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import Tooltip from 'components/common/Tooltip/Tooltip'; +import InfoIcon from 'components/common/Icons/InfoIcon'; +import { CONFIG_SOURCE_NAME_MAP } from 'components/Brokers/Broker/Configs/lib/constants'; + +import * as S from './ConfigSourceHeader.styled'; + +const tooltipContent = `${CONFIG_SOURCE_NAME_MAP.DYNAMIC_TOPIC_CONFIG} = dynamic topic config that is configured for a specific topic +${CONFIG_SOURCE_NAME_MAP.DYNAMIC_BROKER_LOGGER_CONFIG} = dynamic broker logger config that is configured for a specific broker +${CONFIG_SOURCE_NAME_MAP.DYNAMIC_BROKER_CONFIG} = dynamic broker config that is configured for a specific broker +${CONFIG_SOURCE_NAME_MAP.DYNAMIC_DEFAULT_BROKER_CONFIG} = dynamic broker config that is configured as default for all brokers in the cluster +${CONFIG_SOURCE_NAME_MAP.STATIC_BROKER_CONFIG} = static broker config provided as broker properties at start up (e.g. server.properties file) +${CONFIG_SOURCE_NAME_MAP.DEFAULT_CONFIG} = built-in default configuration for configs that have a default value +${CONFIG_SOURCE_NAME_MAP.UNKNOWN} = source unknown e.g. in the ConfigEntry used for alter requests where source is not set`; + +const ConfigSourceHeader = () => ( + + Source + } + content={tooltipContent} + placement="top-end" + /> + +); + +export default ConfigSourceHeader; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellEditMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellEditMode.tsx new file mode 100644 index 000000000..2ac00ca63 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellEditMode.tsx @@ -0,0 +1,53 @@ +import React, { type FC, useState } from 'react'; +import Input from 'components/common/Input/Input'; +import { Button } from 'components/common/Button/Button'; +import CheckmarkIcon from 'components/common/Icons/CheckmarkIcon'; +import CancelIcon from 'components/common/Icons/CancelIcon'; + +import * as S from './styled'; + +interface EditModeProps { + initialValue: string; + onSave: (value: string) => void; + onCancel: () => void; +} + +const InputCellEditMode: FC = ({ + initialValue, + onSave, + onCancel, +}) => { + const [value, setValue] = useState(initialValue); + + return ( + + setValue(target.value)} + /> + + + + + + ); +}; + +export default InputCellEditMode; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx new file mode 100644 index 000000000..b3386dd89 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx @@ -0,0 +1,31 @@ +import React, { type FC } from 'react'; +import { Button } from 'components/common/Button/Button'; +import EditIcon from 'components/common/Icons/EditIcon'; + +import * as S from './styled'; + +interface InputCellViewModeProps { + value: string; + onEdit: () => void; + isDynamic: boolean; +} + +const InputCellViewMode: FC = ({ + value, + onEdit, + isDynamic, +}) => ( + + {value} + + +); + +export default InputCellViewMode; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx new file mode 100644 index 000000000..995fa7313 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx @@ -0,0 +1,49 @@ +import React, { type FC, useState } from 'react'; +import { useConfirm } from 'lib/hooks/useConfirm'; +import { type CellContext } from '@tanstack/react-table'; +import { type BrokerConfig } from 'generated-sources'; +import { + BrokerConfigsTableRow, + UpdateBrokerConfigCallback, +} from 'components/Brokers/Broker/Configs/lib/types'; + +import InputCellViewMode from './InputCellViewMode'; +import InputCellEditMode from './InputCellEditMode'; + +interface InputCellProps + extends CellContext { + onUpdate: UpdateBrokerConfigCallback; +} + +const InputCell: FC = ({ row, getValue, onUpdate }) => { + const initialValue = getValue(); + const [isEdit, setIsEdit] = useState(false); + const confirm = useConfirm(); + + const handleSave = (newValue: string) => { + if (newValue !== initialValue) { + confirm('Are you sure you want to change the value?', () => + onUpdate(row?.original?.name, newValue) + ); + } + setIsEdit(false); + }; + + const isDynamic = row?.original?.source === 'DYNAMIC_BROKER_CONFIG'; + + return isEdit ? ( + setIsEdit(false)} + /> + ) : ( + setIsEdit(true)} + isDynamic={isDynamic} + /> + ); +}; + +export default InputCell; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts new file mode 100644 index 000000000..2e01153c0 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts @@ -0,0 +1,23 @@ +import styled from 'styled-components'; + +export const ValueWrapper = styled.div` + display: flex; + justify-content: space-between; + + button { + margin: 0 10px; + } +`; + +export const Value = styled.span` + line-height: 24px; + margin-right: 10px; + text-overflow: ellipsis; + max-width: 400px; + overflow: hidden; + white-space: nowrap; +`; + +export const ButtonsWrapper = styled.div` + display: flex; +`; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/index.ts b/frontend/src/components/Brokers/Broker/Configs/TableComponents/index.ts new file mode 100644 index 000000000..308200222 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/index.ts @@ -0,0 +1,2 @@ +export { default as InputCell } from './InputCell'; +export { default as ConfigSourceHeader } from './ConfigSourceHeader/ConfigSourceHeader'; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts new file mode 100644 index 000000000..094a210d0 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts @@ -0,0 +1,11 @@ +import { ConfigSource } from 'generated-sources'; + +export const CONFIG_SOURCE_NAME_MAP: Record = { + [ConfigSource.DYNAMIC_TOPIC_CONFIG]: 'Dynamic topic config', + [ConfigSource.DYNAMIC_BROKER_LOGGER_CONFIG]: 'Dynamic broker logger config', + [ConfigSource.DYNAMIC_BROKER_CONFIG]: 'Dynamic broker config', + [ConfigSource.DYNAMIC_DEFAULT_BROKER_CONFIG]: 'Dynamic default broker config', + [ConfigSource.STATIC_BROKER_CONFIG]: 'Static broker config', + [ConfigSource.DEFAULT_CONFIG]: 'Default config', + [ConfigSource.UNKNOWN]: 'Unknown', +}; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/types.ts b/frontend/src/components/Brokers/Broker/Configs/lib/types.ts new file mode 100644 index 000000000..3f485eab9 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/lib/types.ts @@ -0,0 +1,11 @@ +import { type BrokerConfig } from 'generated-sources'; + +export type BrokerConfigsTableRow = Pick< + BrokerConfig, + 'name' | 'value' | 'source' +>; + +export type UpdateBrokerConfigCallback = ( + name: BrokerConfig['name'], + value: BrokerConfig['value'] +) => Promise; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx new file mode 100644 index 000000000..2f0b49ce0 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { type BrokerConfig, ConfigSource } from 'generated-sources'; +import { createColumnHelper } from '@tanstack/react-table'; +import * as BrokerConfigTableComponents from 'components/Brokers/Broker/Configs/TableComponents/index'; +import { + BrokerConfigsTableRow, + UpdateBrokerConfigCallback, +} from 'components/Brokers/Broker/Configs/lib/types'; + +const getConfigFieldMatch = (field: string, query: string) => + field.toLocaleLowerCase().includes(query.toLocaleLowerCase()); + +const filterConfigsBySearchQuery = + (searchQuery: string) => (config: BrokerConfig) => { + const nameMatch = getConfigFieldMatch(config.name, searchQuery); + const valueMatch = + config.value && getConfigFieldMatch(config.value, searchQuery); + + return nameMatch ? true : valueMatch; + }; + +const sortBrokersBySource = (a: BrokerConfig, b: BrokerConfig) => { + if (a.source === b.source) return 0; + return a.source === ConfigSource.DYNAMIC_BROKER_CONFIG ? -1 : 1; +}; + +export const getConfigTableData = ( + configs: BrokerConfig[], + searchQuery: string +) => + configs + .filter(filterConfigsBySearchQuery(searchQuery)) + .sort(sortBrokersBySource); + +export const getBrokerConfigsTableColumns = ( + onUpdateInputCell: UpdateBrokerConfigCallback +) => { + const columnHelper = createColumnHelper(); + + return [ + columnHelper.accessor('name', { header: 'Key' }), + columnHelper.accessor('value', { + header: 'Value', + cell: (props) => ( + + ), + }), + columnHelper.accessor('source', { + header: BrokerConfigTableComponents.ConfigSourceHeader, + }), + ]; +}; From 72979291c5386be5190d45219c22295167d8d5f0 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 15:01:13 +0800 Subject: [PATCH 02/12] Config units. --- .../InputCell/InputCellViewMode.tsx | 33 +++++++++++-------- .../TableComponents/InputCell/index.tsx | 11 ++++--- .../TableComponents/InputCell/styled.ts | 3 +- .../Brokers/Broker/Configs/lib/types.ts | 2 ++ .../Brokers/Broker/Configs/lib/utils.tsx | 16 ++++++++- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx index b3386dd89..365f4688f 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx @@ -1,31 +1,38 @@ import React, { type FC } from 'react'; import { Button } from 'components/common/Button/Button'; import EditIcon from 'components/common/Icons/EditIcon'; +import type { ConfigUnit } from 'components/Brokers/Broker/Configs/lib/types'; import * as S from './styled'; interface InputCellViewModeProps { value: string; + unit: ConfigUnit | undefined; onEdit: () => void; isDynamic: boolean; } const InputCellViewMode: FC = ({ value, + unit, onEdit, isDynamic, -}) => ( - - {value} - - -); +}) => { + const valueWithUnit = unit ? `${value} ${unit}` : value; + + return ( + + {valueWithUnit} + + + ); +}; export default InputCellViewMode; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx index 995fa7313..957607af7 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx @@ -6,6 +6,7 @@ import { BrokerConfigsTableRow, UpdateBrokerConfigCallback, } from 'components/Brokers/Broker/Configs/lib/types'; +import { getConfigUnit } from 'components/Brokers/Broker/Configs/lib/utils'; import InputCellViewMode from './InputCellViewMode'; import InputCellEditMode from './InputCellEditMode'; @@ -15,21 +16,22 @@ interface InputCellProps onUpdate: UpdateBrokerConfigCallback; } -const InputCell: FC = ({ row, getValue, onUpdate }) => { - const initialValue = getValue(); +const InputCell: FC = ({ row, onUpdate }) => { const [isEdit, setIsEdit] = useState(false); const confirm = useConfirm(); + const { name, source, value: initialValue } = row.original; const handleSave = (newValue: string) => { if (newValue !== initialValue) { confirm('Are you sure you want to change the value?', () => - onUpdate(row?.original?.name, newValue) + onUpdate(name, newValue) ); } setIsEdit(false); }; - const isDynamic = row?.original?.source === 'DYNAMIC_BROKER_CONFIG'; + const isDynamic = source === 'DYNAMIC_BROKER_CONFIG'; + const configUnit = getConfigUnit(name); return isEdit ? ( = ({ row, getValue, onUpdate }) => { /> ) : ( setIsEdit(true)} isDynamic={isDynamic} diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts index 2e01153c0..593113ad0 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts @@ -1,8 +1,9 @@ import styled from 'styled-components'; -export const ValueWrapper = styled.div` +export const ValueWrapper = styled.div<{ $isDynamic: boolean }>` display: flex; justify-content: space-between; + font-weight: ${({ $isDynamic }) => ($isDynamic ? 600 : 400)}; button { margin: 0 10px; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/types.ts b/frontend/src/components/Brokers/Broker/Configs/lib/types.ts index 3f485eab9..4f9a68b65 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/types.ts +++ b/frontend/src/components/Brokers/Broker/Configs/lib/types.ts @@ -9,3 +9,5 @@ export type UpdateBrokerConfigCallback = ( name: BrokerConfig['name'], value: BrokerConfig['value'] ) => Promise; + +export type ConfigUnit = 'ms' | 'bytes'; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx index 2f0b49ce0..b95c09a45 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { type BrokerConfig, ConfigSource } from 'generated-sources'; import { createColumnHelper } from '@tanstack/react-table'; import * as BrokerConfigTableComponents from 'components/Brokers/Broker/Configs/TableComponents/index'; -import { +import type { BrokerConfigsTableRow, + ConfigUnit, UpdateBrokerConfigCallback, } from 'components/Brokers/Broker/Configs/lib/types'; @@ -53,3 +54,16 @@ export const getBrokerConfigsTableColumns = ( }), ]; }; + +const unitPatterns = { + ms: /\.ms$/, + bytes: /\.bytes$/, +}; + +export const getConfigUnit = (configName: string): ConfigUnit | undefined => { + const found = Object.entries(unitPatterns).find(([_, pattern]) => + pattern.test(configName) + ); + + return found ? (found[0] as ConfigUnit) : undefined; +}; From cd7bda67506b2c580eef0a4839ad605cabd69c83 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 16:27:39 +0800 Subject: [PATCH 03/12] Human readable source column. --- frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx index b95c09a45..356521a5c 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx @@ -7,6 +7,7 @@ import type { ConfigUnit, UpdateBrokerConfigCallback, } from 'components/Brokers/Broker/Configs/lib/types'; +import { CONFIG_SOURCE_NAME_MAP } from 'components/Brokers/Broker/Configs/lib/constants'; const getConfigFieldMatch = (field: string, query: string) => field.toLocaleLowerCase().includes(query.toLocaleLowerCase()); @@ -51,6 +52,7 @@ export const getBrokerConfigsTableColumns = ( }), columnHelper.accessor('source', { header: BrokerConfigTableComponents.ConfigSourceHeader, + cell: ({ getValue }) => CONFIG_SOURCE_NAME_MAP[getValue()], }), ]; }; From e7d3ead8e1aadd47ea3dd2a1ae86b16df1d38edd Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 16:45:43 +0800 Subject: [PATCH 04/12] Mask sensitive params values by default (isSensitive: true) --- .../InputCell/InputCellViewMode.tsx | 15 +++++++++++++-- .../Configs/TableComponents/InputCell/index.tsx | 7 ++++--- .../Configs/TableComponents/InputCell/styled.ts | 2 +- .../Brokers/Broker/Configs/lib/types.ts | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx index 365f4688f..2e0820958 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx @@ -10,6 +10,7 @@ interface InputCellViewModeProps { unit: ConfigUnit | undefined; onEdit: () => void; isDynamic: boolean; + isSensitive: boolean; } const InputCellViewMode: FC = ({ @@ -17,12 +18,22 @@ const InputCellViewMode: FC = ({ unit, onEdit, isDynamic, + isSensitive, }) => { - const valueWithUnit = unit ? `${value} ${unit}` : value; + let displayValue: string; + let title: string; + + if (isSensitive) { + displayValue = '**********'; + title = 'Sensitive Value'; + } else { + displayValue = unit ? `${value} ${unit}` : value; + title = displayValue; + } return ( - {valueWithUnit} + {displayValue} + + Edit + + } + showTooltip={isReadOnly} + content="Property is read-only" + placement="left" + /> ); }; diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx index cebc20202..b00e26f7a 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx @@ -19,7 +19,13 @@ interface InputCellProps const InputCell: FC = ({ row, onUpdate }) => { const [isEdit, setIsEdit] = useState(false); const confirm = useConfirm(); - const { name, source, value: initialValue, isSensitive } = row.original; + const { + name, + source, + value: initialValue, + isSensitive, + isReadOnly, + } = row.original; const handleSave = (newValue: string) => { if (newValue !== initialValue) { @@ -46,6 +52,7 @@ const InputCell: FC = ({ row, onUpdate }) => { onEdit={() => setIsEdit(true)} isDynamic={isDynamic} isSensitive={isSensitive} + isReadOnly={isReadOnly} /> ); }; diff --git a/frontend/src/components/common/Tooltip/Tooltip.tsx b/frontend/src/components/common/Tooltip/Tooltip.tsx index 0764320f5..6b74391ca 100644 --- a/frontend/src/components/common/Tooltip/Tooltip.tsx +++ b/frontend/src/components/common/Tooltip/Tooltip.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { + Placement, useFloating, useHover, useInteractions, - Placement, } from '@floating-ui/react'; import * as S from './Tooltip.styled'; @@ -12,9 +12,15 @@ interface TooltipProps { value: React.ReactNode; content: string; placement?: Placement; + showTooltip?: boolean; } -const Tooltip: React.FC = ({ value, content, placement }) => { +const Tooltip: React.FC = ({ + value, + content, + placement, + showTooltip = true, +}) => { const [open, setOpen] = useState(false); const { x, y, refs, strategy, context } = useFloating({ open, @@ -28,7 +34,7 @@ const Tooltip: React.FC = ({ value, content, placement }) => {
{value}
- {open && ( + {showTooltip && open && ( Date: Wed, 28 Feb 2024 17:02:42 +0800 Subject: [PATCH 06/12] Loading state for ConfirmationModal --- .../ConfirmationModal/ConfirmationModal.tsx | 1 + .../components/contexts/ConfirmContext.tsx | 35 ++++++++++++------- frontend/src/lib/hooks/useConfirm.ts | 19 ++++++---- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/common/ConfirmationModal/ConfirmationModal.tsx b/frontend/src/components/common/ConfirmationModal/ConfirmationModal.tsx index 1b882c946..98a9a51eb 100644 --- a/frontend/src/components/common/ConfirmationModal/ConfirmationModal.tsx +++ b/frontend/src/components/common/ConfirmationModal/ConfirmationModal.tsx @@ -30,6 +30,7 @@ const ConfirmationModal: React.FC = () => { buttonSize="M" onClick={context.confirm} type="button" + inProgress={context?.isConfirming} > Confirm diff --git a/frontend/src/components/contexts/ConfirmContext.tsx b/frontend/src/components/contexts/ConfirmContext.tsx index d68eda254..21c22dc81 100644 --- a/frontend/src/components/contexts/ConfirmContext.tsx +++ b/frontend/src/components/contexts/ConfirmContext.tsx @@ -1,25 +1,34 @@ -import React, { useState } from 'react'; +import React, { + createContext, + type Dispatch, + type FC, + type PropsWithChildren, + type ReactNode, + type SetStateAction, + useState, +} from 'react'; interface ConfirmContextType { - content: React.ReactNode; + content: ReactNode; confirm?: () => void; - setContent: React.Dispatch>; - setConfirm: React.Dispatch void) | undefined>>; + setContent: Dispatch>; + setConfirm: Dispatch void) | undefined>>; cancel: () => void; dangerButton: boolean; - setDangerButton: React.Dispatch>; + setDangerButton: Dispatch>; + isConfirming: boolean; + setIsConfirming: Dispatch>; } -export const ConfirmContext = React.createContext( - null -); +export const ConfirmContext = createContext(null); -export const ConfirmContextProvider: React.FC< - React.PropsWithChildren -> = ({ children }) => { - const [content, setContent] = useState(null); +export const ConfirmContextProvider: FC> = ({ + children, +}) => { + const [content, setContent] = useState(null); const [confirm, setConfirm] = useState<(() => void) | undefined>(undefined); const [dangerButton, setDangerButton] = useState(false); + const [isConfirming, setIsConfirming] = useState(false); const cancel = () => { setContent(null); @@ -36,6 +45,8 @@ export const ConfirmContextProvider: React.FC< cancel, dangerButton, setDangerButton, + isConfirming, + setIsConfirming, }} > {children} diff --git a/frontend/src/lib/hooks/useConfirm.ts b/frontend/src/lib/hooks/useConfirm.ts index baac856c5..117db2401 100644 --- a/frontend/src/lib/hooks/useConfirm.ts +++ b/frontend/src/lib/hooks/useConfirm.ts @@ -1,17 +1,22 @@ import { ConfirmContext } from 'components/contexts/ConfirmContext'; -import React, { useContext } from 'react'; +import { type ReactNode, useContext } from 'react'; export const useConfirm = (danger = false) => { const context = useContext(ConfirmContext); - return ( - message: React.ReactNode, - callback: () => void | Promise - ) => { + + return (message: ReactNode, callback: () => void | Promise) => { context?.setDangerButton(danger); context?.setContent(message); + context?.setIsConfirming(false); context?.setConfirm(() => async () => { - await callback(); - context?.cancel(); + context?.setIsConfirming(true); + + try { + await callback(); + } finally { + context?.setIsConfirming(false); + context?.cancel(); + } }); }; }; From 8a87b3d5dbacc99ec33e74b299efa3112e237643 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 17:12:59 +0800 Subject: [PATCH 07/12] Config order priority in table. --- .../Brokers/Broker/Configs/lib/constants.ts | 14 ++++++++++++++ .../Brokers/Broker/Configs/lib/utils.tsx | 14 ++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts index 094a210d0..802ae926d 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts +++ b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts @@ -9,3 +9,17 @@ export const CONFIG_SOURCE_NAME_MAP: Record = { [ConfigSource.DEFAULT_CONFIG]: 'Default config', [ConfigSource.UNKNOWN]: 'Unknown', }; + +export const CONFIG_SOURCE_PRIORITY: Record< + ConfigSource | 'UNHANDLED', + number +> = { + [ConfigSource.DYNAMIC_TOPIC_CONFIG]: 1, + [ConfigSource.DYNAMIC_BROKER_LOGGER_CONFIG]: 1, + [ConfigSource.DYNAMIC_BROKER_CONFIG]: 1, + [ConfigSource.DYNAMIC_DEFAULT_BROKER_CONFIG]: 1, + [ConfigSource.STATIC_BROKER_CONFIG]: 2, + [ConfigSource.DEFAULT_CONFIG]: 3, + [ConfigSource.UNKNOWN]: 4, + UNHANDLED: 5, +}; diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx index 356521a5c..06cca6eb0 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { type BrokerConfig, ConfigSource } from 'generated-sources'; import { createColumnHelper } from '@tanstack/react-table'; import * as BrokerConfigTableComponents from 'components/Brokers/Broker/Configs/TableComponents/index'; + import type { BrokerConfigsTableRow, ConfigUnit, UpdateBrokerConfigCallback, -} from 'components/Brokers/Broker/Configs/lib/types'; -import { CONFIG_SOURCE_NAME_MAP } from 'components/Brokers/Broker/Configs/lib/constants'; +} from './types'; +import { CONFIG_SOURCE_NAME_MAP, CONFIG_SOURCE_PRIORITY } from './constants'; const getConfigFieldMatch = (field: string, query: string) => field.toLocaleLowerCase().includes(query.toLocaleLowerCase()); @@ -21,9 +22,14 @@ const filterConfigsBySearchQuery = return nameMatch ? true : valueMatch; }; +const getConfigSourcePriority = (source: ConfigSource): number => + CONFIG_SOURCE_PRIORITY[source]; + const sortBrokersBySource = (a: BrokerConfig, b: BrokerConfig) => { - if (a.source === b.source) return 0; - return a.source === ConfigSource.DYNAMIC_BROKER_CONFIG ? -1 : 1; + const priorityA = getConfigSourcePriority(a.source); + const priorityB = getConfigSourcePriority(b.source); + + return priorityA - priorityB; }; export const getConfigTableData = ( From 72354738037d55052209a0d024ae48fcc7b0a455 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 17:21:32 +0800 Subject: [PATCH 08/12] removed unused var --- frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx index 06cca6eb0..25b989fa2 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/lib/utils.tsx @@ -69,7 +69,7 @@ const unitPatterns = { }; export const getConfigUnit = (configName: string): ConfigUnit | undefined => { - const found = Object.entries(unitPatterns).find(([_, pattern]) => + const found = Object.entries(unitPatterns).find(([, pattern]) => pattern.test(configName) ); From 20307d2b7ed8c81808fdd23b07262c39fa7549c8 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Wed, 28 Feb 2024 19:08:15 +0800 Subject: [PATCH 09/12] Tests --- .../Brokers/Broker/Configs/Configs.tsx | 5 +- .../InputCell/__test__/InputCell.spec.tsx | 72 +++++++++++++++++++ .../__test__/InputCellEditMode.spec.tsx | 42 +++++++++++ .../__test__/InputCellViewMode.spec.tsx | 69 ++++++++++++++++++ .../TableComponents/InputCell/index.tsx | 2 +- .../Configs/lib/__test__/utils.spec.tsx | 47 ++++++++++++ 6 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCell.spec.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellEditMode.spec.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellViewMode.spec.tsx create mode 100644 frontend/src/components/Brokers/Broker/Configs/lib/__test__/utils.spec.tsx diff --git a/frontend/src/components/Brokers/Broker/Configs/Configs.tsx b/frontend/src/components/Brokers/Broker/Configs/Configs.tsx index c098c53c6..54b9bd6a9 100644 --- a/frontend/src/components/Brokers/Broker/Configs/Configs.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/Configs.tsx @@ -19,7 +19,7 @@ const Configs: FC = () => { const [searchQuery, setSearchQuery] = useState(''); const { clusterName, brokerId } = useAppParams(); const { data: configs = [] } = useBrokerConfig(clusterName, Number(brokerId)); - const { mutateAsync } = useUpdateBrokerConfigByName( + const updateBrokerConfigByName = useUpdateBrokerConfigByName( clusterName, Number(brokerId) ); @@ -32,7 +32,8 @@ const Configs: FC = () => { const onUpdateInputCell = async ( name: BrokerConfig['name'], value: BrokerConfig['value'] - ) => mutateAsync({ name, brokerConfigItem: { value } }); + ) => + updateBrokerConfigByName.mutateAsync({ name, brokerConfigItem: { value } }); const columns = useMemo( () => getBrokerConfigsTableColumns(onUpdateInputCell), diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCell.spec.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCell.spec.tsx new file mode 100644 index 000000000..421b00f21 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCell.spec.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import InputCell, { + type InputCellProps, +} from 'components/Brokers/Broker/Configs/TableComponents/InputCell/index'; +import { render } from 'lib/testHelpers'; +import { ConfigSource } from 'generated-sources'; +import { useConfirm } from 'lib/hooks/useConfirm'; +import { BrokerConfigsTableRow } from 'components/Brokers/Broker/Configs/lib/types'; +import { Row } from '@tanstack/react-table'; + +jest.mock('lib/hooks/useConfirm', () => ({ + useConfirm: jest.fn(), +})); + +describe('InputCell', () => { + const mockOnUpdate = jest.fn(); + const initialValue = 'initialValue'; + const name = 'testName'; + const original = { + name, + source: ConfigSource.DYNAMIC_BROKER_CONFIG, + value: initialValue, + isSensitive: false, + isReadOnly: false, + }; + + beforeEach(() => { + const setupWrapper = (props?: Partial) => ( + } + onUpdate={mockOnUpdate} + /> + ); + (useConfirm as jest.Mock).mockImplementation( + () => (message: string, callback: () => void) => callback() + ); + render(setupWrapper()); + }); + + it('renders InputCellViewMode by default', () => { + expect(screen.getByText(initialValue)).toBeInTheDocument(); + }); + + it('switches to InputCellEditMode upon triggering an edit action', async () => { + const user = userEvent.setup(); + await user.click(screen.getByLabelText('editAction')); + expect( + screen.getByRole('textbox', { name: /inputValue/i }) + ).toBeInTheDocument(); + }); + + it('calls onUpdate callback with the new value when saved', async () => { + const user = userEvent.setup(); + await user.click(screen.getByLabelText('editAction')); // Enter edit mode + await user.type( + screen.getByRole('textbox', { name: /inputValue/i }), + '123' + ); + await user.click(screen.getByRole('button', { name: /confirmAction/i })); + expect(mockOnUpdate).toHaveBeenCalledWith(name, 'initialValue123'); + }); + + it('returns to InputCellViewMode upon canceling an edit', async () => { + const user = userEvent.setup(); + await user.click(screen.getByLabelText('editAction')); // Enter edit mode + await user.click(screen.getByRole('button', { name: /cancelAction/i })); + expect(screen.getByText(initialValue)).toBeInTheDocument(); // Back to view mode + }); +}); diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellEditMode.spec.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellEditMode.spec.tsx new file mode 100644 index 000000000..91270469e --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellEditMode.spec.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import InputCellEditMode from 'components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellEditMode'; +import { render } from 'lib/testHelpers'; +import userEvent from '@testing-library/user-event'; + +describe('InputCellEditMode', () => { + const mockOnSave = jest.fn(); + const mockOnCancel = jest.fn(); + + beforeEach(() => { + render( + + ); + }); + + it('renders with initial value', () => { + expect(screen.getByRole('textbox', { name: /inputValue/i })).toHaveValue( + 'test' + ); + }); + + it('calls onSave with new value', async () => { + const user = userEvent.setup(); + await user.type( + screen.getByRole('textbox', { name: /inputValue/i }), + '123' + ); + await user.click(screen.getByRole('button', { name: /confirmAction/i })); + expect(mockOnSave).toHaveBeenCalledWith('test123'); + }); + + it('calls onCancel', async () => { + const user = userEvent.setup(); + await user.click(screen.getByRole('button', { name: /cancelAction/i })); + expect(mockOnCancel).toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellViewMode.spec.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellViewMode.spec.tsx new file mode 100644 index 000000000..9499fa857 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/__test__/InputCellViewMode.spec.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { render } from 'lib/testHelpers'; +import InputCellViewMode from 'components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode'; + +describe('InputCellViewMode', () => { + const mockOnEdit = jest.fn(); + const value = 'testValue'; + + it('displays the correct value for non-sensitive data', () => { + render( + + ); + expect(screen.getByTitle(value)).toBeInTheDocument(); + }); + + it('masks sensitive data with asterisks', () => { + render( + + ); + expect(screen.getByTitle('Sensitive Value')).toBeInTheDocument(); + expect(screen.getByText('**********')).toBeInTheDocument(); + }); + + it('renders edit button and triggers onEdit callback when clicked', async () => { + const user = userEvent.setup(); + render( + + ); + await user.click(screen.getByLabelText('editAction')); + expect(mockOnEdit).toHaveBeenCalled(); + }); + + it('disables edit button for read-only properties', () => { + render( + + ); + expect(screen.getByLabelText('editAction')).toBeDisabled(); + }); +}); diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx index b00e26f7a..a55d7852c 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/index.tsx @@ -11,7 +11,7 @@ import { getConfigUnit } from 'components/Brokers/Broker/Configs/lib/utils'; import InputCellViewMode from './InputCellViewMode'; import InputCellEditMode from './InputCellEditMode'; -interface InputCellProps +export interface InputCellProps extends CellContext { onUpdate: UpdateBrokerConfigCallback; } diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/__test__/utils.spec.tsx b/frontend/src/components/Brokers/Broker/Configs/lib/__test__/utils.spec.tsx new file mode 100644 index 000000000..fa9e04be1 --- /dev/null +++ b/frontend/src/components/Brokers/Broker/Configs/lib/__test__/utils.spec.tsx @@ -0,0 +1,47 @@ +import { + getConfigTableData, + getConfigUnit, +} from 'components/Brokers/Broker/Configs/lib/utils'; +import { ConfigSource } from 'generated-sources'; + +describe('getConfigTableData', () => { + it('filters configs by search query and sorts by source priority', () => { + const configs = [ + { + name: 'log.retention.ms', + value: '7200000', + source: ConfigSource.DEFAULT_CONFIG, + isSensitive: true, + isReadOnly: false, + }, + { + name: 'log.segment.bytes', + value: '1073741824', + source: ConfigSource.DYNAMIC_BROKER_CONFIG, + isSensitive: false, + isReadOnly: true, + }, + { + name: 'compression.type', + value: 'producer', + source: ConfigSource.DEFAULT_CONFIG, + isSensitive: true, + isReadOnly: false, + }, + ]; + const searchQuery = 'log'; + const result = getConfigTableData(configs, searchQuery); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('log.segment.bytes'); + expect(result[1].name).toBe('log.retention.ms'); + }); +}); + +describe('getConfigUnit', () => { + it('identifies the unit of a configuration name', () => { + expect(getConfigUnit('log.retention.ms')).toBe('ms'); + expect(getConfigUnit('log.segment.bytes')).toBe('bytes'); + expect(getConfigUnit('compression.type')).toBeUndefined(); + }); +}); From 5644ccbf43978704c1f231196dfffdc22c4d35f0 Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Fri, 1 Mar 2024 20:38:35 +0800 Subject: [PATCH 10/12] Tests --- .../components/Brokers/Broker/Configs/lib/constants.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts index 802ae926d..5a829323a 100644 --- a/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts +++ b/frontend/src/components/Brokers/Broker/Configs/lib/constants.ts @@ -8,12 +8,9 @@ export const CONFIG_SOURCE_NAME_MAP: Record = { [ConfigSource.STATIC_BROKER_CONFIG]: 'Static broker config', [ConfigSource.DEFAULT_CONFIG]: 'Default config', [ConfigSource.UNKNOWN]: 'Unknown', -}; +} as const; -export const CONFIG_SOURCE_PRIORITY: Record< - ConfigSource | 'UNHANDLED', - number -> = { +export const CONFIG_SOURCE_PRIORITY = { [ConfigSource.DYNAMIC_TOPIC_CONFIG]: 1, [ConfigSource.DYNAMIC_BROKER_LOGGER_CONFIG]: 1, [ConfigSource.DYNAMIC_BROKER_CONFIG]: 1, @@ -22,4 +19,4 @@ export const CONFIG_SOURCE_PRIORITY: Record< [ConfigSource.DEFAULT_CONFIG]: 3, [ConfigSource.UNKNOWN]: 4, UNHANDLED: 5, -}; +} as const; From c0a478c59d0e1c66f27e877f1c68583d2d1ab3ed Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Fri, 1 Mar 2024 21:32:20 +0800 Subject: [PATCH 11/12] review fixes. --- frontend/src/components/contexts/ConfirmContext.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/contexts/ConfirmContext.tsx b/frontend/src/components/contexts/ConfirmContext.tsx index 21c22dc81..b4807bd1d 100644 --- a/frontend/src/components/contexts/ConfirmContext.tsx +++ b/frontend/src/components/contexts/ConfirmContext.tsx @@ -22,9 +22,7 @@ interface ConfirmContextType { export const ConfirmContext = createContext(null); -export const ConfirmContextProvider: FC> = ({ - children, -}) => { +export const ConfirmContextProvider: FC = ({ children }) => { const [content, setContent] = useState(null); const [confirm, setConfirm] = useState<(() => void) | undefined>(undefined); const [dangerButton, setDangerButton] = useState(false); From 3a79107a8fab9ca568f953dc55234854514b3e8e Mon Sep 17 00:00:00 2001 From: alexeykozyurov Date: Tue, 12 Mar 2024 15:09:12 +0800 Subject: [PATCH 12/12] Bytes formatted displaying. --- .../TableComponents/InputCell/InputCellViewMode.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx index 46bc7d093..8e7c19376 100644 --- a/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx +++ b/frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/InputCellViewMode.tsx @@ -1,8 +1,9 @@ -import React, { type FC } from 'react'; +import React, { type FC, ReactNode } from 'react'; import { Button } from 'components/common/Button/Button'; import EditIcon from 'components/common/Icons/EditIcon'; import type { ConfigUnit } from 'components/Brokers/Broker/Configs/lib/types'; import Tooltip from 'components/common/Tooltip/Tooltip'; +import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted'; import * as S from './styled'; @@ -23,15 +24,18 @@ const InputCellViewMode: FC = ({ isSensitive, isReadOnly, }) => { - let displayValue: string; + let displayValue: ReactNode | string; let title: string; if (isSensitive) { displayValue = '**********'; title = 'Sensitive Value'; + } else if (unit === 'bytes' && parseInt(value, 10) > 0) { + displayValue = ; + title = `Bytes: ${value}`; } else { displayValue = unit ? `${value} ${unit}` : value; - title = displayValue; + title = displayValue.toString(); } return (