Repository: provectus/kafka-ui
Issue: Add KC quick actions into the sandwich menu
Labels: type/enhancement
good first issue
scope/frontend
status/accepted

Add the following actions into this menu:
<img width="303" alt="image" src="https://user-images.githubusercontent.com/1494347/195043659-57b84a7f-c139-4ba0-bc34-8adefd149bf9.png">
1. restart connector
2. restart all tasks
3. restart failed tasks
All the actions are available in a connector overview, we can copy them

Issue comments:
@Haarolean, can u assign on me?
> @Haarolean, can u assign on me?

sure, go ahead :)
@corners2wall any luck?
@Haarolean  not started
@corners2wall are you planning to? We have a lot of people wishing to participate in hacktoberfest and we don't have that many frontend tasks available :)
@Haarolean yeap, I planning resolve this issue

Pull request review comments:
do we need this `useMemo` ?
you are right we do not need any useMemo 
this will invalidate everything and everything , if we can do more specific we need to specify the query keys.
removed invalidateQueries from useConnectors and set it in useUpdateConnectorState with query keys 

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx b/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx
index 30b3df8a56d..5b3a24cdb70 100644
--- a/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx
+++ b/kafka-ui-react-app/src/components/Connect/List/ActionsCell.tsx
@@ -1,26 +1,41 @@
 import React from 'react';
-import { FullConnectorInfo } from 'generated-sources';
+import {
+  Action,
+  ConnectorAction,
+  ConnectorState,
+  FullConnectorInfo,
+  ResourceType,
+} from 'generated-sources';
 import { CellContext } from '@tanstack/react-table';
 import { ClusterNameRoute } from 'lib/paths';
 import useAppParams from 'lib/hooks/useAppParams';
 import { Dropdown, DropdownItem } from 'components/common/Dropdown';
-import { useDeleteConnector } from 'lib/hooks/api/kafkaConnect';
+import {
+  useDeleteConnector,
+  useUpdateConnectorState,
+} from 'lib/hooks/api/kafkaConnect';
 import { useConfirm } from 'lib/hooks/useConfirm';
+import { useIsMutating } from '@tanstack/react-query';
+import { ActionDropdownItem } from 'components/common/ActionComponent';
 
 const ActionsCell: React.FC<CellContext<FullConnectorInfo, unknown>> = ({
   row,
 }) => {
-  const { connect, name } = row.original;
-
+  const { connect, name, status } = row.original;
   const { clusterName } = useAppParams<ClusterNameRoute>();
-
+  const mutationsNumber = useIsMutating();
+  const isMutating = mutationsNumber > 0;
   const confirm = useConfirm();
   const deleteMutation = useDeleteConnector({
     clusterName,
     connectName: connect,
     connectorName: name,
   });
-
+  const stateMutation = useUpdateConnectorState({
+    clusterName,
+    connectName: connect,
+    connectorName: name,
+  });
   const handleDelete = () => {
     confirm(
       <>
@@ -31,8 +46,66 @@ const ActionsCell: React.FC<CellContext<FullConnectorInfo, unknown>> = ({
       }
     );
   };
+  // const stateMutation = useUpdateConnectorState(routerProps);
+  const resumeConnectorHandler = () =>
+    stateMutation.mutateAsync(ConnectorAction.RESUME);
+  const restartConnectorHandler = () =>
+    stateMutation.mutateAsync(ConnectorAction.RESTART);
+
+  const restartAllTasksHandler = () =>
+    stateMutation.mutateAsync(ConnectorAction.RESTART_ALL_TASKS);
+
+  const restartFailedTasksHandler = () =>
+    stateMutation.mutateAsync(ConnectorAction.RESTART_FAILED_TASKS);
+
   return (
     <Dropdown>
+      {status.state === ConnectorState.PAUSED && (
+        <ActionDropdownItem
+          onClick={resumeConnectorHandler}
+          disabled={isMutating}
+          permission={{
+            resource: ResourceType.CONNECT,
+            action: Action.EDIT,
+            value: name,
+          }}
+        >
+          Resume
+        </ActionDropdownItem>
+      )}
+      <ActionDropdownItem
+        onClick={restartConnectorHandler}
+        disabled={isMutating}
+        permission={{
+          resource: ResourceType.CONNECT,
+          action: Action.EDIT,
+          value: name,
+        }}
+      >
+        Restart Connector
+      </ActionDropdownItem>
+      <ActionDropdownItem
+        onClick={restartAllTasksHandler}
+        disabled={isMutating}
+        permission={{
+          resource: ResourceType.CONNECT,
+          action: Action.EDIT,
+          value: name,
+        }}
+      >
+        Restart All Tasks
+      </ActionDropdownItem>
+      <ActionDropdownItem
+        onClick={restartFailedTasksHandler}
+        disabled={isMutating}
+        permission={{
+          resource: ResourceType.CONNECT,
+          action: Action.EDIT,
+          value: name,
+        }}
+      >
+        Restart Failed Tasks
+      </ActionDropdownItem>
       <DropdownItem onClick={handleDelete} danger>
         Remove Connector
       </DropdownItem>
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..82b4aab2126 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 } from 'lib/hooks/api/kafkaConnect';
+import {
+  useConnectors,
+  useDeleteConnector,
+  useUpdateConnectorState,
+} from 'lib/hooks/api/kafkaConnect';
 
 const mockedUsedNavigate = jest.fn();
 const mockDelete = jest.fn();
@@ -22,6 +26,7 @@ jest.mock('react-router-dom', () => ({
 jest.mock('lib/hooks/api/kafkaConnect', () => ({
   useConnectors: jest.fn(),
   useDeleteConnector: jest.fn(),
+  useUpdateConnectorState: jest.fn(),
 }));
 
 const clusterName = 'local';
@@ -42,6 +47,10 @@ describe('Connectors List', () => {
       (useConnectors as jest.Mock).mockImplementation(() => ({
         data: connectors,
       }));
+      const restartConnector = jest.fn();
+      (useUpdateConnectorState as jest.Mock).mockImplementation(() => ({
+        mutateAsync: restartConnector,
+      }));
     });
 
     it('renders', async () => {
diff --git a/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts b/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts
index f63fc5fe2a8..d7db888a096 100644
--- a/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts
+++ b/kafka-ui-react-app/src/components/common/Dropdown/Dropdown.styled.ts
@@ -70,7 +70,7 @@ export const DropdownButton = styled.button`
 `;
 
 export const DangerItem = styled.div`
-  color: ${({ theme: { dropdown } }) => dropdown.item.color.normal};
+  color: ${({ theme: { dropdown } }) => dropdown.item.color.danger};
 `;
 
 export const DropdownItemHint = styled.div`
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 b8a17c558d3..1d01d491954 100644
--- a/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts
+++ b/kafka-ui-react-app/src/lib/hooks/api/kafkaConnect.ts
@@ -76,7 +76,8 @@ export function useUpdateConnectorState(props: UseConnectorProps) {
   return useMutation(
     (action: ConnectorAction) => api.updateConnectorState({ ...props, action }),
     {
-      onSuccess: () => client.invalidateQueries(connectorKey(props)),
+      onSuccess: () =>
+        client.invalidateQueries(['clusters', props.clusterName, 'connectors']),
     }
   );
 }


Test patch: