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:
I would suggest to move this logic to redux. Just create a new selector
Getting failed connectors moved to redux selector and .spec file updated
we can actually pass failed parameter to the , setUpComponent and test the failed rendering view. so we can make it here conditional.
Connectors or connects?
```suggestion
  failedConnectors: getFailedConnectors(state),
```
```suggestion
  failedConnectors: FullConnectorInfo[];
```
The kafka component is called Kafka Connect and consists of connectors :)
```suggestion
            title="Failed Connectors"
```
```suggestion
      (connector) => connector.status.state === ConnectorState.FAILED
```
done

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 55f93d1c478..1958ac200a2 100644
--- a/kafka-ui-react-app/src/components/Connect/List/List.tsx
+++ b/kafka-ui-react-app/src/components/Connect/List/List.tsx
@@ -20,6 +20,7 @@ export interface ListProps {
   areConnectorsFetching: boolean;
   connectors: FullConnectorInfo[];
   connects: Connect[];
+  failedConnectors: FullConnectorInfo[];
   fetchConnects(clusterName: ClusterName): void;
   fetchConnectors({ clusterName }: { clusterName: ClusterName }): void;
   search: string;
@@ -30,6 +31,7 @@ const List: React.FC<ListProps> = ({
   connectors,
   areConnectsFetching,
   areConnectorsFetching,
+  failedConnectors,
   fetchConnects,
   fetchConnectors,
   search,
@@ -72,6 +74,13 @@ const List: React.FC<ListProps> = ({
           >
             {connectors.length}
           </Metrics.Indicator>
+          <Metrics.Indicator
+            label="Failed"
+            title="Failed Connectors"
+            fetching={areConnectsFetching}
+          >
+            {failedConnectors?.length}
+          </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 15fb02ad13c..b1180738b48 100644
--- a/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts
+++ b/kafka-ui-react-app/src/components/Connect/List/ListContainer.ts
@@ -11,6 +11,7 @@ import {
   getAreConnectsFetching,
   getAreConnectorsFetching,
   getConnectorSearch,
+  getFailedConnectors,
 } from 'redux/reducers/connect/selectors';
 import List from 'components/Connect/List/List';
 
@@ -18,6 +19,7 @@ const mapStateToProps = (state: RootState) => ({
   areConnectsFetching: getAreConnectsFetching(state),
   areConnectorsFetching: getAreConnectorsFetching(state),
   connects: getConnects(state),
+  failedConnectors: getFailedConnectors(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 68d59cefd02..a1edf8ce47b 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
@@ -1,5 +1,8 @@
 import React from 'react';
-import { connectors } from 'redux/reducers/connect/__test__/fixtures';
+import {
+  connectors,
+  failedConnectors,
+} from 'redux/reducers/connect/__test__/fixtures';
 import ClusterContext, {
   ContextProps,
   initialValue,
@@ -33,6 +36,7 @@ describe('Connectors List', () => {
             areConnectorsFetching
             areConnectsFetching
             connectors={[]}
+            failedConnectors={[]}
             connects={[]}
             fetchConnects={fetchConnects}
             fetchConnectors={fetchConnectors}
@@ -66,6 +70,15 @@ describe('Connectors List', () => {
       expect(screen.getAllByRole('row').length).toEqual(3);
     });
 
+    it('renders failed connectors list', () => {
+      renderComponent({
+        areConnectorsFetching: false,
+        failedConnectors,
+      });
+      expect(screen.queryByRole('PageLoader')).not.toBeInTheDocument();
+      expect(screen.getByTitle('Failed Connectors')).toBeInTheDocument();
+    });
+
     it('handles fetchConnects and fetchConnectors', () => {
       renderComponent();
       expect(fetchConnects).toHaveBeenCalledTimes(1);
diff --git a/kafka-ui-react-app/src/redux/reducers/connect/__test__/fixtures.ts b/kafka-ui-react-app/src/redux/reducers/connect/__test__/fixtures.ts
index 9d8ecff3217..2ef1248acfe 100644
--- a/kafka-ui-react-app/src/redux/reducers/connect/__test__/fixtures.ts
+++ b/kafka-ui-react-app/src/redux/reducers/connect/__test__/fixtures.ts
@@ -69,6 +69,33 @@ export const connectors: FullConnectorInfo[] = [
   },
 ];
 
+export const failedConnectors: FullConnectorInfo[] = [
+  {
+    connect: 'first',
+    name: 'hdfs-source-connector',
+    connectorClass: 'FileStreamSource',
+    type: ConnectorType.SOURCE,
+    topics: ['test-topic'],
+    status: {
+      state: ConnectorState.FAILED,
+    },
+    tasksCount: 2,
+    failedTasksCount: 0,
+  },
+  {
+    connect: 'second',
+    name: 'hdfs2-source-connector',
+    connectorClass: 'FileStreamSource',
+    type: ConnectorType.SINK,
+    topics: ['test-topic'],
+    status: {
+      state: ConnectorState.FAILED,
+    },
+    tasksCount: 3,
+    failedTasksCount: 1,
+  },
+];
+
 export const connectorServerPayload = {
   connect: 'first',
   name: 'hdfs-source-connector',
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 a5becc22a1c..bf1d5e946f9 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
@@ -19,6 +19,7 @@ describe('Connect selectors', () => {
         false
       );
       expect(selectors.getConnectors(store.getState())).toEqual([]);
+      expect(selectors.getFailedConnectors(store.getState())).toEqual([]);
       expect(selectors.getIsConnectorFetching(store.getState())).toEqual(false);
       expect(selectors.getConnector(store.getState())).toEqual(null);
       expect(selectors.getConnectorStatus(store.getState())).toEqual(undefined);
@@ -65,6 +66,14 @@ describe('Connect selectors', () => {
       expect(selectors.getConnectors(store.getState())).toEqual(connectors);
     });
 
+    it('returns failed connectors', () => {
+      store.dispatch({
+        type: fetchConnectors.fulfilled.type,
+        payload: { connectors },
+      });
+      expect(selectors.getFailedConnectors(store.getState()).length).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 9e859492d29..107bc843ab5 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,7 @@
 import { createSelector } from '@reduxjs/toolkit';
 import { ConnectState, RootState } from 'redux/interfaces';
 import { createFetchingSelector } from 'redux/reducers/loader/selectors';
-import { ConnectorTaskStatus } from 'generated-sources';
+import { ConnectorTaskStatus, ConnectorState } from 'generated-sources';
 
 import {
   deleteConnector,
@@ -43,6 +43,15 @@ export const getConnectors = createSelector(
   ({ connectors }) => connectors
 );
 
+export const getFailedConnectors = createSelector(
+  connectState,
+  ({ connectors }) => {
+    return connectors.filter(
+      (connector) => connector.status.state === ConnectorState.FAILED
+    );
+  }
+);
+
 const getConnectorFetchingStatus = createFetchingSelector(
   fetchConnector.typePrefix
 );


Test patch: