浏览代码

[FE] Fix updating message count upon clearing messages (#3080)

* in a function clearTopicMessagesHandler added invalidateQueries

* changed redux Topic messages clear with react query call

* changed redux Topic messages clear with react query call on Overview/ActionCell.tsx

* show on alert deleted item

* added s on variable clearMessage

* removed clearTopicMessages action from redux and replace functionality with hook useClearTopicMessages

---------

Co-authored-by: davitbejanyan <dbejanyan@provectus.com>
David 2 年之前
父节点
当前提交
5cdd44daee

+ 3 - 10
kafka-ui-react-app/src/components/Topics/List/ActionsCell.tsx

@@ -1,20 +1,17 @@
 import React from 'react';
 import { Action, CleanUpPolicy, Topic, ResourceType } from 'generated-sources';
 import { CellContext } from '@tanstack/react-table';
-import { useAppDispatch } from 'lib/hooks/redux';
 import ClusterContext from 'components/contexts/ClusterContext';
 import { ClusterNameRoute } from 'lib/paths';
 import useAppParams from 'lib/hooks/useAppParams';
-import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   Dropdown,
   DropdownItem,
   DropdownItemHint,
 } from 'components/common/Dropdown';
-import { useQueryClient } from '@tanstack/react-query';
 import {
-  topicKeys,
   useDeleteTopic,
+  useClearTopicMessages,
   useRecreateTopic,
 } from 'lib/hooks/api/topics';
 import { ActionDropdownItem } from 'components/common/ActionComponent';
@@ -24,20 +21,16 @@ const ActionsCell: React.FC<CellContext<Topic, unknown>> = ({ row }) => {
 
   const { isReadOnly, isTopicDeletionAllowed } =
     React.useContext(ClusterContext);
-  const dispatch = useAppDispatch();
   const { clusterName } = useAppParams<ClusterNameRoute>();
-  const queryClient = useQueryClient();
 
+  const clearMessages = useClearTopicMessages(clusterName);
   const deleteTopic = useDeleteTopic(clusterName);
   const recreateTopic = useRecreateTopic({ clusterName, topicName: name });
 
   const disabled = internal || isReadOnly;
 
   const clearTopicMessagesHandler = async () => {
-    await dispatch(
-      clearTopicMessages({ clusterName, topicName: name })
-    ).unwrap();
-    return queryClient.invalidateQueries(topicKeys.all(clusterName));
+    await clearMessages.mutateAsync(name);
   };
 
   const isCleanupDisabled = cleanUpPolicy !== CleanUpPolicy.DELETE;

+ 10 - 5
kafka-ui-react-app/src/components/Topics/List/BatchActionsBar.tsx

@@ -3,11 +3,13 @@ import { Row } from '@tanstack/react-table';
 import { Action, Topic, ResourceType } from 'generated-sources';
 import useAppParams from 'lib/hooks/useAppParams';
 import { ClusterName } from 'redux/interfaces';
-import { topicKeys, useDeleteTopic } from 'lib/hooks/api/topics';
+import {
+  topicKeys,
+  useClearTopicMessages,
+  useDeleteTopic,
+} from 'lib/hooks/api/topics';
 import { useConfirm } from 'lib/hooks/useConfirm';
 import { Button } from 'components/common/Button/Button';
-import { useAppDispatch } from 'lib/hooks/redux';
-import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { clusterTopicCopyRelativePath } from 'lib/paths';
 import { useQueryClient } from '@tanstack/react-query';
 import { ActionCanButton } from 'components/common/ActionComponent';
@@ -25,11 +27,14 @@ const BatchActionsbar: React.FC<BatchActionsbarProps> = ({
 }) => {
   const { clusterName } = useAppParams<{ clusterName: ClusterName }>();
   const confirm = useConfirm();
-  const dispatch = useAppDispatch();
   const deleteTopic = useDeleteTopic(clusterName);
   const selectedTopics = rows.map(({ original }) => original.name);
   const client = useQueryClient();
 
+  const clearMessages = useClearTopicMessages(clusterName);
+  const clearTopicMessagesHandler = async (topicName: Topic['name']) => {
+    await clearMessages.mutateAsync(topicName);
+  };
   const deleteTopicsHandler = () => {
     confirm('Are you sure you want to remove selected topics?', async () => {
       try {
@@ -50,7 +55,7 @@ const BatchActionsbar: React.FC<BatchActionsbarProps> = ({
         try {
           await Promise.all(
             selectedTopics.map((topicName) =>
-              dispatch(clearTopicMessages({ clusterName, topicName })).unwrap()
+              clearTopicMessagesHandler(topicName)
             )
           );
           resetRowSelection();

+ 9 - 8
kafka-ui-react-app/src/components/Topics/List/__tests__/TopicTable.spec.tsx

@@ -6,16 +6,15 @@ import { externalTopicPayload, topicsPayload } from 'lib/fixtures/topics';
 import ClusterContext from 'components/contexts/ClusterContext';
 import userEvent from '@testing-library/user-event';
 import {
+  useClearTopicMessages,
   useDeleteTopic,
   useRecreateTopic,
   useTopics,
 } from 'lib/hooks/api/topics';
 import TopicTable from 'components/Topics/List/TopicTable';
 import { clusterTopicsPath } from 'lib/paths';
-import { useAppDispatch } from 'lib/hooks/redux';
 
 const clusterName = 'test-cluster';
-const unwrapMock = jest.fn();
 
 jest.mock('lib/hooks/redux', () => ({
   ...jest.requireActual('lib/hooks/redux'),
@@ -29,22 +28,24 @@ jest.mock('lib/hooks/api/topics', () => ({
   useDeleteTopic: jest.fn(),
   useRecreateTopic: jest.fn(),
   useTopics: jest.fn(),
+  useClearTopicMessages: jest.fn(),
 }));
 
 const deleteTopicMock = jest.fn();
 const recreateTopicMock = jest.fn();
+const clearTopicMessages = jest.fn();
 
 describe('TopicTable Components', () => {
   beforeEach(() => {
     (useDeleteTopic as jest.Mock).mockImplementation(() => ({
       mutateAsync: deleteTopicMock,
     }));
+    (useClearTopicMessages as jest.Mock).mockImplementation(() => ({
+      mutateAsync: clearTopicMessages,
+    }));
     (useRecreateTopic as jest.Mock).mockImplementation(() => ({
       mutateAsync: recreateTopicMock,
     }));
-    (useAppDispatch as jest.Mock).mockImplementation(() => () => ({
-      unwrap: unwrapMock,
-    }));
   });
 
   const renderComponent = (
@@ -185,9 +186,9 @@ describe('TopicTable Components', () => {
             ).toBeInTheDocument();
             const confirmBtn = getButtonByName('Confirm');
             expect(confirmBtn).toBeInTheDocument();
-            expect(unwrapMock).not.toHaveBeenCalled();
+            expect(clearTopicMessages).not.toHaveBeenCalled();
             await userEvent.click(confirmBtn);
-            expect(unwrapMock).toHaveBeenCalledTimes(2);
+            expect(clearTopicMessages).toHaveBeenCalledTimes(2);
             expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked();
             expect(screen.getAllByRole('checkbox')[2]).not.toBeChecked();
           });
@@ -282,7 +283,7 @@ describe('TopicTable Components', () => {
           await userEvent.click(
             screen.getByRole('button', { name: 'Confirm' })
           );
-          expect(unwrapMock).toHaveBeenCalled();
+          expect(clearTopicMessages).toHaveBeenCalled();
         });
       });
 

+ 4 - 7
kafka-ui-react-app/src/components/Topics/Topic/Overview/ActionsCell.tsx

@@ -1,13 +1,11 @@
 import React from 'react';
 import { Action, Partition, ResourceType } from 'generated-sources';
 import { CellContext } from '@tanstack/react-table';
-import { useAppDispatch } from 'lib/hooks/redux';
 import ClusterContext from 'components/contexts/ClusterContext';
 import { RouteParamsClusterTopic } from 'lib/paths';
 import useAppParams from 'lib/hooks/useAppParams';
-import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { Dropdown } from 'components/common/Dropdown';
-import { useTopicDetails } from 'lib/hooks/api/topics';
+import { useClearTopicMessages, useTopicDetails } from 'lib/hooks/api/topics';
 import { ActionDropdownItem } from 'components/common/ActionComponent';
 
 const ActionsCell: React.FC<CellContext<Partition, unknown>> = ({ row }) => {
@@ -15,12 +13,11 @@ const ActionsCell: React.FC<CellContext<Partition, unknown>> = ({ row }) => {
   const { data } = useTopicDetails({ clusterName, topicName });
   const { isReadOnly } = React.useContext(ClusterContext);
   const { partition } = row.original;
-  const dispatch = useAppDispatch();
+
+  const clearMessages = useClearTopicMessages(clusterName, [partition]);
 
   const clearTopicMessagesHandler = async () => {
-    await dispatch(
-      clearTopicMessages({ clusterName, topicName, partitions: [partition] })
-    ).unwrap();
+    await clearMessages.mutateAsync(topicName);
   };
   const disabled =
     data?.internal || isReadOnly || data?.cleanUpPolicy !== 'DELETE';

+ 7 - 15
kafka-ui-react-app/src/components/Topics/Topic/Overview/__test__/Overview.spec.tsx

@@ -8,8 +8,7 @@ import ClusterContext from 'components/contexts/ClusterContext';
 import userEvent from '@testing-library/user-event';
 import { clusterTopicPath } from 'lib/paths';
 import { Replica } from 'components/Topics/Topic/Overview/Overview.styled';
-import { useTopicDetails } from 'lib/hooks/api/topics';
-import { useAppDispatch } from 'lib/hooks/redux';
+import { useClearTopicMessages, useTopicDetails } from 'lib/hooks/api/topics';
 import {
   externalTopicPayload,
   internalTopicPayload,
@@ -26,14 +25,10 @@ const defaultContextValues = {
 
 jest.mock('lib/hooks/api/topics', () => ({
   useTopicDetails: jest.fn(),
+  useClearTopicMessages: jest.fn(),
 }));
 
-const unwrapMock = jest.fn();
-
-jest.mock('lib/hooks/redux', () => ({
-  ...jest.requireActual('lib/hooks/redux'),
-  useAppDispatch: jest.fn(),
-}));
+const clearTopicMessage = jest.fn();
 
 describe('Overview', () => {
   const renderComponent = (
@@ -43,6 +38,9 @@ describe('Overview', () => {
     (useTopicDetails as jest.Mock).mockImplementation(() => ({
       data: topic,
     }));
+    (useClearTopicMessages as jest.Mock).mockImplementation(() => ({
+      mutateAsync: clearTopicMessage,
+    }));
     const path = clusterTopicPath(clusterName, topicName);
     return render(
       <WithRoute path={clusterTopicPath()}>
@@ -54,12 +52,6 @@ describe('Overview', () => {
     );
   };
 
-  beforeEach(() => {
-    (useAppDispatch as jest.Mock).mockImplementation(() => () => ({
-      unwrap: unwrapMock,
-    }));
-  });
-
   it('at least one replica was rendered', () => {
     renderComponent();
     expect(screen.getByLabelText('replica-info')).toBeInTheDocument();
@@ -136,7 +128,7 @@ describe('Overview', () => {
 
       const clearMessagesButton = screen.getByText('Clear Messages');
       await userEvent.click(clearMessagesButton);
-      expect(unwrapMock).toHaveBeenCalledTimes(1);
+      expect(clearTopicMessage).toHaveBeenCalledTimes(1);
     });
   });
 

+ 7 - 9
kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx

@@ -21,14 +21,12 @@ import { useAppDispatch } from 'lib/hooks/redux';
 import useAppParams from 'lib/hooks/useAppParams';
 import { Dropdown, DropdownItemHint } from 'components/common/Dropdown';
 import {
+  useClearTopicMessages,
   useDeleteTopic,
   useRecreateTopic,
   useTopicDetails,
 } from 'lib/hooks/api/topics';
-import {
-  clearTopicMessages,
-  resetTopicMessages,
-} from 'redux/reducers/topicMessages/topicMessagesSlice';
+import { resetTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { Action, CleanUpPolicy, ResourceType } from 'generated-sources';
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import SlidingSidebar from 'components/common/SlidingSidebar';
@@ -69,9 +67,11 @@ const Topic: React.FC = () => {
       dispatch(resetTopicMessages());
     };
   }, []);
-
+  const clearMessages = useClearTopicMessages(clusterName);
+  const clearTopicMessagesHandler = async () => {
+    await clearMessages.mutateAsync(topicName);
+  };
   const canCleanup = data?.cleanUpPolicy === CleanUpPolicy.DELETE;
-
   return (
     <>
       <PageHeading
@@ -110,9 +110,7 @@ const Topic: React.FC = () => {
           </ActionDropdownItem>
 
           <ActionDropdownItem
-            onClick={() =>
-              dispatch(clearTopicMessages({ clusterName, topicName })).unwrap()
-            }
+            onClick={clearTopicMessagesHandler}
             confirm="Are you sure want to clear topic messages?"
             disabled={!canCleanup}
             danger

+ 7 - 1
kafka-ui-react-app/src/components/Topics/Topic/__test__/Topic.spec.tsx

@@ -16,6 +16,7 @@ import {
 import { CleanUpPolicy, Topic } from 'generated-sources';
 import { externalTopicPayload } from 'lib/fixtures/topics';
 import {
+  useClearTopicMessages,
   useDeleteTopic,
   useRecreateTopic,
   useTopicDetails,
@@ -31,9 +32,11 @@ jest.mock('lib/hooks/api/topics', () => ({
   useTopicDetails: jest.fn(),
   useDeleteTopic: jest.fn(),
   useRecreateTopic: jest.fn(),
+  useClearTopicMessages: jest.fn(),
 }));
 
 const unwrapMock = jest.fn();
+const clearTopicMessages = jest.fn();
 
 jest.mock('lib/hooks/redux', () => ({
   ...jest.requireActual('lib/hooks/redux'),
@@ -98,6 +101,9 @@ describe('Details', () => {
     (useRecreateTopic as jest.Mock).mockImplementation(() => ({
       mutateAsync: mockRecreate,
     }));
+    (useClearTopicMessages as jest.Mock).mockImplementation(() => ({
+      mutateAsync: clearTopicMessages,
+    }));
     (useAppDispatch as jest.Mock).mockImplementation(() => () => ({
       unwrap: unwrapMock,
     }));
@@ -145,7 +151,7 @@ describe('Details', () => {
           name: 'Confirm',
         })[0];
         await waitFor(() => userEvent.click(submitButton));
-        expect(unwrapMock).toHaveBeenCalledTimes(1);
+        expect(clearTopicMessages).toHaveBeenCalledTimes(1);
       });
 
       it('closes the modal when cancel button is clicked', async () => {

+ 29 - 0
kafka-ui-react-app/src/lib/hooks/api/topics.ts

@@ -2,6 +2,7 @@ import {
   topicsApiClient as api,
   messagesApiClient as messagesApi,
   consumerGroupsApiClient,
+  messagesApiClient,
 } from 'lib/api';
 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import {
@@ -233,6 +234,34 @@ export function useDeleteTopic(clusterName: ClusterName) {
     }
   );
 }
+
+export function useClearTopicMessages(
+  clusterName: ClusterName,
+  partitions?: number[]
+) {
+  const client = useQueryClient();
+  return useMutation(
+    async (topicName: Topic['name']) => {
+      await messagesApiClient.deleteTopicMessages({
+        clusterName,
+        partitions,
+        topicName,
+      });
+      return topicName;
+    },
+
+    {
+      onSuccess: (topicName) => {
+        showSuccessAlert({
+          id: `message-${topicName}-${clusterName}-${partitions}`,
+          message: `${topicName} messages have been successfully cleared!`,
+        });
+        client.invalidateQueries(topicKeys.all(clusterName));
+      },
+    }
+  );
+}
+
 export function useRecreateTopic(props: GetTopicDetailsRequest) {
   const client = useQueryClient();
   return useMutation(() => api.recreateTopic(props), {

+ 0 - 22
kafka-ui-react-app/src/redux/reducers/topicMessages/__test__/reducer.spec.ts

@@ -1,6 +1,5 @@
 import reducer, {
   addTopicMessage,
-  clearTopicMessages,
   resetTopicMessages,
   updateTopicMessagesMeta,
   updateTopicMessagesPhase,
@@ -12,9 +11,6 @@ import {
   topicMessagesMetaPayload,
 } from './fixtures';
 
-const clusterName = 'local';
-const topicName = 'localTopic';
-
 describe('TopicMessages reducer', () => {
   it('Adds new message', () => {
     const state = reducer(
@@ -67,24 +63,6 @@ describe('TopicMessages reducer', () => {
     expect(newState.messages.length).toEqual(0);
   });
 
-  it('clear messages', () => {
-    const state = reducer(
-      undefined,
-      addTopicMessage({ message: topicMessagePayload })
-    );
-    expect(state.messages.length).toEqual(1);
-
-    expect(
-      reducer(state, {
-        type: clearTopicMessages.fulfilled,
-        payload: { clusterName, topicName },
-      })
-    ).toEqual({
-      ...state,
-      messages: [],
-    });
-  });
-
   it('Updates Topic Messages Phase', () => {
     const phase = 'Polling';
 

+ 2 - 37
kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts

@@ -1,36 +1,6 @@
-import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
-import { TopicMessagesState, ClusterName, TopicName } from 'redux/interfaces';
+import { createSlice } from '@reduxjs/toolkit';
+import { TopicMessagesState } from 'redux/interfaces';
 import { TopicMessage } from 'generated-sources';
-import {
-  getResponse,
-  showServerError,
-  showSuccessAlert,
-} from 'lib/errorHandling';
-import { messagesApiClient } from 'lib/api';
-
-export const clearTopicMessages = createAsyncThunk<
-  undefined,
-  { clusterName: ClusterName; topicName: TopicName; partitions?: number[] }
->(
-  'topicMessages/clearTopicMessages',
-  async ({ clusterName, topicName, partitions }, { rejectWithValue }) => {
-    try {
-      await messagesApiClient.deleteTopicMessages({
-        clusterName,
-        topicName,
-        partitions,
-      });
-      showSuccessAlert({
-        id: `message-${topicName}-${clusterName}-${partitions}`,
-        message: `${topicName} messages have been successfully cleared!`,
-      });
-      return undefined;
-    } catch (err) {
-      showServerError(err as Response);
-      return rejectWithValue(await getResponse(err as Response));
-    }
-  }
-);
 
 export const initialState: TopicMessagesState = {
   messages: [],
@@ -68,11 +38,6 @@ const topicMessagesSlice = createSlice({
       state.isFetching = action.payload;
     },
   },
-  extraReducers: (builder) => {
-    builder.addCase(clearTopicMessages.fulfilled, (state) => {
-      state.messages = [];
-    });
-  },
 });
 
 export const {