Repository: provectus/kafka-ui
Issue: UI: Display smart filters filtered stats
Labels: type/enhancement
scope/backend
scope/frontend
status/accepted

When polling messages from topic (and filtering is enabled) we should show number of errors that appeared during filtration process - > this number placed in `TopicMessageConsuming.filterApplyErrors`
UI: 
1. during polling process - show value of `TopicMessageConsuming.filterApplyErrors` if it is > 0
2. when polling is finished BE will send `TopicMessageEvent` with `type = DONE` with final stats of polling process - this stats should be rendered. (also this event should be logically treated as polling end by UI)


Issue comments:

Pull request review comments:
can we put `null` here instead of empty string, cause here React will try to render the empty string. 

or we can do it with short circuit checks such as 
```
{!!filterApplyErrors && (
          <S.Metric title="Errors">
            <span>{filterApplyErrors} errors</span>
          </S.Metric>
        )}
```

shouldn't this be an `enum` ?

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx
index f9fa3401fc0..2941536f8bf 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx
@@ -53,11 +53,13 @@ export interface FiltersProps {
   phaseMessage?: string;
   meta: TopicMessageConsuming;
   isFetching: boolean;
+  messageEventType?: string;
   addMessage(content: { message: TopicMessage; prepend: boolean }): void;
   resetMessages(): void;
   updatePhase(phase: string): void;
   updateMeta(meta: TopicMessageConsuming): void;
   setIsFetching(status: boolean): void;
+  setMessageType(messageType: string): void;
 }
 
 export interface MessageFilters {
@@ -80,13 +82,15 @@ export const SeekTypeOptions = [
 
 const Filters: React.FC<FiltersProps> = ({
   phaseMessage,
-  meta: { elapsedMs, bytesConsumed, messagesConsumed },
+  meta: { elapsedMs, bytesConsumed, messagesConsumed, filterApplyErrors },
   isFetching,
   addMessage,
   resetMessages,
   updatePhase,
   updateMeta,
   setIsFetching,
+  setMessageType,
+  messageEventType,
 }) => {
   const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
   const location = useLocation();
@@ -355,6 +359,12 @@ const Filters: React.FC<FiltersProps> = ({
           case TopicMessageEventTypeEnum.CONSUMING:
             if (consuming) updateMeta(consuming);
             break;
+          case TopicMessageEventTypeEnum.DONE:
+            if (consuming && type) {
+              setMessageType(type);
+              updateMeta(consuming);
+            }
+            break;
           default:
         }
       };
@@ -551,6 +561,7 @@ const Filters: React.FC<FiltersProps> = ({
           {seekDirection !== SeekDirection.TAILING &&
             isFetching &&
             phaseMessage}
+          {!isFetching && messageEventType}
         </S.Message>
         <S.MessageLoading isLive={isTailing}>
           <S.MessageLoadingSpinner isFetching={isFetching} />
@@ -582,6 +593,11 @@ const Filters: React.FC<FiltersProps> = ({
           </S.MetricsIcon>
           <span>{messagesConsumed} messages consumed</span>
         </S.Metric>
+        {!!filterApplyErrors && (
+          <S.Metric title="Errors">
+            <span>{filterApplyErrors} errors</span>
+          </S.Metric>
+        )}
       </S.FiltersMetrics>
     </S.FiltersWrapper>
   );
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/FiltersContainer.ts b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/FiltersContainer.ts
index 144672ee603..19dca0184bd 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/FiltersContainer.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/FiltersContainer.ts
@@ -6,11 +6,13 @@ import {
   updateTopicMessagesMeta,
   updateTopicMessagesPhase,
   setTopicMessagesFetchingStatus,
+  setMessageEventType,
 } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   getTopicMessgesMeta,
   getTopicMessgesPhase,
   getIsTopicMessagesFetching,
+  getIsTopicMessagesType,
 } from 'redux/reducers/topicMessages/selectors';
 
 import Filters from './Filters';
@@ -19,6 +21,7 @@ const mapStateToProps = (state: RootState) => ({
   phaseMessage: getTopicMessgesPhase(state),
   meta: getTopicMessgesMeta(state),
   isFetching: getIsTopicMessagesFetching(state),
+  messageEventType: getIsTopicMessagesType(state),
 });
 
 const mapDispatchToProps = {
@@ -27,6 +30,7 @@ const mapDispatchToProps = {
   updatePhase: updateTopicMessagesPhase,
   updateMeta: updateTopicMessagesMeta,
   setIsFetching: setTopicMessagesFetchingStatus,
+  setMessageType: setMessageEventType,
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(Filters);
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.spec.tsx
index e248fdeaeb8..3e75f117874 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.spec.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.spec.tsx
@@ -44,13 +44,17 @@ const renderComponent = (
     <WithRoute path={clusterTopicPath()}>
       <TopicMessagesContext.Provider value={ctx}>
         <Filters
-          meta={{}}
+          meta={{
+            filterApplyErrors: 10,
+          }}
           isFetching={false}
           addMessage={jest.fn()}
           resetMessages={jest.fn()}
           updatePhase={jest.fn()}
           updateMeta={jest.fn()}
           setIsFetching={jest.fn()}
+          setMessageType={jest.fn}
+          messageEventType="Done"
           {...props}
         />
       </TopicMessagesContext.Provider>
@@ -228,4 +232,17 @@ describe('Filters component', () => {
       expect(anotherSmartFilterElement).not.toBeInTheDocument();
     });
   });
+
+  describe('show errors when get an filterApplyErrors and message event type', () => {
+    it('show errors', () => {
+      renderComponent();
+      const errors = screen.getByText('10 errors');
+      expect(errors).toBeInTheDocument();
+    });
+    it('message event type when fetching is false ', () => {
+      renderComponent();
+      const messageType = screen.getByText('Done');
+      expect(messageType).toBeInTheDocument();
+    });
+  });
 });
diff --git a/kafka-ui-react-app/src/redux/interfaces/topic.ts b/kafka-ui-react-app/src/redux/interfaces/topic.ts
index 9f667e135e8..153002240a1 100644
--- a/kafka-ui-react-app/src/redux/interfaces/topic.ts
+++ b/kafka-ui-react-app/src/redux/interfaces/topic.ts
@@ -56,5 +56,6 @@ export interface TopicMessagesState {
   messages: TopicMessage[];
   phase?: string;
   meta: TopicMessageConsuming;
+  messageEventType?: string;
   isFetching: boolean;
 }
diff --git a/kafka-ui-react-app/src/redux/reducers/topicMessages/selectors.ts b/kafka-ui-react-app/src/redux/reducers/topicMessages/selectors.ts
index 03adca8e424..b2636cdf2ad 100644
--- a/kafka-ui-react-app/src/redux/reducers/topicMessages/selectors.ts
+++ b/kafka-ui-react-app/src/redux/reducers/topicMessages/selectors.ts
@@ -23,3 +23,8 @@ export const getIsTopicMessagesFetching = createSelector(
   topicMessagesState,
   ({ isFetching }) => isFetching
 );
+
+export const getIsTopicMessagesType = createSelector(
+  topicMessagesState,
+  ({ messageEventType }) => messageEventType
+);
diff --git a/kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts b/kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts
index 846cbd0c978..530a3781140 100644
--- a/kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts
+++ b/kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts
@@ -10,6 +10,7 @@ export const initialState: TopicMessagesState = {
     messagesConsumed: 0,
     isCancelled: false,
   },
+  messageEventType: '',
   isFetching: false,
 };
 
@@ -37,6 +38,10 @@ const topicMessagesSlice = createSlice({
     setTopicMessagesFetchingStatus: (state, action) => {
       state.isFetching = action.payload;
     },
+
+    setMessageEventType: (state, action) => {
+      state.messageEventType = action.payload;
+    },
   },
 });
 
@@ -46,6 +51,7 @@ export const {
   updateTopicMessagesPhase,
   updateTopicMessagesMeta,
   setTopicMessagesFetchingStatus,
+  setMessageEventType,
 } = topicMessagesSlice.actions;
 
 export default topicMessagesSlice.reducer;


Test patch: