Repository: provectus/kafka-ui
Issue: Connector "Restart All Tasks" button has no effect
Labels: type/enhancement
scope/backend
scope/frontend
status/accepted
status/confirmed

When choosing a specific connector there are several options you can choose:
<img width="1083" alt="image" src="https://user-images.githubusercontent.com/94184844/150494050-67a4692f-2133-4c78-94e3-c74857b65057.png">

Restart all tasks sends the next POST request that actually does do nothing.
https://xxx/api/clusters/local/connects/local/connectors/reddit-source-json/action/RESTART

According to the REST API documentation https://docs.confluent.io/platform/current/connect/references/restapi.html there is no such request available. And the correct one is this:
<img width="904" alt="image" src="https://user-images.githubusercontent.com/94184844/150494299-2cffab5d-abb7-4e05-af35-f3af1a57a050.png">

Restarting the specific task using the triple dot context menu works great tho.

Issue comments:
@5hin0bi we currently only restart **connector instance**, not tasks on this button click. I will change this to restart both connector and all its tasks. I think we will also need to rename this button to smth like "Restart connector". 
cc @Haarolean 
@5hin0bi can you please verify it works, and reopen issue if not
Checked on our dev environment. The button is now called "Restart Connector", but failed tasks themselves are not restarted.
To do: create 3 buttons
Need to implement frontend
I'm seeing a similar error with KafkaConnect where it's trying to hit an api endpoint:
```500 Server Error for HTTP GET "/api/clusters/local/connectors?search="```
that also doesn't seem to exist, per Confluent's API. 
> I'm seeing a similar error with KafkaConnect where it's trying to hit an api endpoint: `500 Server Error for HTTP GET "/api/clusters/local/connectors?search="` that also doesn't seem to exist, per Confluent's API.

That's our backend with our API, not confluent's. Please raise a new issue.

Pull request review comments:
I think since we have multiple dispatches here we can batch those , with the new redux Api we actually have this feature to decrease re-rendering
👍 

Patch that resolved the issue:
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 ab19d773ae0..9fe477ca134 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
@@ -1,6 +1,6 @@
 import React from 'react';
 import { useHistory, useParams } from 'react-router-dom';
-import { ConnectorState } from 'generated-sources';
+import { ConnectorState, ConnectorAction } from 'generated-sources';
 import { ClusterName, ConnectName, ConnectorName } from 'redux/interfaces';
 import {
   clusterConnectConnectorEditPath,
@@ -34,6 +34,12 @@ export interface ActionsProps {
     connectName: ConnectName,
     connectorName: ConnectorName
   ): void;
+  restartTasks(
+    clusterName: ClusterName,
+    connectName: ConnectName,
+    connectorName: ConnectorName,
+    action: ConnectorAction
+  ): void;
   pauseConnector(
     clusterName: ClusterName,
     connectName: ConnectName,
@@ -52,11 +58,13 @@ const Actions: React.FC<ActionsProps> = ({
   isConnectorDeleting,
   connectorStatus,
   restartConnector,
+  restartTasks,
   pauseConnector,
   resumeConnector,
   isConnectorActionRunning,
 }) => {
   const { clusterName, connectName, connectorName } = useParams<RouterParams>();
+
   const history = useHistory();
   const [
     isDeleteConnectorConfirmationVisible,
@@ -76,6 +84,13 @@ const Actions: React.FC<ActionsProps> = ({
     restartConnector(clusterName, connectName, connectorName);
   }, [restartConnector, clusterName, connectName, connectorName]);
 
+  const restartTasksHandler = React.useCallback(
+    (actionType) => {
+      restartTasks(clusterName, connectName, connectorName, actionType);
+    },
+    [restartTasks, clusterName, connectName, connectorName]
+  );
+
   const pauseConnectorHandler = React.useCallback(() => {
     pauseConnector(clusterName, connectName, connectorName);
   }, [pauseConnector, clusterName, connectName, connectorName]);
@@ -128,6 +143,32 @@ const Actions: React.FC<ActionsProps> = ({
         </span>
         <span>Restart Connector</span>
       </Button>
+      <Button
+        buttonSize="M"
+        buttonType="primary"
+        type="button"
+        onClick={() => restartTasksHandler(ConnectorAction.RESTART_ALL_TASKS)}
+        disabled={isConnectorActionRunning}
+      >
+        <span>
+          <i className="fas fa-sync-alt" />
+        </span>
+        <span>Restart All Tasks</span>
+      </Button>
+      <Button
+        buttonSize="M"
+        buttonType="primary"
+        type="button"
+        onClick={() =>
+          restartTasksHandler(ConnectorAction.RESTART_FAILED_TASKS)
+        }
+        disabled={isConnectorActionRunning}
+      >
+        <span>
+          <i className="fas fa-sync-alt" />
+        </span>
+        <span>Restart Failed Tasks</span>
+      </Button>
       <Button
         buttonSize="M"
         buttonType="primary"
diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/ActionsContainer.ts b/kafka-ui-react-app/src/components/Connect/Details/Actions/ActionsContainer.ts
index d69de08642b..059a0c32215 100644
--- a/kafka-ui-react-app/src/components/Connect/Details/Actions/ActionsContainer.ts
+++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/ActionsContainer.ts
@@ -4,6 +4,7 @@ import { RootState } from 'redux/interfaces';
 import {
   deleteConnector,
   restartConnector,
+  restartTasks,
   pauseConnector,
   resumeConnector,
 } from 'redux/actions';
@@ -24,6 +25,7 @@ const mapStateToProps = (state: RootState) => ({
 const mapDispatchToProps = {
   deleteConnector,
   restartConnector,
+  restartTasks,
   pauseConnector,
   resumeConnector,
 };
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 509bc5606c3..d1aec604d4a 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
@@ -50,6 +50,7 @@ describe('Actions', () => {
             isConnectorDeleting={false}
             connectorStatus={ConnectorState.RUNNING}
             restartConnector={jest.fn()}
+            restartTasks={jest.fn()}
             pauseConnector={jest.fn()}
             resumeConnector={jest.fn()}
             isConnectorActionRunning={false}
diff --git a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/__snapshots__/Actions.spec.tsx.snap b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/__snapshots__/Actions.spec.tsx.snap
index 2d1cd9580c4..ca3f1d6b2b2 100644
--- a/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/__snapshots__/Actions.spec.tsx.snap
+++ b/kafka-ui-react-app/src/components/Connect/Details/Actions/__tests__/__snapshots__/Actions.spec.tsx.snap
@@ -144,6 +144,36 @@ exports[`Actions view matches snapshot 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
@@ -337,6 +367,36 @@ exports[`Actions view matches snapshot when deleting connector 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
@@ -515,6 +575,36 @@ exports[`Actions view matches snapshot when failed 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
@@ -708,6 +798,36 @@ exports[`Actions view matches snapshot when paused 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
@@ -901,6 +1021,36 @@ exports[`Actions view matches snapshot when running connector action 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={true}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
@@ -1079,6 +1229,36 @@ exports[`Actions view matches snapshot when unassigned 1`] = `
       Restart Connector
     </span>
   </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart All Tasks
+    </span>
+  </button>
+  <button
+    className="c1"
+    disabled={false}
+    onClick={[Function]}
+    type="button"
+  >
+    <span>
+      <i
+        className="fas fa-sync-alt"
+      />
+    </span>
+    <span>
+      Restart Failed Tasks
+    </span>
+  </button>
   <a
     href="/ui/clusters/my-cluster/connects/my-connect/connectors/my-connector/edit"
     onClick={[Function]}
diff --git a/kafka-ui-react-app/src/redux/actions/__test__/thunks/connectors.spec.ts b/kafka-ui-react-app/src/redux/actions/__test__/thunks/connectors.spec.ts
index bca43b69e0b..c72e15653ad 100644
--- a/kafka-ui-react-app/src/redux/actions/__test__/thunks/connectors.spec.ts
+++ b/kafka-ui-react-app/src/redux/actions/__test__/thunks/connectors.spec.ts
@@ -323,7 +323,7 @@ describe('Thunks', () => {
         actions.restartConnectorAction.failure({
           alert: {
             subject: 'local-first-hdfs-source-connector',
-            title: 'Kafka Connect Connector Tasks Restart',
+            title: 'Kafka Connect Connector Restart',
             response: {
               status: 404,
               statusText: 'Not Found',
diff --git a/kafka-ui-react-app/src/redux/actions/actions.ts b/kafka-ui-react-app/src/redux/actions/actions.ts
index 464d9185204..dd72f87a8a4 100644
--- a/kafka-ui-react-app/src/redux/actions/actions.ts
+++ b/kafka-ui-react-app/src/redux/actions/actions.ts
@@ -106,6 +106,12 @@ export const restartConnectorAction = createAsyncAction(
   'RESTART_CONNECTOR__FAILURE'
 )<undefined, undefined, { alert?: FailurePayload }>();
 
+export const restartTasksAction = createAsyncAction(
+  'RESTART_TASKS__REQUEST',
+  'RESTART_TASKS__SUCCESS',
+  'RESTART_TASKS__FAILURE'
+)<undefined, undefined, { alert?: FailurePayload }>();
+
 export const pauseConnectorAction = createAsyncAction(
   'PAUSE_CONNECTOR__REQUEST',
   'PAUSE_CONNECTOR__SUCCESS',
diff --git a/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts b/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts
index 57514b03f6f..35124e7ceb5 100644
--- a/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts
+++ b/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts
@@ -18,6 +18,7 @@ import {
 } from 'redux/interfaces';
 import * as actions from 'redux/actions';
 import { getResponse } from 'lib/errorHandling';
+import { batch } from 'react-redux';
 
 const apiClientConf = new Configuration(BASE_PARAMS);
 export const kafkaConnectApiClient = new KafkaConnectApi(apiClientConf);
@@ -196,13 +197,46 @@ export const restartConnector =
       const response = await getResponse(error);
       const alert: FailurePayload = {
         subject: [clusterName, connectName, connectorName].join('-'),
-        title: `Kafka Connect Connector Tasks Restart`,
+        title: `Kafka Connect Connector Restart`,
         response,
       };
       dispatch(actions.restartConnectorAction.failure({ alert }));
     }
   };
 
+export const restartTasks =
+  (
+    clusterName: ClusterName,
+    connectName: ConnectName,
+    connectorName: ConnectorName,
+    action: ConnectorAction
+  ): PromiseThunkResult<void> =>
+  async (dispatch) => {
+    dispatch(actions.restartTasksAction.request());
+    try {
+      await kafkaConnectApiClient.updateConnectorState({
+        clusterName,
+        connectName,
+        connectorName,
+        action,
+      });
+      batch(() => {
+        dispatch(actions.restartTasksAction.success());
+        dispatch(
+          fetchConnectorTasks(clusterName, connectName, connectorName, true)
+        );
+      });
+    } catch (error) {
+      const response = await getResponse(error);
+      const alert: FailurePayload = {
+        subject: [clusterName, connectName, connectorName].join('-'),
+        title: `Kafka Connect Connector Tasks Restart`,
+        response,
+      };
+      dispatch(actions.restartTasksAction.failure({ alert }));
+    }
+  };
+
 export const pauseConnector =
   (
     clusterName: ClusterName,


Test patch: