Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#962 Bulk connectors ops #3473

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,29 @@ const Actions: React.FC = () => {
);

const stateMutation = useUpdateConnectorState(routerProps);
const mutationParams = (action: ConnectorAction) => {
return {
clusterName: routerProps.clusterName,
connectName: routerProps.connectName,
connectorName: routerProps.connectorName,
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 (
<S.ConnectorActionsWrapperStyled>
<Dropdown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,12 @@ describe('Actions', () => {
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 () => {
Expand All @@ -151,9 +156,12 @@ describe('Actions', () => {
await userEvent.click(
screen.getByRole('menuitem', { name: 'Restart All Tasks' })
);
expect(restartAllTasks).toHaveBeenCalledWith(
ConnectorAction.RESTART_ALL_TASKS
);
expect(restartAllTasks).toHaveBeenCalledWith({
action: ConnectorAction.RESTART_ALL_TASKS,
clusterName: 'myCluster',
connectName: 'myConnect',
connectorName: 'myConnector',
});
});

it('calls restartFailedTasks', async () => {
Expand All @@ -166,9 +174,12 @@ describe('Actions', () => {
await userEvent.click(
screen.getByRole('menuitem', { name: 'Restart Failed Tasks' })
);
expect(restartFailedTasks).toHaveBeenCalledWith(
ConnectorAction.RESTART_FAILED_TASKS
);
expect(restartFailedTasks).toHaveBeenCalledWith({
action: ConnectorAction.RESTART_FAILED_TASKS,
clusterName: 'myCluster',
connectName: 'myConnect',
connectorName: 'myConnector',
});
});

it('calls pauseConnector when pause button clicked', async () => {
Expand All @@ -179,7 +190,12 @@ describe('Actions', () => {
renderComponent();
await afterClickRestartButton();
await userEvent.click(screen.getByRole('menuitem', { name: 'Pause' }));
expect(pauseConnector).toHaveBeenCalledWith(ConnectorAction.PAUSE);
expect(pauseConnector).toHaveBeenCalledWith({
action: ConnectorAction.PAUSE,
clusterName: 'myCluster',
connectName: 'myConnect',
connectorName: 'myConnector',
});
});

it('calls resumeConnector when resume button clicked', async () => {
Expand All @@ -193,7 +209,12 @@ 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',
});
});
});
});
Expand Down
174 changes: 174 additions & 0 deletions kafka-ui-react-app/src/components/Connect/List/BatchActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
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 { 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';

interface BatchActionsBarProps {
rows: Row<Connector>[];
resetRowSelection(): void;
}

const BatchActionsBar: React.FC<BatchActionsBarProps> = ({
rows,
resetRowSelection,
}) => {
const confirm = useConfirm();

const selectedConnectors = rows.map(({ original }) => original);

const mutationsNumber = useIsMutating();
const isMutating = mutationsNumber > 0;

const routerProps = useAppParams<RouterParamsClusterConnectConnector>();
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 deleteConnectorsHandler = () =>
confirm(
<>Are you sure you want to remove selected connectors?</>,
async () => {
try {
await deleteConnectorMutation.mutateAsync();
resetRowSelection();
} 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 (
<>
<ActionCanButton
buttonSize="M"
buttonType="secondary"
onClick={pauseConnectorHandler}
disabled={isMutating}
canDoAction={canEdit}
>
Pause
</ActionCanButton>
<ActionCanButton
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ActionButton please check this component , because the <ActionCanButton is just so we would be able to use some very custom selectors together.

use <ActionButton and it has a permisson props , please check that.

so we will have a uniform way for doing the permission.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActionButton is only capable on checking permissions on one entity, but collection. I need either modify the way we operate permissions in ActionButton or use ActionCanButton or I can create new component ActionOnCollectionButton and implement those permissions use there. WDYT is the best way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mgrdich any thoughts here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will think about it and get back to you.

buttonSize="M"
buttonType="secondary"
onClick={resumeConnectorHandler}
disabled={isMutating}
canDoAction={canEdit}
>
Resume
</ActionCanButton>
<ActionCanButton
buttonSize="M"
buttonType="secondary"
onClick={restartConnectorHandler}
disabled={isMutating}
canDoAction={canEdit}
>
Restart Connector
</ActionCanButton>
<ActionCanButton
buttonSize="M"
buttonType="secondary"
onClick={restartAllTasksHandler}
disabled={isMutating}
canDoAction={canEdit}
>
Restart All Tasks
</ActionCanButton>
<ActionCanButton
buttonSize="M"
buttonType="secondary"
onClick={restartFailedTasksHandler}
disabled={isMutating}
canDoAction={canEdit}
>
Restart Failed Tasks
</ActionCanButton>
<ActionCanButton
buttonSize="M"
buttonType="secondary"
onClick={deleteConnectorsHandler}
disabled={isMutating}
canDoAction={canDelete}
>
Delete
</ActionCanButton>
</>
);
};

export default BatchActionsBar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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<CellContext<FullConnectorInfo, unknown>> = ({
row: { original },
}) => {
const navigate = useNavigate();
const { name, connect } = original;
const { clusterName } = useAppParams<ClusterNameRoute>();
const path = clusterConnectConnectorPath(clusterName, connect, name);
const handleOnClick = () => navigate(path);

return <div onClick={handleOnClick}> {name} </div>;
};

export default ConnectorCell;
14 changes: 7 additions & 7 deletions kafka-ui-react-app/src/components/Connect/List/List.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React from 'react';
import useAppParams from 'lib/hooks/useAppParams';
import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths';
import { ClusterNameRoute } from 'lib/paths';
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';
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<ClusterNameRoute>();
const [searchParams] = useSearchParams();
const { data: connectors } = useConnectors(
Expand All @@ -22,7 +23,7 @@ const List: React.FC = () => {

const columns = React.useMemo<ColumnDef<FullConnectorInfo>[]>(
() => [
{ header: 'Name', accessorKey: 'name' },
{ header: 'Name', accessorKey: 'name', cell: ConnectorCell },
{ header: 'Connect', accessorKey: 'connect' },
{ header: 'Type', accessorKey: 'type' },
{ header: 'Plugin', accessorKey: 'connectorClass' },
Expand All @@ -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
emptyMessage="No connectors found"
/>
);
Expand Down
Loading