Repository: provectus/kafka-ui
Issue: Please add a counters of all connectors, tasks and failed connectors, tasks
Labels: type/enhancement
good first issue
scope/frontend
status/accepted

### Is your proposal related to a problem?

KC can have a very large number of connectors. It can be very tedious and error prone to scroll through the list in search of any failed tasks/connectors. It would be easy to miss any failed connector/task as well.

### Describe the solution you'd like

Ideally -- a filter for the list to only show connectors that have failed tasks. But good starting point would be at least to show a counter of total connectors/tasks and counter of failed connectors/tasks.

### Describe alternatives you've considered

N/A

### Additional context

N/A


Issue comments:
@lazzy-panda let's rename "failed" to "failed connectors" and add a "failed tasks" pane as well
![image](https://user-images.githubusercontent.com/1494347/169536427-98ea2972-d172-4c8e-b1c6-817fba5a9390.png)


Pull request review comments:
```suggestion
    .reduce(acc, value) => {
```

Patch that resolved the issue:
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 1958ac200a2..0f1ab074b0c 100644
--- a/kafka-ui-react-app/src/components/Connect/List/List.tsx
+++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx
@@ -21,6 +21,7 @@ export interface ListProps {
   connectors: FullConnectorInfo[];
   connects: Connect[];
   failedConnectors: FullConnectorInfo[];
+  failedTasks: number | undefined;
   fetchConnects(clusterName: ClusterName): void;
   fetchConnectors({ clusterName }: { clusterName: ClusterName }): void;
   search: string;
@@ -32,6 +33,7 @@ const List: React.FC<ListProps> = ({
   areConnectsFetching,
   areConnectorsFetching,
   failedConnectors,
+  failedTasks,
   fetchConnects,
   fetchConnectors,
   search,
@@ -75,12 +77,19 @@ const List: React.FC<ListProps> = ({
             {connectors.length}
           </Metrics.Indicator>
           <Metrics.Indicator
-            label="Failed"
+            label="Failed Connectors"
             title="Failed Connectors"
             fetching={areConnectsFetching}
           >
             {failedConnectors?.length}
           </Metrics.Indicator>
+          <Metrics.Indicator
+            label="Failed Tasks"
+            title="Failed Tasks"
+            fetching={areConnectsFetching}
+          >
+            {failedTasks}
+          </Metrics.Indicator>
         </Metrics.Section>
       </Metrics.Wrapper>
       <ControlPanelWrapper hasInput>
diff --git a/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts b/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts
index b1180738b48..c3e939a2db2 100644
--- a/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts
+++ b/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts
@@ -12,6 +12,7 @@ import {
   getAreConnectorsFetching,
   getConnectorSearch,
   getFailedConnectors,
+  getFailedTasks,
 } from 'redux/reducers/connect/selectors';
 import List from 'components/Connect/List/List';
 
@@ -20,6 +21,7 @@ const mapStateToProps = (state: RootState) => ({
   areConnectorsFetching: getAreConnectorsFetching(state),
   connects: getConnects(state),
   failedConnectors: getFailedConnectors(state),
+  failedTasks: getFailedTasks(state),
   connectors: getConnectors(state),
   search: getConnectorSearch(state),
 });
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 a1edf8ce47b..bb8fb3cfa67 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
@@ -37,6 +37,7 @@ describe('Connectors List', () => {
             areConnectsFetching
             connectors={[]}
             failedConnectors={[]}
+            failedTasks={0}
             connects={[]}
             fetchConnects={fetchConnects}
             fetchConnectors={fetchConnectors}
diff --git a/kafka-ui-react-app/src/redux/reducers/connect/__test__/selectors.spec.ts b/kafka-ui-react-app/src/redux/reducers/connect/__test__/selectors.spec.ts
index bf1d5e946f9..0ca9e2fdd0b 100644
--- a/kafka-ui-react-app/src/redux/reducers/connect/__test__/selectors.spec.ts
+++ b/kafka-ui-react-app/src/redux/reducers/connect/__test__/selectors.spec.ts
@@ -20,6 +20,7 @@ describe('Connect selectors', () => {
       );
       expect(selectors.getConnectors(store.getState())).toEqual([]);
       expect(selectors.getFailedConnectors(store.getState())).toEqual([]);
+      expect(selectors.getFailedTasks(store.getState())).toEqual(0);
       expect(selectors.getIsConnectorFetching(store.getState())).toEqual(false);
       expect(selectors.getConnector(store.getState())).toEqual(null);
       expect(selectors.getConnectorStatus(store.getState())).toEqual(undefined);
@@ -74,6 +75,14 @@ describe('Connect selectors', () => {
       expect(selectors.getFailedConnectors(store.getState()).length).toEqual(1);
     });
 
+    it('returns failed tasks', () => {
+      store.dispatch({
+        type: fetchConnectors.fulfilled.type,
+        payload: { connectors },
+      });
+      expect(selectors.getFailedTasks(store.getState())).toEqual(1);
+    });
+
     it('returns connector', () => {
       store.dispatch({
         type: fetchConnector.fulfilled.type,
diff --git a/kafka-ui-react-app/src/redux/reducers/connect/selectors.ts b/kafka-ui-react-app/src/redux/reducers/connect/selectors.ts
index 107bc843ab5..2e029a7f9e9 100644
--- a/kafka-ui-react-app/src/redux/reducers/connect/selectors.ts
+++ b/kafka-ui-react-app/src/redux/reducers/connect/selectors.ts
@@ -1,7 +1,11 @@
 import { createSelector } from '@reduxjs/toolkit';
 import { ConnectState, RootState } from 'redux/interfaces';
 import { createFetchingSelector } from 'redux/reducers/loader/selectors';
-import { ConnectorTaskStatus, ConnectorState } from 'generated-sources';
+import {
+  ConnectorTaskStatus,
+  ConnectorState,
+  FullConnectorInfo,
+} from 'generated-sources';
 
 import {
   deleteConnector,
@@ -47,11 +51,18 @@ export const getFailedConnectors = createSelector(
   connectState,
   ({ connectors }) => {
     return connectors.filter(
-      (connector) => connector.status.state === ConnectorState.FAILED
+      (connector: FullConnectorInfo) =>
+        connector.status.state === ConnectorState.FAILED
     );
   }
 );
 
+export const getFailedTasks = createSelector(connectState, ({ connectors }) => {
+  return connectors
+    .map((connector: FullConnectorInfo) => connector.failedTasksCount || 0)
+    .reduce((acc: number, value: number) => acc + value, 0);
+});
+
 const getConnectorFetchingStatus = createFetchingSelector(
   fetchConnector.typePrefix
 );


Test patch: