Sfoglia il codice sorgente

Refactor TopicMessages's store to redux/toolkit (#1902)

* Refactor TopicMessages's store to redux/toolkit

* adding test cases to improve coverage

* fixing eslint error

* removing extra code

* Transform redux connect reducer and actions into toolkit slice. (#1883)

* integrate redux toolkit for connects

* uncomment test case

* remove unnecessary comment

* reduce duplication

* Implement code highlighting for smart filters (#1868)

* Implement code highlighting for smart filters

* delete unnecessary code

* fixing eslint problem

* fixing sonar code smells (#1826)

* fixing sonar code smells

* removing unnecessary change

* fixing some sonar code smell issues

* making requested changes

* Fix sonar badges in readme (#1906)

* fixing merge conflicts

Co-authored-by: Oleg Shur <workshur@gmail.com>

* Add positive notifications after some successful actions. (#1830)

* add some positive notifications after successful actions

* some improvements

* improve alerts reducer tests

* Fix test warnings (#1908)

* fix fetch mock warnings in brokers.spec.tsx

* use separate waitFor-s for fetch-mock call expects

* Persist show internal topics switch state (#1832)

* persist show internal topics switch state

* minor improvements in topics list tests

* prevent duplication in topic list test file

* fix seek type select border-radius and user-select (#1904)

* Refetch topics list to display the updated data (#1900)

* refetch topics list to display the updated data

* fixing sonar code smells (#1826)

* fixing sonar code smells

* removing unnecessary change

* fixing some sonar code smell issues

* making requested changes

* Fix sonar badges in readme (#1906)

Co-authored-by: Robert Azizbekyan <103438454+rAzizbekyan@users.noreply.github.com>
Co-authored-by: Oleg Shur <workshur@gmail.com>

* Show confirmation modal when clear messages is clicked in topics list (#1899)

* show confirmation modal when clear messages is clicked in topics list

* change variable name

* add missing dependencies

* use useModal hook for topics list modals

* display a human-readable error message for topic custom parameers value field (#1896)

* modify dependencies to fix partitions filter bug (#1901)

Co-authored-by: Arsen Simonyan <103444767+simonyandev@users.noreply.github.com>
Co-authored-by: Oleg Shur <workshur@gmail.com>
Robert Azizbekyan 3 anni fa
parent
commit
81d1e955da
20 ha cambiato i file con 317 aggiunte e 279 eliminazioni
  1. 160 67
      kafka-ui-react-app/package-lock.json
  2. 6 6
      kafka-ui-react-app/src/components/Topics/List/List.tsx
  3. 1 1
      kafka-ui-react-app/src/components/Topics/List/ListContainer.ts
  4. 2 0
      kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx
  5. 5 2
      kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx
  6. 2 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts
  7. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/FiltersContainer.ts
  8. 10 8
      kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx
  9. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/OverviewContainer.ts
  10. 4 4
      kafka-ui-react-app/src/components/Topics/Topic/Details/__test__/Details.spec.tsx
  11. 2 1
      kafka-ui-react-app/src/components/Topics/Topic/TopicContainer.tsx
  12. 0 37
      kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts
  13. 0 32
      kafka-ui-react-app/src/redux/actions/__test__/thunks/topics.spec.ts
  14. 0 21
      kafka-ui-react-app/src/redux/actions/actions.ts
  15. 2 33
      kafka-ui-react-app/src/redux/actions/thunks/topics.ts
  16. 1 1
      kafka-ui-react-app/src/redux/reducers/index.ts
  17. 26 4
      kafka-ui-react-app/src/redux/reducers/topicMessages/__test__/reducer.spec.ts
  18. 2 2
      kafka-ui-react-app/src/redux/reducers/topicMessages/__test__/selectors.spec.ts
  19. 0 57
      kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts
  20. 92 0
      kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts

File diff suppressed because it is too large
+ 160 - 67
kafka-ui-react-app/package-lock.json


+ 6 - 6
kafka-ui-react-app/src/components/Topics/List/List.tsx

@@ -50,11 +50,11 @@ export interface TopicsListProps {
   deleteTopics(topicName: TopicName, clusterNames: ClusterName[]): void;
   recreateTopic(topicName: TopicName, clusterName: ClusterName): void;
   clearTopicsMessages(topicName: TopicName, clusterNames: ClusterName[]): void;
-  clearTopicMessages(
-    topicName: TopicName,
-    clusterName: ClusterName,
-    partitions?: number[]
-  ): void;
+  clearTopicMessages(params: {
+    topicName: TopicName;
+    clusterName: ClusterName;
+    partitions?: number[];
+  }): void;
   search: string;
   orderBy: TopicColumnsToSort | null;
   sortOrder: SortOrder;
@@ -225,7 +225,7 @@ const List: React.FC<TopicsListProps> = ({
       }, [name]);
 
       const clearTopicMessagesHandler = React.useCallback(() => {
-        clearTopicMessages(clusterName, name);
+        clearTopicMessages({ clusterName, topicName: name });
         fetchTopicsList(topicsListParams);
         closeClearMessagesModal();
       }, [name, fetchTopicsList, topicsListParams]);

+ 1 - 1
kafka-ui-react-app/src/components/Topics/List/ListContainer.ts

@@ -6,10 +6,10 @@ import {
   deleteTopics,
   recreateTopic,
   clearTopicsMessages,
-  clearTopicMessages,
   setTopicsSearchAction,
   setTopicsOrderByAction,
 } from 'redux/actions';
+import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   getTopicList,
   getAreTopicsFetching,

+ 2 - 0
kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx

@@ -298,10 +298,12 @@ describe('List', () => {
 
     it('triggers the deleteTopics when clicked on the delete button', async () => {
       await checkActionButtonClick('deleteTopics');
+      expect(mockDeleteTopics).toBeCalledTimes(1);
     });
 
     it('triggers the clearTopicsMessages when clicked on the clear button', async () => {
       await checkActionButtonClick('clearTopicsMessages');
+      expect(mockClearTopicsMessages).toBeCalledTimes(1);
     });
 
     it('closes ConfirmationModal when clicked on the cancel button', async () => {

+ 5 - 2
kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx

@@ -37,7 +37,10 @@ interface Props extends Topic, TopicDetails {
   isDeletePolicy: boolean;
   deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
   recreateTopic: (clusterName: ClusterName, topicName: TopicName) => void;
-  clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void;
+  clearTopicMessages(params: {
+    clusterName: ClusterName;
+    topicName: TopicName;
+  }): void;
 }
 
 const HeaderControlsWrapper = styled.div`
@@ -81,7 +84,7 @@ const Details: React.FC<Props> = ({
   }, [isDeleted, clusterName, dispatch, history]);
 
   const clearTopicMessagesHandler = React.useCallback(() => {
-    clearTopicMessages(clusterName, topicName);
+    clearTopicMessages({ clusterName, topicName });
     setClearTopicConfirmationVisible(false);
   }, [clusterName, topicName, clearTopicMessages]);
 

+ 2 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/DetailsContainer.ts

@@ -1,7 +1,8 @@
 import { connect } from 'react-redux';
 import { ClusterName, RootState, TopicName } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
-import { deleteTopic, clearTopicMessages, recreateTopic } from 'redux/actions';
+import { deleteTopic, recreateTopic } from 'redux/actions';
+import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   getIsTopicDeleted,
   getIsTopicDeletePolicy,

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/FiltersContainer.ts

@@ -7,7 +7,7 @@ import {
   updateTopicMessagesMeta,
   updateTopicMessagesPhase,
   setTopicMessagesFetchingStatus,
-} from 'redux/actions';
+} from 'redux/reducers/topicMessages/topicMessagesSlice';
 import {
   getTopicMessgesMeta,
   getTopicMessgesPhase,

+ 10 - 8
kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/Overview.tsx

@@ -14,11 +14,11 @@ import { Tag } from 'components/common/Tag/Tag.styled';
 export interface Props extends Topic, TopicDetails {
   clusterName: ClusterName;
   topicName: TopicName;
-  clearTopicMessages(
-    clusterName: ClusterName,
-    topicName: TopicName,
-    partitions?: number[]
-  ): void;
+  clearTopicMessages(params: {
+    clusterName: ClusterName;
+    topicName: TopicName;
+    partitions?: number[];
+  }): void;
 }
 
 const Overview: React.FC<Props> = ({
@@ -120,9 +120,11 @@ const Overview: React.FC<Props> = ({
                     <Dropdown label={<VerticalElipsisIcon />} right>
                       <DropdownItem
                         onClick={() =>
-                          clearTopicMessages(clusterName, topicName, [
-                            partition,
-                          ])
+                          clearTopicMessages({
+                            clusterName,
+                            topicName,
+                            partitions: [partition],
+                          })
                         }
                         danger
                       >

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/OverviewContainer.ts

@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
 import { RootState, TopicName, ClusterName } from 'redux/interfaces';
 import { getTopicByName } from 'redux/reducers/topics/selectors';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
-import { clearTopicMessages } from 'redux/actions';
+import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import Overview from 'components/Topics/Topic/Details/Overview/Overview';
 
 interface RouteProps {

+ 4 - 4
kafka-ui-react-app/src/components/Topics/Topic/Details/__test__/Details.spec.tsx

@@ -127,10 +127,10 @@ describe('Details', () => {
       const submitButton = screen.getAllByText('Submit')[0];
       userEvent.click(submitButton);
 
-      expect(mockClearTopicMessages).toHaveBeenCalledWith(
-        mockClusterName,
-        internalTopicPayload.name
-      );
+      expect(mockClearTopicMessages).toHaveBeenCalledWith({
+        clusterName: mockClusterName,
+        topicName: internalTopicPayload.name,
+      });
     });
 
     it('closes the modal when cancel button is clicked', () => {

+ 2 - 1
kafka-ui-react-app/src/components/Topics/Topic/TopicContainer.tsx

@@ -1,6 +1,7 @@
 import { connect } from 'react-redux';
 import { RootState } from 'redux/interfaces';
-import { fetchTopicDetails, resetTopicMessages } from 'redux/actions';
+import { fetchTopicDetails } from 'redux/actions';
+import { resetTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { getIsTopicDetailsFetching } from 'redux/reducers/topics/selectors';
 
 import Topic from './Topic';

+ 0 - 37
kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts

@@ -5,10 +5,6 @@ import {
   TopicMessageSchema,
 } from 'generated-sources';
 import { FailurePayload } from 'redux/interfaces';
-import {
-  topicMessagePayload,
-  topicMessagesMetaPayload,
-} from 'redux/reducers/topicMessages/__test__/fixtures';
 
 import { mockTopicsState } from './fixtures';
 
@@ -85,39 +81,6 @@ describe('Actions', () => {
     });
   });
 
-  describe('topic messages', () => {
-    it('creates ADD_TOPIC_MESSAGE', () => {
-      expect(actions.addTopicMessage({ message: topicMessagePayload })).toEqual(
-        {
-          type: 'ADD_TOPIC_MESSAGE',
-          payload: { message: topicMessagePayload },
-        }
-      );
-    });
-
-    it('creates RESET_TOPIC_MESSAGES', () => {
-      expect(actions.resetTopicMessages()).toEqual({
-        type: 'RESET_TOPIC_MESSAGES',
-      });
-    });
-
-    it('creates UPDATE_TOPIC_MESSAGES_PHASE', () => {
-      expect(actions.updateTopicMessagesPhase('Polling')).toEqual({
-        type: 'UPDATE_TOPIC_MESSAGES_PHASE',
-        payload: 'Polling',
-      });
-    });
-
-    it('creates UPDATE_TOPIC_MESSAGES_META', () => {
-      expect(actions.updateTopicMessagesMeta(topicMessagesMetaPayload)).toEqual(
-        {
-          type: 'UPDATE_TOPIC_MESSAGES_META',
-          payload: topicMessagesMetaPayload,
-        }
-      );
-    });
-  });
-
   describe('sending messages', () => {
     describe('fetchTopicMessageSchemaAction', () => {
       it('creates GET_TOPIC_SCHEMA__REQUEST', () => {

+ 0 - 32
kafka-ui-react-app/src/redux/actions/__test__/thunks/topics.spec.ts

@@ -83,38 +83,6 @@ describe('Thunks', () => {
     });
   });
 
-  describe('clearTopicMessages', () => {
-    it('creates CLEAR_TOPIC_MESSAGES__SUCCESS when deleting existing messages', async () => {
-      fetchMock.deleteOnce(
-        `/api/clusters/${clusterName}/topics/${topicName}/messages`,
-        200
-      );
-      await store.dispatch(thunks.clearTopicMessages(clusterName, topicName));
-      expect(getTypeAndPayload(store)).toEqual([
-        actions.clearMessagesTopicAction.request(),
-        actions.clearMessagesTopicAction.success(),
-        ...getAlertActions(store),
-      ]);
-    });
-
-    it('creates CLEAR_TOPIC_MESSAGES__FAILURE when deleting existing messages', async () => {
-      fetchMock.deleteOnce(
-        `/api/clusters/${clusterName}/topics/${topicName}/messages`,
-        404
-      );
-      try {
-        await store.dispatch(thunks.clearTopicMessages(clusterName, topicName));
-      } catch (error) {
-        const err = error as Response;
-        expect(err.status).toEqual(404);
-        expect(store.getActions()).toEqual([
-          actions.clearMessagesTopicAction.request(),
-          actions.clearMessagesTopicAction.failure({}),
-        ]);
-      }
-    });
-  });
-
   describe('fetchTopicConsumerGroups', () => {
     it('GET_TOPIC_CONSUMER_GROUPS__FAILURE', async () => {
       fetchMock.getOnce(

+ 0 - 21
kafka-ui-react-app/src/redux/actions/actions.ts

@@ -3,8 +3,6 @@ import { FailurePayload, TopicName, TopicsState } from 'redux/interfaces';
 import {
   TopicColumnsToSort,
   Topic,
-  TopicMessage,
-  TopicMessageConsuming,
   TopicMessageSchema,
 } from 'generated-sources';
 
@@ -73,25 +71,6 @@ export const fetchTopicConsumerGroupsAction = createAsyncAction(
   'GET_TOPIC_CONSUMER_GROUPS__FAILURE'
 )<undefined, TopicsState, undefined>();
 
-export const addTopicMessage = createAction('ADD_TOPIC_MESSAGE')<{
-  message: TopicMessage;
-  prepend?: boolean;
-}>();
-
-export const resetTopicMessages = createAction('RESET_TOPIC_MESSAGES')();
-
-export const setTopicMessagesFetchingStatus = createAction(
-  'SET_TOPIC_MESSAGES_FETCHING_STATUS'
-)<boolean>();
-
-export const updateTopicMessagesPhase = createAction(
-  'UPDATE_TOPIC_MESSAGES_PHASE'
-)<string>();
-
-export const updateTopicMessagesMeta = createAction(
-  'UPDATE_TOPIC_MESSAGES_META'
-)<TopicMessageConsuming>();
-
 export const fetchTopicMessageSchemaAction = createAsyncAction(
   'GET_TOPIC_SCHEMA__REQUEST',
   'GET_TOPIC_SCHEMA__SUCCESS',

+ 2 - 33
kafka-ui-react-app/src/redux/actions/thunks/topics.ts

@@ -21,6 +21,7 @@ import {
   TopicFormData,
   AppDispatch,
 } from 'redux/interfaces';
+import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
 import { BASE_PARAMS } from 'lib/constants';
 import * as actions from 'redux/actions/actions';
 import { getResponse } from 'lib/errorHandling';
@@ -63,44 +64,12 @@ export const fetchTopicsList =
       dispatch(actions.fetchTopicsListAction.failure());
     }
   };
-export const clearTopicMessages =
-  (
-    clusterName: ClusterName,
-    topicName: TopicName,
-    partitions?: number[]
-  ): PromiseThunkResult =>
-  async (dispatch) => {
-    dispatch(actions.clearMessagesTopicAction.request());
-    try {
-      await messagesApiClient.deleteTopicMessages({
-        clusterName,
-        topicName,
-        partitions,
-      });
-      dispatch(actions.clearMessagesTopicAction.success());
-
-      (dispatch as AppDispatch)(
-        showSuccessAlert({
-          id: `message-${topicName}-${clusterName}-${partitions}`,
-          message: 'Messages successfully cleared!',
-        })
-      );
-    } catch (e) {
-      const response = await getResponse(e);
-      const alert: FailurePayload = {
-        subject: [clusterName, topicName, partitions].join('-'),
-        title: `Clear Topic Messages`,
-        response,
-      };
-      dispatch(actions.clearMessagesTopicAction.failure({ alert }));
-    }
-  };
 
 export const clearTopicsMessages =
   (clusterName: ClusterName, topicsName: TopicName[]): PromiseThunkResult =>
   async (dispatch) => {
     topicsName.forEach((topicName) => {
-      dispatch(clearTopicMessages(clusterName, topicName));
+      dispatch(clearTopicMessages({ clusterName, topicName }));
     });
   };
 

+ 1 - 1
kafka-ui-react-app/src/redux/reducers/index.ts

@@ -7,7 +7,7 @@ import schemas from 'redux/reducers/schemas/schemasSlice';
 import connect from 'redux/reducers/connect/connectSlice';
 
 import topics from './topics/reducer';
-import topicMessages from './topicMessages/reducer';
+import topicMessages from './topicMessages/topicMessagesSlice';
 import consumerGroups from './consumerGroups/consumerGroupsSlice';
 import ksqlDb from './ksqlDb/ksqlDbSlice';
 import legacyLoader from './loader/reducer';

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

@@ -1,10 +1,10 @@
-import {
+import reducer, {
   addTopicMessage,
+  clearTopicMessages,
   resetTopicMessages,
   updateTopicMessagesMeta,
   updateTopicMessagesPhase,
-} from 'redux/actions';
-import reducer from 'redux/reducers/topicMessages/reducer';
+} from 'redux/reducers/topicMessages/topicMessagesSlice';
 
 import {
   topicMessagePayload,
@@ -12,6 +12,9 @@ import {
   topicMessagesMetaPayload,
 } from './fixtures';
 
+const clusterName = 'local';
+const topicName = 'localTopic';
+
 describe('TopicMessages reducer', () => {
   it('Adds new message', () => {
     const state = reducer(
@@ -53,7 +56,7 @@ describe('TopicMessages reducer', () => {
     ]);
   });
 
-  it('Clears messages', () => {
+  it('reset messages', () => {
     const state = reducer(
       undefined,
       addTopicMessage({ message: topicMessagePayload })
@@ -63,6 +66,25 @@ describe('TopicMessages reducer', () => {
     const newState = reducer(state, resetTopicMessages());
     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 - 2
kafka-ui-react-app/src/redux/reducers/topicMessages/__test__/selectors.spec.ts

@@ -1,11 +1,11 @@
 import { store } from 'redux/store';
 import * as selectors from 'redux/reducers/topicMessages/selectors';
-import { initialState } from 'redux/reducers/topicMessages/reducer';
 import {
+  initialState,
   addTopicMessage,
   updateTopicMessagesMeta,
   updateTopicMessagesPhase,
-} from 'redux/actions';
+} from 'redux/reducers/topicMessages/topicMessagesSlice';
 
 import { topicMessagePayload, topicMessagesMetaPayload } from './fixtures';
 

+ 0 - 57
kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts

@@ -1,57 +0,0 @@
-import { Action, TopicMessagesState } from 'redux/interfaces';
-import { getType } from 'typesafe-actions';
-import * as actions from 'redux/actions';
-import { TopicMessage } from 'generated-sources';
-
-export const initialState: TopicMessagesState = {
-  messages: [],
-  meta: {
-    bytesConsumed: 0,
-    elapsedMs: 0,
-    messagesConsumed: 0,
-    isCancelled: false,
-  },
-  isFetching: false,
-};
-
-// eslint-disable-next-line @typescript-eslint/default-param-last
-const reducer = (state = initialState, action: Action): TopicMessagesState => {
-  switch (action.type) {
-    case getType(actions.addTopicMessage): {
-      const messages: TopicMessage[] = action.payload.prepend
-        ? [action.payload.message, ...state.messages]
-        : [...state.messages, action.payload.message];
-
-      return {
-        ...state,
-        messages,
-      };
-    }
-    case getType(actions.resetTopicMessages):
-      return initialState;
-    case getType(actions.updateTopicMessagesPhase):
-      return {
-        ...state,
-        phase: action.payload,
-      };
-    case getType(actions.updateTopicMessagesMeta):
-      return {
-        ...state,
-        meta: action.payload,
-      };
-    case getType(actions.setTopicMessagesFetchingStatus):
-      return {
-        ...state,
-        isFetching: action.payload,
-      };
-    case getType(actions.clearMessagesTopicAction.success):
-      return {
-        ...state,
-        messages: [],
-      };
-    default:
-      return state;
-  }
-};
-
-export default reducer;

+ 92 - 0
kafka-ui-react-app/src/redux/reducers/topicMessages/topicMessagesSlice.ts

@@ -0,0 +1,92 @@
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+import { TopicMessagesState, ClusterName, TopicName } from 'redux/interfaces';
+import { TopicMessage, Configuration, MessagesApi } from 'generated-sources';
+import { BASE_PARAMS } from 'lib/constants';
+import { getResponse } from 'lib/errorHandling';
+import { showSuccessAlert } from 'redux/reducers/alerts/alertsSlice';
+
+const apiClientConf = new Configuration(BASE_PARAMS);
+export const messagesApiClient = new MessagesApi(apiClientConf);
+
+export const clearTopicMessages = createAsyncThunk<
+  undefined,
+  { clusterName: ClusterName; topicName: TopicName; partitions?: number[] }
+>(
+  'topicMessages/clearTopicMessages',
+  async (
+    { clusterName, topicName, partitions },
+    { rejectWithValue, dispatch }
+  ) => {
+    try {
+      await messagesApiClient.deleteTopicMessages({
+        clusterName,
+        topicName,
+        partitions,
+      });
+
+      dispatch(
+        showSuccessAlert({
+          id: `message-${topicName}-${clusterName}-${partitions}`,
+          message: 'Messages successfully cleared!',
+        })
+      );
+
+      return undefined;
+    } catch (err) {
+      return rejectWithValue(await getResponse(err as Response));
+    }
+  }
+);
+
+export const initialState: TopicMessagesState = {
+  messages: [],
+  meta: {
+    bytesConsumed: 0,
+    elapsedMs: 0,
+    messagesConsumed: 0,
+    isCancelled: false,
+  },
+  isFetching: false,
+};
+
+export const topicMessagesSlice = createSlice({
+  name: 'topicMessages',
+  initialState,
+  reducers: {
+    addTopicMessage: (state, action) => {
+      const messages: TopicMessage[] = action.payload.prepend
+        ? [action.payload.message, ...state.messages]
+        : [...state.messages, action.payload.message];
+
+      return {
+        ...state,
+        messages,
+      };
+    },
+    resetTopicMessages: () => initialState,
+    updateTopicMessagesPhase: (state, action) => {
+      state.phase = action.payload;
+    },
+    updateTopicMessagesMeta: (state, action) => {
+      state.meta = action.payload;
+    },
+    setTopicMessagesFetchingStatus: (state, action) => {
+      state.isFetching = action.payload;
+    },
+  },
+  extraReducers: (builder) => {
+    builder.addCase(clearTopicMessages.fulfilled, (state) => {
+      state.messages = [];
+    });
+  },
+});
+
+export const {
+  addTopicMessage,
+  resetTopicMessages,
+  updateTopicMessagesPhase,
+  updateTopicMessagesMeta,
+  setTopicMessagesFetchingStatus,
+} = topicMessagesSlice.actions;
+
+export default topicMessagesSlice.reducer;

Some files were not shown because too many files changed in this diff