From db86942e47621cf2a2ded26d2c7cdbd2b0ee202a Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 8 May 2023 11:52:11 +0400 Subject: [PATCH 1/5] FE: Add a clear button to the search component (#3634) --- .../components/common/Input/Input.styled.ts | 10 ++++++ .../src/components/common/Input/Input.tsx | 36 +++++++++++-------- .../src/components/common/Search/Search.tsx | 33 ++++++++++++++++- .../common/Search/__tests__/Search.spec.tsx | 20 +++++++++++ 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/kafka-ui-react-app/src/components/common/Input/Input.styled.ts b/kafka-ui-react-app/src/components/common/Input/Input.styled.ts index 9495aaecbe..f21962fe6b 100644 --- a/kafka-ui-react-app/src/components/common/Input/Input.styled.ts +++ b/kafka-ui-react-app/src/components/common/Input/Input.styled.ts @@ -29,6 +29,16 @@ export const Wrapper = styled.div` width: 16px; fill: ${({ theme }) => theme.input.icon.color}; } + svg:last-child { + position: absolute; + top: 8px; + line-height: 0; + z-index: 1; + left: unset; + right: 12px; + height: 16px; + width: 16px; + } `; export const Input = styled.input( diff --git a/kafka-ui-react-app/src/components/common/Input/Input.tsx b/kafka-ui-react-app/src/components/common/Input/Input.tsx index ae76bc4717..4d04b730e5 100644 --- a/kafka-ui-react-app/src/components/common/Input/Input.tsx +++ b/kafka-ui-react-app/src/components/common/Input/Input.tsx @@ -16,6 +16,7 @@ export interface InputProps withError?: boolean; label?: React.ReactNode; hint?: React.ReactNode; + clearIcon?: React.ReactNode; // Some may only accept integer, like `Number of Partitions` // some may accept decimal @@ -99,19 +100,22 @@ function pasteNumberCheck( return value; } -const Input: React.FC = ({ - name, - hookFormOptions, - search, - inputSize = 'L', - type, - positiveOnly, - integerOnly, - withError = false, - label, - hint, - ...rest -}) => { +const Input = React.forwardRef((props, ref) => { + const { + name, + hookFormOptions, + search, + inputSize = 'L', + type, + positiveOnly, + integerOnly, + withError = false, + label, + hint, + clearIcon, + ...rest + } = props; + const methods = useFormContext(); const fieldId = React.useId(); @@ -168,7 +172,6 @@ const Input: React.FC = ({ // if the field is a part of react-hook-form form inputOptions = { ...rest, ...methods.register(name, hookFormOptions) }; } - return (
{label && {label}} @@ -181,8 +184,11 @@ const Input: React.FC = ({ type={type} onKeyPress={keyPressEventHandler} onPaste={pasteEventHandler} + ref={ref} {...inputOptions} /> + {clearIcon} + {withError && isHookFormField && ( @@ -192,6 +198,6 @@ const Input: React.FC = ({
); -}; +}); export default Input; diff --git a/kafka-ui-react-app/src/components/common/Search/Search.tsx b/kafka-ui-react-app/src/components/common/Search/Search.tsx index 66c0e95030..65116d645a 100644 --- a/kafka-ui-react-app/src/components/common/Search/Search.tsx +++ b/kafka-ui-react-app/src/components/common/Search/Search.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { useDebouncedCallback } from 'use-debounce'; import Input from 'components/common/Input/Input'; import { useSearchParams } from 'react-router-dom'; +import CloseIcon from 'components/common/Icons/CloseIcon'; +import styled from 'styled-components'; interface SearchProps { placeholder?: string; @@ -10,6 +12,16 @@ interface SearchProps { value?: string; } +const IconButtonWrapper = styled.span.attrs(() => ({ + role: 'button', + tabIndex: '0', +}))` + height: 16px !important; + display: inline-block; + &:hover { + cursor: pointer; + } +`; const Search: React.FC = ({ placeholder = 'Search', disabled = false, @@ -17,7 +29,11 @@ const Search: React.FC = ({ onChange, }) => { const [searchParams, setSearchParams] = useSearchParams(); + const ref = useRef(null); const handleChange = useDebouncedCallback((e) => { + if (ref.current != null) { + ref.current.value = e.target.value; + } if (onChange) { onChange(e.target.value); } else { @@ -28,6 +44,15 @@ const Search: React.FC = ({ setSearchParams(searchParams); } }, 500); + const clearSearchValue = () => { + if (searchParams.get('q')) { + searchParams.set('q', ''); + setSearchParams(searchParams); + } + if (ref.current != null) { + ref.current.value = ''; + } + }; return ( = ({ defaultValue={value || searchParams.get('q') || ''} inputSize="M" disabled={disabled} + ref={ref} search + clearIcon={ + + + + } /> ); }; diff --git a/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx index 808f229317..2103d22336 100644 --- a/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx +++ b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx @@ -41,4 +41,24 @@ describe('Search', () => { render(); expect(screen.queryByPlaceholderText('Search')).toBeInTheDocument(); }); + + it('Clear button is visible', () => { + render(); + + const clearButton = screen.getByRole('button'); + expect(clearButton).toBeInTheDocument(); + }); + + it('Clear button should clear text from input', async () => { + render(); + + const searchField = screen.getAllByRole('textbox')[0]; + await userEvent.type(searchField, 'some text'); + expect(searchField).toHaveValue('some text'); + + const clearButton = screen.getByRole('button'); + await userEvent.click(clearButton); + + expect(searchField).toHaveValue(''); + }); }); From 61fb62276e8aee6b7730cd8a76e9a54cb7e76d44 Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 8 May 2023 11:53:57 +0400 Subject: [PATCH 2/5] FE: Serde fallback icon: Add a tooltip (#3786) --- .../Topics/Topic/Messages/Message.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx index dd5cfae748..af76db6739 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx @@ -8,6 +8,7 @@ import { formatTimestamp } from 'lib/dateTimeHelpers'; import { JSONPath } from 'jsonpath-plus'; import Ellipsis from 'components/common/Ellipsis/Ellipsis'; import WarningRedIcon from 'components/common/Icons/WarningRedIcon'; +import Tooltip from 'components/common/Tooltip/Tooltip'; import MessageContent from './MessageContent/MessageContent'; import * as S from './MessageContent/MessageContent.styled'; @@ -110,14 +111,26 @@ const Message: React.FC = ({ - {keySerde === 'Fallback' && } + {keySerde === 'Fallback' && ( + } + content="Fallback serde was used" + placement="left" + /> + )} - {valueSerde === 'Fallback' && } + {valueSerde === 'Fallback' && ( + } + content="Fallback serde was used" + placement="left" + /> + )} From 97a694b3f04ceec2f103a6c8836d32c686e72c40 Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 8 May 2023 11:55:23 +0400 Subject: [PATCH 3/5] FE: Messages: Produce pane is too long (#3785) --- .../src/components/Topics/Topic/SendMessage/SendMessage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx index bacfa76c93..b7f31a230b 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx @@ -210,6 +210,7 @@ const SendMessage: React.FC<{ closeSidebar: () => void }> = ({ name={name} onChange={onChange} value={value} + height="40px" /> )} /> @@ -225,6 +226,7 @@ const SendMessage: React.FC<{ closeSidebar: () => void }> = ({ name={name} onChange={onChange} value={value} + height="280px" /> )} /> @@ -242,7 +244,7 @@ const SendMessage: React.FC<{ closeSidebar: () => void }> = ({ defaultValue="{}" name={name} onChange={onChange} - height="200px" + height="40px" /> )} /> From a1e7a20887c624195e68593d8aa6ae7a4e6c3daa Mon Sep 17 00:00:00 2001 From: David Bejanyan <58771979+David-DB88@users.noreply.github.com> Date: Mon, 8 May 2023 11:58:36 +0400 Subject: [PATCH 4/5] FE: RBAC: Wizard: Disable configure buttons if there are no permissions (#3684) --- .../Dashboard/ClusterTableActionsCell.tsx | 24 +++++++++++++++---- .../src/components/Dashboard/Dashboard.tsx | 22 +++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/kafka-ui-react-app/src/components/Dashboard/ClusterTableActionsCell.tsx b/kafka-ui-react-app/src/components/Dashboard/ClusterTableActionsCell.tsx index cb41ab06a8..19fefd784c 100644 --- a/kafka-ui-react-app/src/components/Dashboard/ClusterTableActionsCell.tsx +++ b/kafka-ui-react-app/src/components/Dashboard/ClusterTableActionsCell.tsx @@ -1,17 +1,31 @@ -import React from 'react'; -import { Cluster } from 'generated-sources'; +import React, { useMemo } from 'react'; +import { Cluster, ResourceType } from 'generated-sources'; import { CellContext } from '@tanstack/react-table'; -import { Button } from 'components/common/Button/Button'; import { clusterConfigPath } from 'lib/paths'; +import { useGetUserInfo } from 'lib/hooks/api/roles'; +import { ActionCanButton } from 'components/common/ActionComponent'; type Props = CellContext; const ClusterTableActionsCell: React.FC = ({ row }) => { const { name } = row.original; + const { data } = useGetUserInfo(); + + const isApplicationConfig = useMemo(() => { + return !!data?.userInfo?.permissions.some( + (permission) => permission.resource === ResourceType.APPLICATIONCONFIG + ); + }, [data]); + return ( - + ); }; diff --git a/kafka-ui-react-app/src/components/Dashboard/Dashboard.tsx b/kafka-ui-react-app/src/components/Dashboard/Dashboard.tsx index 7eab4c1d2f..c7b64aef1c 100644 --- a/kafka-ui-react-app/src/components/Dashboard/Dashboard.tsx +++ b/kafka-ui-react-app/src/components/Dashboard/Dashboard.tsx @@ -1,23 +1,25 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import PageHeading from 'components/common/PageHeading/PageHeading'; import * as Metrics from 'components/common/Metrics'; import { Tag } from 'components/common/Tag/Tag.styled'; import Switch from 'components/common/Switch/Switch'; import { useClusters } from 'lib/hooks/api/clusters'; -import { Cluster, ServerStatus } from 'generated-sources'; +import { Cluster, ResourceType, ServerStatus } from 'generated-sources'; import { ColumnDef } from '@tanstack/react-table'; import Table, { SizeCell } from 'components/common/NewTable'; import useBoolean from 'lib/hooks/useBoolean'; -import { Button } from 'components/common/Button/Button'; import { clusterNewConfigPath } from 'lib/paths'; import { GlobalSettingsContext } from 'components/contexts/GlobalSettingsContext'; import { useNavigate } from 'react-router-dom'; +import { ActionCanButton } from 'components/common/ActionComponent'; +import { useGetUserInfo } from 'lib/hooks/api/roles'; import * as S from './Dashboard.styled'; import ClusterName from './ClusterName'; import ClusterTableActionsCell from './ClusterTableActionsCell'; const Dashboard: React.FC = () => { + const { data } = useGetUserInfo(); const clusters = useClusters(); const { value: showOfflineOnly, toggle } = useBoolean(false); const appInfo = React.useContext(GlobalSettingsContext); @@ -62,6 +64,11 @@ const Dashboard: React.FC = () => { } }, [clusters, appInfo.hasDynamicConfig]); + const isApplicationConfig = useMemo(() => { + return !!data?.userInfo?.permissions.some( + (permission) => permission.resource === ResourceType.APPLICATIONCONFIG + ); + }, [data]); return ( <> @@ -87,9 +94,14 @@ const Dashboard: React.FC = () => { {appInfo.hasDynamicConfig && ( - + )} Date: Mon, 8 May 2023 12:00:43 +0400 Subject: [PATCH 5/5] FE: Messages: Fix UI displays nonsensical timestamps(#3715) --- kafka-ui-react-app/src/lib/dateTimeHelpers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kafka-ui-react-app/src/lib/dateTimeHelpers.ts b/kafka-ui-react-app/src/lib/dateTimeHelpers.ts index 3dce0edd78..148a70d2a3 100644 --- a/kafka-ui-react-app/src/lib/dateTimeHelpers.ts +++ b/kafka-ui-react-app/src/lib/dateTimeHelpers.ts @@ -1,6 +1,6 @@ export const formatTimestamp = ( timestamp?: number | string | Date, - format: Intl.DateTimeFormatOptions = { hour12: false } + format: Intl.DateTimeFormatOptions = { hourCycle: 'h23' } ): string => { if (!timestamp) { return ''; @@ -8,7 +8,6 @@ export const formatTimestamp = ( // empty array gets the default one from the browser const date = new Date(timestamp); - // invalid date if (Number.isNaN(date.getTime())) { return '';