From f9ec61ef7061642710091260ea29a04413ddf559 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Thu, 9 Mar 2023 13:16:03 -0500 Subject: [PATCH 1/7] 962 - bulk connectors operations --- .../Connect/Details/Actions/Actions.tsx | 19 ++- .../Actions/__tests__/Actions.spec.tsx | 41 ++++- .../Connect/List/BatchActionsBar.tsx | 158 ++++++++++++++++++ .../components/Connect/List/ConnectorCell.tsx | 29 ++++ .../src/components/Connect/List/List.tsx | 18 +- .../Connect/List/__tests__/List.spec.tsx | 9 +- .../Topics/List/BatchActionsBar.tsx | 6 +- .../src/components/Topics/List/TopicTable.tsx | 4 +- .../hooks/api/__tests__/kafkaConnect.spec.ts | 11 +- .../src/lib/hooks/api/kafkaConnect.ts | 9 +- 10 files changed, 266 insertions(+), 38 deletions(-) create mode 100644 kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx create mode 100644 kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx index 530f0db1471..957ca9edd80 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx @@ -51,16 +51,25 @@ const Actions: React.FC = () => { ); const stateMutation = useUpdateConnectorState(routerProps); + const mutationParams = (action: ConnectorAction) => { + return { + clusterName: routerProps.clusterName, + connectName: routerProps.connectName, + connectorName: routerProps.connectorName, + action: action + } + } const restartConnectorHandler = () => - stateMutation.mutateAsync(ConnectorAction.RESTART); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART)); const restartAllTasksHandler = () => - stateMutation.mutateAsync(ConnectorAction.RESTART_ALL_TASKS); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART_ALL_TASKS)); const restartFailedTasksHandler = () => - stateMutation.mutateAsync(ConnectorAction.RESTART_FAILED_TASKS); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART_FAILED_TASKS)); const pauseConnectorHandler = () => - stateMutation.mutateAsync(ConnectorAction.PAUSE); + stateMutation.mutateAsync(mutationParams(ConnectorAction.PAUSE)); const resumeConnectorHandler = () => - stateMutation.mutateAsync(ConnectorAction.RESUME); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESUME)); + return ( { await userEvent.click( screen.getByRole('menuitem', { name: 'Restart Connector' }) ); - expect(restartConnector).toHaveBeenCalledWith(ConnectorAction.RESTART); + expect(restartConnector).toHaveBeenCalledWith({ + 'action': ConnectorAction.RESTART, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + }); }); it('calls restartAllTasks', async () => { @@ -152,7 +157,12 @@ describe('Actions', () => { screen.getByRole('menuitem', { name: 'Restart All Tasks' }) ); expect(restartAllTasks).toHaveBeenCalledWith( - ConnectorAction.RESTART_ALL_TASKS + { + 'action': ConnectorAction.RESTART_ALL_TASKS, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + } ); }); @@ -167,7 +177,12 @@ describe('Actions', () => { screen.getByRole('menuitem', { name: 'Restart Failed Tasks' }) ); expect(restartFailedTasks).toHaveBeenCalledWith( - ConnectorAction.RESTART_FAILED_TASKS + { + 'action': ConnectorAction.RESTART_FAILED_TASKS, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + } ); }); @@ -178,8 +193,15 @@ describe('Actions', () => { })); renderComponent(); await afterClickRestartButton(); - await userEvent.click(screen.getByRole('menuitem', { name: 'Pause' })); - expect(pauseConnector).toHaveBeenCalledWith(ConnectorAction.PAUSE); + await userEvent.click(screen.getByRole('menuitem', {name: 'Pause'})); + expect(pauseConnector).toHaveBeenCalledWith( + { + 'action': ConnectorAction.PAUSE, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + } + ); }); it('calls resumeConnector when resume button clicked', async () => { @@ -193,7 +215,14 @@ describe('Actions', () => { renderComponent(); await afterClickRestartButton(); await userEvent.click(screen.getByRole('menuitem', { name: 'Resume' })); - expect(resumeConnector).toHaveBeenCalledWith(ConnectorAction.RESUME); + expect(resumeConnector).toHaveBeenCalledWith( + { + 'action': ConnectorAction.RESUME, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + } + ); }); }); }); diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx new file mode 100644 index 00000000000..2937e030ed0 --- /dev/null +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import {Action, ResourceType, ConnectorAction, Connector} from 'generated-sources'; +import useAppParams from 'lib/hooks/useAppParams'; +import { useConfirm } from 'lib/hooks/useConfirm'; +import {clusterConnectorsPath, RouterParamsClusterConnectConnector} from 'lib/paths'; +import {useIsMutating} from '@tanstack/react-query'; +import {ActionCanButton} from 'components/common/ActionComponent'; +import { usePermission } from 'lib/hooks/usePermission'; +import {useDeleteConnector, useUpdateConnectorState} from 'lib/hooks/api/kafkaConnect'; +import {useNavigate} from 'react-router-dom'; +import {Row} from '@tanstack/react-table'; +import { useQueryClient } from '@tanstack/react-query'; + +interface BatchActionsBarProps { + rows: Row[]; + resetRowSelection(): void; +} + +const BatchActionsBar: React.FC = ({ + rows, + resetRowSelection, +}) => { + + const confirm = useConfirm(); + const navigate = useNavigate(); + + const selectedConnectors = rows.map(({ original }) => original); + + const mutationsNumber = useIsMutating(); + const isMutating = mutationsNumber > 0; + + const routerProps = useAppParams(); + const clusterName = routerProps.clusterName; + const client = useQueryClient(); + + const canEdit = usePermission( + ResourceType.CONNECT, + Action.EDIT, + routerProps.connectorName + ); + const canDelete = usePermission( + ResourceType.CONNECT, + Action.DELETE, + routerProps.connectorName + ); + + const deleteConnectorMutation = useDeleteConnector(routerProps); + const deleteConnectorHandler = () => + confirm( + <> + Are you sure you want to remove {routerProps.connectorName}{' '} + connector? + , + async () => { + try { + await deleteConnectorMutation.mutateAsync(); + navigate(clusterConnectorsPath(clusterName)); + } catch { + // do not redirect + } + } + ); + + const stateMutation = useUpdateConnectorState(routerProps); + const updateConnector = (action: ConnectorAction, message: string) => { + confirm(message, async () => { + try { + await Promise.all( + selectedConnectors.map((connector) => ( + stateMutation.mutateAsync({ + clusterName, + connectName: connector.connect, + connectorName: connector.name, + action, + }) + )) + ); + resetRowSelection(); + } catch (e) { + // do nothing; + } finally { + client.invalidateQueries(['clusters', clusterName, 'connectors']); + } + }); + }; + const restartConnectorHandler = () => { + updateConnector(ConnectorAction.RESTART, 'Are you sure you want to restart selected connectors?'); + }; + const restartAllTasksHandler = () => + updateConnector(ConnectorAction.RESTART_ALL_TASKS, 'Are you sure you want to restart all tasks in selected connectors?'); + const restartFailedTasksHandler = () => + updateConnector(ConnectorAction.RESTART_FAILED_TASKS, 'Are you sure you want to restart failed tasks in selected connectors?'); + const pauseConnectorHandler = () => + updateConnector(ConnectorAction.PAUSE, 'Are you sure you want to pause selected connectors?'); + const resumeConnectorHandler = () => + updateConnector(ConnectorAction.RESUME, 'Are you sure you want to resume selected connectors?'); + + return ( + <> + + Pause + + + Resume + + + Restart Connector + + + Restart All Tasks + + + Restart Failed Tasks + + + Delete + + + ); +}; + +export default BatchActionsBar; diff --git a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx new file mode 100644 index 00000000000..8e849a7f029 --- /dev/null +++ b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { CellContext } from '@tanstack/react-table'; +import {FullConnectorInfo} from 'generated-sources'; +import { NavLink, useNavigate, useSearchParams } from 'react-router-dom'; +import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths'; +import useAppParams from 'lib/hooks/useAppParams'; +import { LinkCell } from 'components/common/NewTable'; + + +const ConnectorCell: React.FC> = ({ + row: { original }, +}) => { + const navigate = useNavigate(); + const { clusterName } = useAppParams(); + const { name, connect } = original; + const path = clusterConnectConnectorPath(clusterName, connect, name); + return ( + navigate(path)} + value={name} + to={path} + /> + //
navigate(path)}> + // {name} + //
+ ); +}; + +export default ConnectorCell; diff --git a/kafka-ui-react-app/src/components/Connect/List/List.tsx b/kafka-ui-react-app/src/components/Connect/List/List.tsx index b5935e7bab2..0868881d642 100644 --- a/kafka-ui-react-app/src/components/Connect/List/List.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx @@ -1,18 +1,19 @@ import React from 'react'; import useAppParams from 'lib/hooks/useAppParams'; -import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths'; -import Table, { TagCell } from 'components/common/NewTable'; -import { FullConnectorInfo } from 'generated-sources'; +import { ClusterNameRoute } from 'lib/paths'; +import Table, { TagCell, LinkCell } from 'components/common/NewTable'; +import {FullConnectorInfo} from 'generated-sources'; import { useConnectors } from 'lib/hooks/api/kafkaConnect'; import { ColumnDef } from '@tanstack/react-table'; -import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import ActionsCell from './ActionsCell'; import TopicsCell from './TopicsCell'; +import ConnectorCell from './ConnectorCell'; import RunningTasksCell from './RunningTasksCell'; +import BatchActionsBar from './BatchActionsBar'; const List: React.FC = () => { - const navigate = useNavigate(); const { clusterName } = useAppParams(); const [searchParams] = useSearchParams(); const { data: connectors } = useConnectors( @@ -22,7 +23,7 @@ const List: React.FC = () => { const columns = React.useMemo[]>( () => [ - { header: 'Name', accessorKey: 'name' }, + { header: 'Name', accessorKey: 'name', cell: ConnectorCell }, { header: 'Connect', accessorKey: 'connect' }, { header: 'Type', accessorKey: 'type' }, { header: 'Plugin', accessorKey: 'connectorClass' }, @@ -39,9 +40,8 @@ const List: React.FC = () => { data={connectors || []} columns={columns} enableSorting - onRowClick={({ original: { connect, name } }) => - navigate(clusterConnectConnectorPath(clusterName, connect, name)) - } + batchActionsBar={BatchActionsBar} + enableRowSelection={true} emptyMessage="No connectors found" /> ); diff --git a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx index 9de28f38ffd..339e8c49344 100644 --- a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx @@ -10,6 +10,7 @@ import userEvent from '@testing-library/user-event'; import { render, WithRoute } from 'lib/testHelpers'; import { clusterConnectConnectorPath, clusterConnectorsPath } from 'lib/paths'; import { useConnectors, useDeleteConnector } from 'lib/hooks/api/kafkaConnect'; +import { BrowserRouter } from "react-router-dom"; const mockedUsedNavigate = jest.fn(); const mockDelete = jest.fn(); @@ -22,6 +23,7 @@ jest.mock('react-router-dom', () => ({ jest.mock('lib/hooks/api/kafkaConnect', () => ({ useConnectors: jest.fn(), useDeleteConnector: jest.fn(), + useUpdateConnectorState: jest.fn(), })); const clusterName = 'local'; @@ -52,11 +54,8 @@ describe('Connectors List', () => { it('opens broker when row clicked', async () => { renderComponent(); - await userEvent.click( - screen.getByRole('row', { - name: 'hdfs-source-connector first SOURCE FileStreamSource a b c RUNNING 2 of 2', - }) - ); + + await userEvent.click(screen.getByRole('link', { name: 'hdfs-source-connector' })); await waitFor(() => expect(mockedUsedNavigate).toBeCalledWith( clusterConnectConnectorPath( diff --git a/kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx index d75f4d0da2c..f8e4fde7dc2 100644 --- a/kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx @@ -14,12 +14,12 @@ import { ActionCanButton } from 'components/common/ActionComponent'; import { isPermitted } from 'lib/permissions'; import { useUserInfo } from 'lib/hooks/useUserInfo'; -interface BatchActionsbarProps { +interface BatchActionsBarProps { rows: Row[]; resetRowSelection(): void; } -const BatchActionsbar: React.FC = ({ +const BatchActionsBar: React.FC = ({ rows, resetRowSelection, }) => { @@ -148,4 +148,4 @@ const BatchActionsbar: React.FC = ({ ); }; -export default BatchActionsbar; +export default BatchActionsBar; diff --git a/kafka-ui-react-app/src/components/Topics/List/TopicTable.tsx b/kafka-ui-react-app/src/components/Topics/List/TopicTable.tsx index dcafa61b915..ed081727be1 100644 --- a/kafka-ui-react-app/src/components/Topics/List/TopicTable.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/TopicTable.tsx @@ -11,7 +11,7 @@ import { PER_PAGE } from 'lib/constants'; import { TopicTitleCell } from './TopicTitleCell'; import ActionsCell from './ActionsCell'; -import BatchActionsbar from './BatchActionsBar'; +import BatchActionsBar from './BatchActionsBar'; const TopicTable: React.FC = () => { const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); @@ -101,7 +101,7 @@ const TopicTable: React.FC = () => { columns={columns} enableSorting serverSideProcessing - batchActionsBar={BatchActionsbar} + batchActionsBar={BatchActionsBar} enableRowSelection={ !isReadOnly ? (row) => !row.original.internal : undefined } diff --git a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts index 96d280f7d1c..f16c955cc49 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts @@ -91,7 +91,16 @@ describe('kafkaConnect hooks', () => { () => hooks.useUpdateConnectorState(connectorProps), { wrapper: TestQueryClientProvider } ); - await act(() => result.current.mutateAsync(action)); + await act(() => { + result.current.mutateAsync( + { + clusterName: clusterName, + connectName: connectName, + connectorName: connectorName, + action: action + }, + ) + }); await waitFor(() => expect(result.current.isSuccess).toBeTruthy()); expect(mock.calls()).toHaveLength(1); }); diff --git a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts index dd340306535..e9cdffd7c54 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts @@ -1,9 +1,4 @@ -import { - Connect, - Connector, - ConnectorAction, - NewConnector, -} from 'generated-sources'; +import {Connect, Connector, NewConnector, UpdateConnectorStateRequest} from 'generated-sources'; import { kafkaConnectApiClient as api } from 'lib/api'; import sortBy from 'lodash/sortBy'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; @@ -74,7 +69,7 @@ export function useConnectorTasks(props: UseConnectorProps) { export function useUpdateConnectorState(props: UseConnectorProps) { const client = useQueryClient(); return useMutation( - (action: ConnectorAction) => api.updateConnectorState({ ...props, action }), + (message: UpdateConnectorStateRequest) => api.updateConnectorState(message), { onSuccess: () => client.invalidateQueries(connectorKey(props)), } From 6cd3a445c982aabb9ebff7a4ace27e8e0fa49fd3 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Tue, 11 Apr 2023 11:18:26 -0400 Subject: [PATCH 2/7] 962 - bulk connectors operations tests are added --- .../Connect/List/BatchActionsBar.tsx | 13 +- .../components/Connect/List/ConnectorCell.tsx | 24 +-- .../src/components/Connect/List/List.tsx | 2 +- .../Connect/List/__tests__/List.spec.tsx | 144 +++++++++++++++++- 4 files changed, 150 insertions(+), 33 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index 2937e030ed0..3da641fa7bc 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -2,12 +2,11 @@ import React from 'react'; import {Action, ResourceType, ConnectorAction, Connector} from 'generated-sources'; import useAppParams from 'lib/hooks/useAppParams'; import { useConfirm } from 'lib/hooks/useConfirm'; -import {clusterConnectorsPath, RouterParamsClusterConnectConnector} from 'lib/paths'; +import { RouterParamsClusterConnectConnector } from 'lib/paths'; import {useIsMutating} from '@tanstack/react-query'; import {ActionCanButton} from 'components/common/ActionComponent'; import { usePermission } from 'lib/hooks/usePermission'; import {useDeleteConnector, useUpdateConnectorState} from 'lib/hooks/api/kafkaConnect'; -import {useNavigate} from 'react-router-dom'; import {Row} from '@tanstack/react-table'; import { useQueryClient } from '@tanstack/react-query'; @@ -22,7 +21,6 @@ const BatchActionsBar: React.FC = ({ }) => { const confirm = useConfirm(); - const navigate = useNavigate(); const selectedConnectors = rows.map(({ original }) => original); @@ -45,16 +43,15 @@ const BatchActionsBar: React.FC = ({ ); const deleteConnectorMutation = useDeleteConnector(routerProps); - const deleteConnectorHandler = () => + const deleteConnectorsHandler = () => confirm( <> - Are you sure you want to remove {routerProps.connectorName}{' '} - connector? + Are you sure you want to remove selected connectors? , async () => { try { await deleteConnectorMutation.mutateAsync(); - navigate(clusterConnectorsPath(clusterName)); + resetRowSelection(); } catch { // do not redirect } @@ -145,7 +142,7 @@ const BatchActionsBar: React.FC = ({ diff --git a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx index 8e849a7f029..da24ce18e27 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx @@ -1,28 +1,18 @@ import React from 'react'; -import { CellContext } from '@tanstack/react-table'; -import {FullConnectorInfo} from 'generated-sources'; -import { NavLink, useNavigate, useSearchParams } from 'react-router-dom'; +import { FullConnectorInfo } from 'generated-sources'; +import { useNavigate } from 'react-router-dom'; import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; -import { LinkCell } from 'components/common/NewTable'; - -const ConnectorCell: React.FC> = ({ - row: { original }, -}) => { +const ConnectorCell: React.FC<{ row: FullConnectorInfo }> = ({ row: { original } }) => { const navigate = useNavigate(); - const { clusterName } = useAppParams(); const { name, connect } = original; + const { clusterName } = useAppParams(); const path = clusterConnectConnectorPath(clusterName, connect, name); + const handleOnClick = () => navigate(path); + return ( - navigate(path)} - value={name} - to={path} - /> - //
navigate(path)}> - // {name} - //
+
{name}
); }; diff --git a/kafka-ui-react-app/src/components/Connect/List/List.tsx b/kafka-ui-react-app/src/components/Connect/List/List.tsx index 0868881d642..d1eedb9918a 100644 --- a/kafka-ui-react-app/src/components/Connect/List/List.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx @@ -1,7 +1,7 @@ import React from 'react'; import useAppParams from 'lib/hooks/useAppParams'; import { ClusterNameRoute } from 'lib/paths'; -import Table, { TagCell, LinkCell } from 'components/common/NewTable'; +import Table, { TagCell } from 'components/common/NewTable'; import {FullConnectorInfo} from 'generated-sources'; import { useConnectors } from 'lib/hooks/api/kafkaConnect'; import { ColumnDef } from '@tanstack/react-table'; diff --git a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx index 339e8c49344..9b4fc1e8267 100644 --- a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx @@ -9,14 +9,14 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render, WithRoute } from 'lib/testHelpers'; import { clusterConnectConnectorPath, clusterConnectorsPath } from 'lib/paths'; -import { useConnectors, useDeleteConnector } from 'lib/hooks/api/kafkaConnect'; -import { BrowserRouter } from "react-router-dom"; +import { useConnectors, useDeleteConnector, useUpdateConnectorState } from 'lib/hooks/api/kafkaConnect'; const mockedUsedNavigate = jest.fn(); const mockDelete = jest.fn(); +const mockUpdate = jest.fn(); jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), + ...jest.requireActual('react-router-dom') as any, useNavigate: () => mockedUsedNavigate, })); @@ -55,16 +55,146 @@ describe('Connectors List', () => { it('opens broker when row clicked', async () => { renderComponent(); - await userEvent.click(screen.getByRole('link', { name: 'hdfs-source-connector' })); - await waitFor(() => + const link = await screen.findByRole('cell', {name: 'hdfs-source-connector'}); + + await userEvent.click(link); + await waitFor(() => { expect(mockedUsedNavigate).toBeCalledWith( clusterConnectConnectorPath( clusterName, 'first', 'hdfs-source-connector' ) - ) - ); + ); + }); + }); + + describe('Selectable rows', () => { + it('renders selectable rows', () => { + renderComponent(); + expect(screen.getAllByRole('checkbox').length).toEqual(3); + }); + }); + + describe('Batch actions bar', () => { + const getButtonByName = (name: string) => screen.getByRole('button', { name }); + + beforeEach(async () => { + useDeleteConnector.mockClear(); + (useDeleteConnector as jest.Mock).mockImplementation(() => ({ + mutateAsync: mockDelete, + })); + (useUpdateConnectorState as jest.Mock).mockImplementation(() => ({ + mutateAsync: mockUpdate, + })); + + renderComponent(); + await userEvent.click(screen.getAllByRole('checkbox')[1]); + }); + it('renders batch actions bar', () => { + expect(getButtonByName('Pause')).toBeInTheDocument(); + expect(getButtonByName('Resume')).toBeInTheDocument(); + expect(getButtonByName('Restart Connector')).toBeInTheDocument(); + expect(getButtonByName('Restart All Tasks')).toBeInTheDocument(); + expect(getButtonByName('Restart Failed Tasks')).toBeInTheDocument(); + expect(getButtonByName('Delete')).toBeInTheDocument(); + }); + + it('handles delete button click', async () => { + const button = getButtonByName('Delete'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to remove selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockDelete).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockDelete).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); + + it('handles pause button click', async () => { + const button = getButtonByName('Pause'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to pause selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockUpdate).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); + + it('handles resume button click', async () => { + const button = getButtonByName('Resume'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to resume selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockUpdate).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); + + it('handles restart connector button click', async () => { + const button = getButtonByName('Restart Connector'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to restart selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockUpdate).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); + + it('handles restart all tasks button click', async () => { + const button = getButtonByName('Restart All Tasks'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to restart all tasks in selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockUpdate).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); + + it('handles restart failed tasks button click', async () => { + const button = getButtonByName('Restart Failed Tasks'); + await userEvent.click(button); + expect( + screen.getByText( + 'Are you sure you want to restart failed tasks in selected connectors?' + ) + ).toBeInTheDocument(); + const confirmBtn = getButtonByName('Confirm'); + expect(mockUpdate).not.toHaveBeenCalled(); + await userEvent.click(confirmBtn); + expect(mockUpdate).toHaveBeenCalledTimes(1); + expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked(); + expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked(); + }); }); }); From afc016a7dc41e00f63a3e7baa7d0718d78e5d431 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Tue, 11 Apr 2023 12:17:09 -0400 Subject: [PATCH 3/7] 962 - bulk connectors operations linters fix --- .../Connect/Details/Actions/Actions.tsx | 26 +++++++++----- .../Actions/__tests__/Actions.spec.tsx | 36 +++++++++---------- .../Connect/List/BatchActionsBar.tsx | 12 +++---- .../components/Connect/List/ConnectorCell.tsx | 5 ++- .../src/components/Connect/List/List.tsx | 2 +- .../Connect/List/__tests__/List.spec.tsx | 3 +- .../hooks/api/__tests__/kafkaConnect.spec.ts | 8 ++--- 7 files changed, 50 insertions(+), 42 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx index 957ca9edd80..e3610519d96 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx @@ -56,19 +56,29 @@ const Actions: React.FC = () => { clusterName: routerProps.clusterName, connectName: routerProps.connectName, connectorName: routerProps.connectorName, - action: action - } - } + action, + }; + }; const restartConnectorHandler = () => - stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART)); + stateMutation.mutateAsync( + mutationParams(ConnectorAction.RESTART) + ); const restartAllTasksHandler = () => - stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART_ALL_TASKS)); + stateMutation.mutateAsync( + mutationParams(ConnectorAction.RESTART_ALL_TASKS) + ); const restartFailedTasksHandler = () => - stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART_FAILED_TASKS)); + stateMutation.mutateAsync( + mutationParams(ConnectorAction.RESTART_FAILED_TASKS) + ); const pauseConnectorHandler = () => - stateMutation.mutateAsync(mutationParams(ConnectorAction.PAUSE)); + stateMutation.mutateAsync( + mutationParams(ConnectorAction.PAUSE) + ); const resumeConnectorHandler = () => - stateMutation.mutateAsync(mutationParams(ConnectorAction.RESUME)); + stateMutation.mutateAsync( + mutationParams(ConnectorAction.RESUME) + ); return ( diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx index 78bca064bb8..0171c6c15be 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx @@ -139,10 +139,10 @@ describe('Actions', () => { screen.getByRole('menuitem', { name: 'Restart Connector' }) ); expect(restartConnector).toHaveBeenCalledWith({ - 'action': ConnectorAction.RESTART, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' + action: ConnectorAction.RESTART, + clusterName: 'myCluster', + connectName: 'myConnect', + connectorName: 'myConnector' }); }); @@ -156,14 +156,12 @@ describe('Actions', () => { await userEvent.click( screen.getByRole('menuitem', { name: 'Restart All Tasks' }) ); - expect(restartAllTasks).toHaveBeenCalledWith( - { - 'action': ConnectorAction.RESTART_ALL_TASKS, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' - } - ); + expect(restartAllTasks).toHaveBeenCalledWith({ + action: ConnectorAction.RESTART_ALL_TASKS, + clusterName: 'myCluster', + connectName: 'myConnect', + connectorName: 'myConnector' + }); }); it('calls restartFailedTasks', async () => { @@ -176,14 +174,12 @@ describe('Actions', () => { await userEvent.click( screen.getByRole('menuitem', { name: 'Restart Failed Tasks' }) ); - expect(restartFailedTasks).toHaveBeenCalledWith( - { - 'action': ConnectorAction.RESTART_FAILED_TASKS, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' - } - ); + expect(restartFailedTasks).toHaveBeenCalledWith({ + 'action': ConnectorAction.RESTART_FAILED_TASKS, + 'clusterName': 'myCluster', + 'connectName': 'myConnect', + "connectorName": 'myConnector' + }); }); it('calls pauseConnector when pause button clicked', async () => { diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index 3da641fa7bc..ffc8ac1a65f 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import {Action, ResourceType, ConnectorAction, Connector} from 'generated-sources'; +import { Action, ResourceType, ConnectorAction, Connector } from 'generated-sources'; import useAppParams from 'lib/hooks/useAppParams'; import { useConfirm } from 'lib/hooks/useConfirm'; import { RouterParamsClusterConnectConnector } from 'lib/paths'; -import {useIsMutating} from '@tanstack/react-query'; -import {ActionCanButton} from 'components/common/ActionComponent'; +import { useIsMutating, useQueryClient } from '@tanstack/react-query'; +import { ActionCanButton } from 'components/common/ActionComponent'; import { usePermission } from 'lib/hooks/usePermission'; -import {useDeleteConnector, useUpdateConnectorState} from 'lib/hooks/api/kafkaConnect'; -import {Row} from '@tanstack/react-table'; -import { useQueryClient } from '@tanstack/react-query'; +import { useDeleteConnector, useUpdateConnectorState } from 'lib/hooks/api/kafkaConnect'; +import { Row } from '@tanstack/react-table'; + interface BatchActionsBarProps { rows: Row[]; diff --git a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx index da24ce18e27..1169c621f87 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx @@ -1,10 +1,13 @@ import React from 'react'; +import { CellContext } from '@tanstack/react-table'; import { FullConnectorInfo } from 'generated-sources'; import { useNavigate } from 'react-router-dom'; import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths'; import useAppParams from 'lib/hooks/useAppParams'; -const ConnectorCell: React.FC<{ row: FullConnectorInfo }> = ({ row: { original } }) => { +const ConnectorCell: React.FC> = ({ + row: { original }, +}) => { const navigate = useNavigate(); const { name, connect } = original; const { clusterName } = useAppParams(); diff --git a/kafka-ui-react-app/src/components/Connect/List/List.tsx b/kafka-ui-react-app/src/components/Connect/List/List.tsx index d1eedb9918a..7325e69d66a 100644 --- a/kafka-ui-react-app/src/components/Connect/List/List.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx @@ -41,7 +41,7 @@ const List: React.FC = () => { columns={columns} enableSorting batchActionsBar={BatchActionsBar} - enableRowSelection={true} + enableRowSelection emptyMessage="No connectors found" /> ); diff --git a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx index 9b4fc1e8267..4b4f9e7edb8 100644 --- a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx @@ -16,7 +16,7 @@ const mockDelete = jest.fn(); const mockUpdate = jest.fn(); jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom') as any, + ...jest.requireActual('react-router-dom'), useNavigate: () => mockedUsedNavigate, })); @@ -80,7 +80,6 @@ describe('Connectors List', () => { const getButtonByName = (name: string) => screen.getByRole('button', { name }); beforeEach(async () => { - useDeleteConnector.mockClear(); (useDeleteConnector as jest.Mock).mockImplementation(() => ({ mutateAsync: mockDelete, })); diff --git a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts index f16c955cc49..516a1fd4ca0 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts @@ -94,10 +94,10 @@ describe('kafkaConnect hooks', () => { await act(() => { result.current.mutateAsync( { - clusterName: clusterName, - connectName: connectName, - connectorName: connectorName, - action: action + clusterName, + connectName, + connectorName, + action, }, ) }); From 52fa539e3fc8cd04ade88a80c9d0afc64f5d8ed0 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Tue, 11 Apr 2023 13:34:17 -0400 Subject: [PATCH 4/7] 962 - bulk connectors operations linters fix --- .../Connect/Details/Actions/Actions.tsx | 12 ++--- .../Actions/__tests__/Actions.spec.tsx | 42 ++++++++--------- .../Connect/List/BatchActionsBar.tsx | 47 +++++++++++++------ .../components/Connect/List/ConnectorCell.tsx | 4 +- .../src/components/Connect/List/List.tsx | 2 +- .../Connect/List/__tests__/List.spec.tsx | 13 +++-- .../hooks/api/__tests__/kafkaConnect.spec.ts | 14 +++--- .../src/lib/hooks/api/kafkaConnect.ts | 7 ++- 8 files changed, 79 insertions(+), 62 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx index e3610519d96..d598ffcc708 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx @@ -60,9 +60,7 @@ const Actions: React.FC = () => { }; }; const restartConnectorHandler = () => - stateMutation.mutateAsync( - mutationParams(ConnectorAction.RESTART) - ); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESTART)); const restartAllTasksHandler = () => stateMutation.mutateAsync( mutationParams(ConnectorAction.RESTART_ALL_TASKS) @@ -72,13 +70,9 @@ const Actions: React.FC = () => { mutationParams(ConnectorAction.RESTART_FAILED_TASKS) ); const pauseConnectorHandler = () => - stateMutation.mutateAsync( - mutationParams(ConnectorAction.PAUSE) - ); + stateMutation.mutateAsync(mutationParams(ConnectorAction.PAUSE)); const resumeConnectorHandler = () => - stateMutation.mutateAsync( - mutationParams(ConnectorAction.RESUME) - ); + stateMutation.mutateAsync(mutationParams(ConnectorAction.RESUME)); return ( diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx index 0171c6c15be..deb0a7f9080 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx @@ -142,7 +142,7 @@ describe('Actions', () => { action: ConnectorAction.RESTART, clusterName: 'myCluster', connectName: 'myConnect', - connectorName: 'myConnector' + connectorName: 'myConnector', }); }); @@ -160,7 +160,7 @@ describe('Actions', () => { action: ConnectorAction.RESTART_ALL_TASKS, clusterName: 'myCluster', connectName: 'myConnect', - connectorName: 'myConnector' + connectorName: 'myConnector', }); }); @@ -175,10 +175,10 @@ describe('Actions', () => { screen.getByRole('menuitem', { name: 'Restart Failed Tasks' }) ); expect(restartFailedTasks).toHaveBeenCalledWith({ - 'action': ConnectorAction.RESTART_FAILED_TASKS, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' + action: ConnectorAction.RESTART_FAILED_TASKS, + clusterName: 'myCluster', + connectName: 'myConnect', + connectorName: 'myConnector', }); }); @@ -189,15 +189,13 @@ describe('Actions', () => { })); renderComponent(); await afterClickRestartButton(); - await userEvent.click(screen.getByRole('menuitem', {name: 'Pause'})); - expect(pauseConnector).toHaveBeenCalledWith( - { - 'action': ConnectorAction.PAUSE, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' - } - ); + await userEvent.click(screen.getByRole('menuitem', { name: 'Pause' })); + expect(pauseConnector).toHaveBeenCalledWith({ + action: ConnectorAction.PAUSE, + clusterName: 'myCluster', + connectName: 'myConnect', + connectorName: 'myConnector', + }); }); it('calls resumeConnector when resume button clicked', async () => { @@ -211,14 +209,12 @@ describe('Actions', () => { renderComponent(); await afterClickRestartButton(); await userEvent.click(screen.getByRole('menuitem', { name: 'Resume' })); - expect(resumeConnector).toHaveBeenCalledWith( - { - 'action': ConnectorAction.RESUME, - 'clusterName': 'myCluster', - 'connectName': 'myConnect', - "connectorName": 'myConnector' - } - ); + expect(resumeConnector).toHaveBeenCalledWith({ + action: ConnectorAction.RESUME, + clusterName: 'myCluster', + connectName: 'myConnect', + connectorName: 'myConnector', + }); }); }); }); diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index ffc8ac1a65f..bb4d0aa7abc 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -1,15 +1,22 @@ import React from 'react'; -import { Action, ResourceType, ConnectorAction, Connector } from 'generated-sources'; +import { + Action, + ResourceType, + ConnectorAction, + Connector, +} from 'generated-sources'; import useAppParams from 'lib/hooks/useAppParams'; import { useConfirm } from 'lib/hooks/useConfirm'; import { RouterParamsClusterConnectConnector } from 'lib/paths'; import { useIsMutating, useQueryClient } from '@tanstack/react-query'; import { ActionCanButton } from 'components/common/ActionComponent'; import { usePermission } from 'lib/hooks/usePermission'; -import { useDeleteConnector, useUpdateConnectorState } from 'lib/hooks/api/kafkaConnect'; +import { + useDeleteConnector, + useUpdateConnectorState, +} from 'lib/hooks/api/kafkaConnect'; import { Row } from '@tanstack/react-table'; - interface BatchActionsBarProps { rows: Row[]; resetRowSelection(): void; @@ -19,7 +26,6 @@ const BatchActionsBar: React.FC = ({ rows, resetRowSelection, }) => { - const confirm = useConfirm(); const selectedConnectors = rows.map(({ original }) => original); @@ -45,9 +51,7 @@ const BatchActionsBar: React.FC = ({ const deleteConnectorMutation = useDeleteConnector(routerProps); const deleteConnectorsHandler = () => confirm( - <> - Are you sure you want to remove selected connectors? - , + <>Are you sure you want to remove selected connectors?, async () => { try { await deleteConnectorMutation.mutateAsync(); @@ -63,14 +67,14 @@ const BatchActionsBar: React.FC = ({ confirm(message, async () => { try { await Promise.all( - selectedConnectors.map((connector) => ( + selectedConnectors.map((connector) => stateMutation.mutateAsync({ clusterName, connectName: connector.connect, connectorName: connector.name, action, }) - )) + ) ); resetRowSelection(); } catch (e) { @@ -81,16 +85,31 @@ const BatchActionsBar: React.FC = ({ }); }; const restartConnectorHandler = () => { - updateConnector(ConnectorAction.RESTART, 'Are you sure you want to restart selected connectors?'); + updateConnector( + ConnectorAction.RESTART, + 'Are you sure you want to restart selected connectors?' + ); }; const restartAllTasksHandler = () => - updateConnector(ConnectorAction.RESTART_ALL_TASKS, 'Are you sure you want to restart all tasks in selected connectors?'); + updateConnector( + ConnectorAction.RESTART_ALL_TASKS, + 'Are you sure you want to restart all tasks in selected connectors?' + ); const restartFailedTasksHandler = () => - updateConnector(ConnectorAction.RESTART_FAILED_TASKS, 'Are you sure you want to restart failed tasks in selected connectors?'); + updateConnector( + ConnectorAction.RESTART_FAILED_TASKS, + 'Are you sure you want to restart failed tasks in selected connectors?' + ); const pauseConnectorHandler = () => - updateConnector(ConnectorAction.PAUSE, 'Are you sure you want to pause selected connectors?'); + updateConnector( + ConnectorAction.PAUSE, + 'Are you sure you want to pause selected connectors?' + ); const resumeConnectorHandler = () => - updateConnector(ConnectorAction.RESUME, 'Are you sure you want to resume selected connectors?'); + updateConnector( + ConnectorAction.RESUME, + 'Are you sure you want to resume selected connectors?' + ); return ( <> diff --git a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx index 1169c621f87..d8d2fbdbf4e 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx @@ -14,9 +14,7 @@ const ConnectorCell: React.FC> = ({ const path = clusterConnectConnectorPath(clusterName, connect, name); const handleOnClick = () => navigate(path); - return ( -
{name}
- ); + return
{name}
; }; export default ConnectorCell; diff --git a/kafka-ui-react-app/src/components/Connect/List/List.tsx b/kafka-ui-react-app/src/components/Connect/List/List.tsx index 7325e69d66a..6873fc7e1b7 100644 --- a/kafka-ui-react-app/src/components/Connect/List/List.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx @@ -2,7 +2,7 @@ import React from 'react'; import useAppParams from 'lib/hooks/useAppParams'; import { ClusterNameRoute } from 'lib/paths'; import Table, { TagCell } from 'components/common/NewTable'; -import {FullConnectorInfo} from 'generated-sources'; +import { FullConnectorInfo } from 'generated-sources'; import { useConnectors } from 'lib/hooks/api/kafkaConnect'; import { ColumnDef } from '@tanstack/react-table'; import { useSearchParams } from 'react-router-dom'; diff --git a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx index 4b4f9e7edb8..6ef92f31098 100644 --- a/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/__tests__/List.spec.tsx @@ -9,7 +9,11 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render, WithRoute } from 'lib/testHelpers'; import { clusterConnectConnectorPath, clusterConnectorsPath } from 'lib/paths'; -import { useConnectors, useDeleteConnector, useUpdateConnectorState } from 'lib/hooks/api/kafkaConnect'; +import { + useConnectors, + useDeleteConnector, + useUpdateConnectorState, +} from 'lib/hooks/api/kafkaConnect'; const mockedUsedNavigate = jest.fn(); const mockDelete = jest.fn(); @@ -55,7 +59,9 @@ describe('Connectors List', () => { it('opens broker when row clicked', async () => { renderComponent(); - const link = await screen.findByRole('cell', {name: 'hdfs-source-connector'}); + const link = await screen.findByRole('cell', { + name: 'hdfs-source-connector', + }); await userEvent.click(link); await waitFor(() => { @@ -77,7 +83,8 @@ describe('Connectors List', () => { }); describe('Batch actions bar', () => { - const getButtonByName = (name: string) => screen.getByRole('button', { name }); + const getButtonByName = (name: string) => + screen.getByRole('button', { name }); beforeEach(async () => { (useDeleteConnector as jest.Mock).mockImplementation(() => ({ diff --git a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts index 516a1fd4ca0..835a4be0adb 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/__tests__/kafkaConnect.spec.ts @@ -92,14 +92,12 @@ describe('kafkaConnect hooks', () => { { wrapper: TestQueryClientProvider } ); await act(() => { - result.current.mutateAsync( - { - clusterName, - connectName, - connectorName, - action, - }, - ) + result.current.mutateAsync({ + clusterName, + connectName, + connectorName, + action, + }); }); await waitFor(() => expect(result.current.isSuccess).toBeTruthy()); expect(mock.calls()).toHaveLength(1); diff --git a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts index 007eab87070..1d811554548 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts @@ -1,4 +1,9 @@ -import {Connect, Connector, NewConnector, UpdateConnectorStateRequest} from 'generated-sources'; +import { + Connect, + Connector, + NewConnector, + UpdateConnectorStateRequest, +} from 'generated-sources'; import { kafkaConnectApiClient as api } from 'lib/api'; import sortBy from 'lodash/sortBy'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; From 19c0b29362450d1da9abe1c88feb186aba562d49 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Mon, 17 Apr 2023 19:27:49 -0400 Subject: [PATCH 5/7] 962 - bulk connectors operations delete connectors fix --- .../Connect/List/BatchActionsBar.tsx | 23 ++++++++++++++----- .../src/lib/hooks/api/kafkaConnect.ts | 9 +++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index bb4d0aa7abc..691d4ae7551 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -34,7 +34,7 @@ const BatchActionsBar: React.FC = ({ const isMutating = mutationsNumber > 0; const routerProps = useAppParams(); - const clusterName = routerProps.clusterName; + const { clusterName } = routerProps; const client = useQueryClient(); const canEdit = usePermission( @@ -49,18 +49,29 @@ const BatchActionsBar: React.FC = ({ ); const deleteConnectorMutation = useDeleteConnector(routerProps); - const deleteConnectorsHandler = () => + const deleteConnectorsHandler = () => { confirm( - <>Are you sure you want to remove selected connectors?, + 'Are you sure you want to remove selected connectors?', async () => { try { - await deleteConnectorMutation.mutateAsync(); + await Promise.all( + selectedConnectors.map((connector) => { + deleteConnectorMutation.mutateAsync({ + clusterName, + connectName: connector.connect, + connectorName: connector.name, + }); + }) + ); resetRowSelection(); - } catch { - // do not redirect + } catch (e) { + // do nothing; + } finally { + client.invalidateQueries(['clusters', clusterName, 'connectors']); } } ); + }; const stateMutation = useUpdateConnectorState(routerProps); const updateConnector = (action: ConnectorAction, message: string) => { diff --git a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts index 1d811554548..6447490f80f 100644 --- a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts +++ b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts @@ -135,7 +135,10 @@ export function useCreateConnector(clusterName: ClusterName) { export function useDeleteConnector(props: UseConnectorProps) { const client = useQueryClient(); - return useMutation(() => api.deleteConnector(props), { - onSuccess: () => client.invalidateQueries(connectorsKey(props.clusterName)), - }); + return useMutation( + (message: UseConnectorProps) => api.deleteConnector(message), + { + onSuccess: () => client.invalidateQueries(connectorKey(props)), + } + ); } From 174e4d8f6f72db7e6f4d461df74a04b7b6acd254 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Tue, 18 Apr 2023 13:25:57 -0400 Subject: [PATCH 6/7] 962 - bulk connectors operations permissions for batch actions bar are fixed --- .../Connect/List/BatchActionsBar.tsx | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index 691d4ae7551..b756cda4c3f 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -10,12 +10,13 @@ import { useConfirm } from 'lib/hooks/useConfirm'; import { RouterParamsClusterConnectConnector } from 'lib/paths'; import { useIsMutating, useQueryClient } from '@tanstack/react-query'; import { ActionCanButton } from 'components/common/ActionComponent'; -import { usePermission } from 'lib/hooks/usePermission'; import { useDeleteConnector, useUpdateConnectorState, } from 'lib/hooks/api/kafkaConnect'; import { Row } from '@tanstack/react-table'; +import { isPermitted } from 'lib/permissions'; +import { useUserInfo } from 'lib/hooks/useUserInfo'; interface BatchActionsBarProps { rows: Row[]; @@ -36,17 +37,23 @@ const BatchActionsBar: React.FC = ({ const routerProps = useAppParams(); const { clusterName } = routerProps; const client = useQueryClient(); + const { roles, rbacFlag } = useUserInfo(); - const canEdit = usePermission( - ResourceType.CONNECT, - Action.EDIT, - routerProps.connectorName - ); - const canDelete = usePermission( - ResourceType.CONNECT, - Action.DELETE, - routerProps.connectorName - ); + const canPerformActionOnSelected = (action: Action) => { + return selectedConnectors.every((connector) => + isPermitted({ + roles, + resource: ResourceType.CONNECT, + action, + value: connector.name, + clusterName, + rbacFlag, + }) + ); + }; + + const canEdit = canPerformActionOnSelected(Action.EDIT); + const canDelete = canPerformActionOnSelected(Action.DELETE); const deleteConnectorMutation = useDeleteConnector(routerProps); const deleteConnectorsHandler = () => { From 42eafabd06e3f415ee805c03e295264dd0089ab1 Mon Sep 17 00:00:00 2001 From: Kamila Alekbaeva Date: Wed, 26 Apr 2023 11:22:45 -0400 Subject: [PATCH 7/7] 962 - bulk connectors operations: code review fixes --- .../src/components/Connect/List/BatchActionsBar.tsx | 6 +++--- .../src/components/Connect/List/ConnectorCell.tsx | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx index b756cda4c3f..14f5d5915df 100644 --- a/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx @@ -62,13 +62,13 @@ const BatchActionsBar: React.FC = ({ async () => { try { await Promise.all( - selectedConnectors.map((connector) => { + selectedConnectors.map((connector) => deleteConnectorMutation.mutateAsync({ clusterName, connectName: connector.connect, connectorName: connector.name, - }); - }) + }) + ) ); resetRowSelection(); } catch (e) { diff --git a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx index d8d2fbdbf4e..9f7054127d2 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ConnectorCell.tsx @@ -14,7 +14,16 @@ const ConnectorCell: React.FC> = ({ const path = clusterConnectConnectorPath(clusterName, connect, name); const handleOnClick = () => navigate(path); - return
{name}
; + return ( +
+ {name} +
+ ); }; export default ConnectorCell;