Repository: provectus/kafka-ui
Issue: Live tailing improvements
Labels: type/enhancement
good first issue
scope/frontend
status/accepted

1. New messages should appear on top
2. Pagination buttons (e.g. "Next") shouldn't be visible.
3.
wrong parameters passed for live tailing: 
`https://www.kafka-ui.provectus.io/api/clusters/local/topics/7890/messages?filterQueryType=STRING_CONTAINS&attempt=1&limit=100&seekDirection=TAILING&seekType=OFFSET&seekTo=0::0`

this should be `https://www.kafka-ui.provectus.io/api/clusters/local/topics/7890/messages&seekDirection=TAILING&seekType=LATEST&seekTo=0::0`

`seekType` should be `LATEST`
`attempt` has no effect (can be removed from all endpoints)
`limit` has no effect in TAILING mode
(not critical) `filterQueryType` make sense only when `q` param passed
`seekTo` should be 0::0,1::0,... where first number is partition

#943

Issue comments:
1. While live mode is on (active loading) all the controls (search, offset, partitions select) should be disabled. 
2. Once "stop loading" is pressed, fetched messages should NOT disappear. And controls (p. 1) should be enabled back again.

Pull request review comments:
`toggle` usually implies only two possible states **on** || **off** (true || false);

Let's try to fine more suitable name for this method. Eg: `setSeekDirection`  
Pls describe)
from my point of view, it is better when the name of test case describes the behavior of the component, and not the actions inside the test case
> it reacts to a change of seekDirection in the url ....
okay will do.
yeah it makes total sense , i will fix it.

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/KsqlDb/Query/__test__/Query.spec.tsx b/kafka-ui-react-app/src/components/KsqlDb/Query/__test__/Query.spec.tsx
index 0e4884ce1c3..7b6f133f2a5 100644
--- a/kafka-ui-react-app/src/components/KsqlDb/Query/__test__/Query.spec.tsx
+++ b/kafka-ui-react-app/src/components/KsqlDb/Query/__test__/Query.spec.tsx
@@ -1,4 +1,4 @@
-import { render } from 'lib/testHelpers';
+import { render, EventSourceMock } from 'lib/testHelpers';
 import React from 'react';
 import Query, {
   getFormattedErrorFromTableData,
@@ -20,27 +20,6 @@ const renderComponent = () =>
     }
   );
 
-// Small mock to get rid of reference error
-class EventSourceMock {
-  url: string;
-
-  close: () => void;
-
-  open: () => void;
-
-  error: () => void;
-
-  onmessage: () => void;
-
-  constructor(url: string) {
-    this.url = url;
-    this.open = jest.fn();
-    this.error = jest.fn();
-    this.onmessage = jest.fn();
-    this.close = jest.fn();
-  }
-}
-
 describe('Query', () => {
   it('renders', () => {
     renderComponent();
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx
index 84aaa0c7629..379a48f68e6 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx
@@ -10,7 +10,7 @@ import {
   TopicMessageEventTypeEnum,
   MessageFilterType,
 } from 'generated-sources';
-import React from 'react';
+import React, { useContext } from 'react';
 import { omitBy } from 'lodash';
 import { useHistory, useLocation } from 'react-router';
 import DatePicker from 'react-datepicker';
@@ -25,6 +25,8 @@ import { Button } from 'components/common/Button/Button';
 import FilterModal, {
   FilterEdit,
 } from 'components/Topics/Topic/Details/Messages/Filters/FilterModal';
+import { SeekDirectionOptions } from 'components/Topics/Topic/Details/Messages/Messages';
+import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
 
 import * as S from './Filters.styled';
 import {
@@ -66,11 +68,6 @@ export const SeekTypeOptions = [
   { value: SeekType.OFFSET, label: 'Offset' },
   { value: SeekType.TIMESTAMP, label: 'Timestamp' },
 ];
-export const SeekDirectionOptions = [
-  { value: SeekDirection.FORWARD, label: 'Oldest First', isLive: false },
-  { value: SeekDirection.BACKWARD, label: 'Newest First', isLive: false },
-  { value: SeekDirection.TAILING, label: 'Live Mode', isLive: true },
-];
 
 const Filters: React.FC<FiltersProps> = ({
   clusterName,
@@ -88,16 +85,14 @@ const Filters: React.FC<FiltersProps> = ({
   const location = useLocation();
   const history = useHistory();
 
+  const { searchParams, seekDirection, isLive, changeSeekDirection } =
+    useContext(TopicMessagesContext);
+
   const [isOpen, setIsOpen] = React.useState(false);
   const toggleIsOpen = () => setIsOpen(!isOpen);
 
   const source = React.useRef<EventSource | null>(null);
 
-  const searchParams = React.useMemo(
-    () => new URLSearchParams(location.search),
-    [location]
-  );
-
   const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
     getSelectedPartitionsFromSeekToParam(searchParams, partitions)
   );
@@ -132,10 +127,6 @@ const Filters: React.FC<FiltersProps> = ({
       : MessageFilterType.STRING_CONTAINS
   );
   const [query, setQuery] = React.useState<string>(searchParams.get('q') || '');
-  const [seekDirection, setSeekDirection] = React.useState<SeekDirection>(
-    (searchParams.get('seekDirection') as SeekDirection) ||
-      SeekDirection.FORWARD
-  );
   const isSeekTypeControlVisible = React.useMemo(
     () => selectedPartitions.length > 0,
     [selectedPartitions]
@@ -178,7 +169,7 @@ const Filters: React.FC<FiltersProps> = ({
     setAttempt(attempt + 1);
 
     if (isSeekTypeControlVisible) {
-      props.seekType = currentSeekType;
+      props.seekType = isLive ? SeekType.LATEST : currentSeekType;
       props.seekTo = selectedPartitions.map(({ value }) => {
         let seekToOffset;
 
@@ -217,21 +208,6 @@ const Filters: React.FC<FiltersProps> = ({
     query,
   ]);
 
-  const toggleSeekDirection = (val: string) => {
-    switch (val) {
-      case SeekDirection.FORWARD:
-        setSeekDirection(SeekDirection.FORWARD);
-        break;
-      case SeekDirection.BACKWARD:
-        setSeekDirection(SeekDirection.BACKWARD);
-        break;
-      case SeekDirection.TAILING:
-        setSeekDirection(SeekDirection.TAILING);
-        break;
-      default:
-    }
-  };
-
   const handleSSECancel = () => {
     if (!source.current) return;
 
@@ -295,7 +271,7 @@ const Filters: React.FC<FiltersProps> = ({
   };
   // eslint-disable-next-line consistent-return
   React.useEffect(() => {
-    if (location.search.length !== 0) {
+    if (location.search?.length !== 0) {
       const url = `${BASE_PARAMS.basePath}/api/clusters/${clusterName}/topics/${topicName}/messages${location.search}`;
       const sse = new EventSource(url);
 
@@ -346,7 +322,7 @@ const Filters: React.FC<FiltersProps> = ({
     updatePhase,
   ]);
   React.useEffect(() => {
-    if (location.search.length === 0) {
+    if (location.search?.length === 0) {
       handleFiltersSubmit();
     }
   }, [handleFiltersSubmit, location]);
@@ -376,7 +352,7 @@ const Filters: React.FC<FiltersProps> = ({
                 selectSize="M"
                 minWidth="100px"
                 options={SeekTypeOptions}
-                disabled={seekDirection === SeekDirection.TAILING}
+                disabled={isLive}
               />
               {currentSeekType === SeekType.OFFSET ? (
                 <Input
@@ -387,7 +363,7 @@ const Filters: React.FC<FiltersProps> = ({
                   className="offset-selector"
                   placeholder="Offset"
                   onChange={({ target: { value } }) => setOffset(value)}
-                  disabled={seekDirection === SeekDirection.TAILING}
+                  disabled={isLive}
                 />
               ) : (
                 <DatePicker
@@ -398,7 +374,7 @@ const Filters: React.FC<FiltersProps> = ({
                   dateFormat="MMMM d, yyyy HH:mm"
                   className="date-picker"
                   placeholderText="Select timestamp"
-                  disabled={seekDirection === SeekDirection.TAILING}
+                  disabled={isLive}
                 />
               )}
             </S.SeekTypeSelectorWrapper>
@@ -440,11 +416,11 @@ const Filters: React.FC<FiltersProps> = ({
         </S.FilterInputs>
         <Select
           selectSize="M"
-          onChange={(option) => toggleSeekDirection(option as string)}
+          onChange={(option) => changeSeekDirection(option as string)}
           value={seekDirection}
           minWidth="120px"
           options={SeekDirectionOptions}
-          isLive={seekDirection === SeekDirection.TAILING}
+          isLive={isLive}
         />
       </div>
       <S.ActiveSmartFilterWrapper>
@@ -479,12 +455,12 @@ const Filters: React.FC<FiltersProps> = ({
             isFetching &&
             phaseMessage}
         </p>
-        <S.MessageLoading isLive={seekDirection === SeekDirection.TAILING}>
+        <S.MessageLoading isLive={isLive}>
           <S.MessageLoadingSpinner isFetching={isFetching} />
           Loading messages.
           <S.StopLoading
             onClick={() => {
-              setSeekDirection(SeekDirection.FORWARD);
+              changeSeekDirection(SeekDirection.FORWARD);
               setIsFetching(false);
             }}
           >
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.spec.tsx
index 09418a5346a..4e453782026 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.spec.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.spec.tsx
@@ -1,29 +1,46 @@
 import React from 'react';
+import { SeekDirectionOptions } from 'components/Topics/Topic/Details/Messages/Messages';
 import Filters, {
   FiltersProps,
-  SeekDirectionOptions,
   SeekTypeOptions,
 } from 'components/Topics/Topic/Details/Messages/Filters/Filters';
 import { render } from 'lib/testHelpers';
 import { screen, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
+import TopicMessagesContext, {
+  ContextProps,
+} from 'components/contexts/TopicMessagesContext';
+import { SeekDirection } from 'generated-sources';
 
-const setupWrapper = (props?: Partial<FiltersProps>) =>
+const defaultContextValue: ContextProps = {
+  isLive: false,
+  seekDirection: SeekDirection.FORWARD,
+  searchParams: new URLSearchParams(''),
+  changeSeekDirection: jest.fn(),
+};
+
+const setupWrapper = (
+  props: Partial<FiltersProps> = {},
+  ctx: ContextProps = defaultContextValue
+) => {
   render(
-    <Filters
-      clusterName="test-cluster"
-      topicName="test-topic"
-      partitions={[{ partition: 0, offsetMin: 0, offsetMax: 100 }]}
-      meta={{}}
-      isFetching={false}
-      addMessage={jest.fn()}
-      resetMessages={jest.fn()}
-      updatePhase={jest.fn()}
-      updateMeta={jest.fn()}
-      setIsFetching={jest.fn()}
-      {...props}
-    />
+    <TopicMessagesContext.Provider value={ctx}>
+      <Filters
+        clusterName="test-cluster"
+        topicName="test-topic"
+        partitions={[{ partition: 0, offsetMin: 0, offsetMax: 100 }]}
+        meta={{}}
+        isFetching={false}
+        addMessage={jest.fn()}
+        resetMessages={jest.fn()}
+        updatePhase={jest.fn()}
+        updateMeta={jest.fn()}
+        setIsFetching={jest.fn()}
+        {...props}
+      />
+    </TopicMessagesContext.Provider>
   );
+};
 describe('Filters component', () => {
   it('renders component', () => {
     setupWrapper();
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx
index e685720f5f9..dde0eaa7ff1 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx
@@ -1,13 +1,84 @@
-import React from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
+import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
+import { SeekDirection } from 'generated-sources';
+import { useLocation } from 'react-router';
 
 import FiltersContainer from './Filters/FiltersContainer';
 import MessagesTable from './MessagesTable';
 
-const Messages: React.FC = () => (
-  <div>
-    <FiltersContainer />
-    <MessagesTable />
-  </div>
-);
+export const SeekDirectionOptionsObj = {
+  [SeekDirection.FORWARD]: {
+    value: SeekDirection.FORWARD,
+    label: 'Oldest First',
+    isLive: false,
+  },
+  [SeekDirection.BACKWARD]: {
+    value: SeekDirection.BACKWARD,
+    label: 'Newest First',
+    isLive: false,
+  },
+  [SeekDirection.TAILING]: {
+    value: SeekDirection.TAILING,
+    label: 'Live Mode',
+    isLive: true,
+  },
+};
+
+export const SeekDirectionOptions = Object.values(SeekDirectionOptionsObj);
+
+const Messages: React.FC = () => {
+  const location = useLocation();
+
+  const searchParams = React.useMemo(
+    () => new URLSearchParams(location.search),
+    [location.search]
+  );
+
+  const defaultSeekValue = SeekDirectionOptions[0];
+
+  const [seekDirection, setSeekDirection] = React.useState<SeekDirection>(
+    (searchParams.get('seekDirection') as SeekDirection) ||
+      defaultSeekValue.value
+  );
+
+  const [isLive, setIsLive] = useState<boolean>(
+    SeekDirectionOptionsObj[seekDirection].isLive
+  );
+
+  const changeSeekDirection = useCallback((val: string) => {
+    switch (val) {
+      case SeekDirection.FORWARD:
+        setSeekDirection(SeekDirection.FORWARD);
+        setIsLive(SeekDirectionOptionsObj[SeekDirection.FORWARD].isLive);
+        break;
+      case SeekDirection.BACKWARD:
+        setSeekDirection(SeekDirection.BACKWARD);
+        setIsLive(SeekDirectionOptionsObj[SeekDirection.BACKWARD].isLive);
+        break;
+      case SeekDirection.TAILING:
+        setSeekDirection(SeekDirection.TAILING);
+        setIsLive(SeekDirectionOptionsObj[SeekDirection.TAILING].isLive);
+        break;
+      default:
+    }
+  }, []);
+
+  const contextValue = useMemo(
+    () => ({
+      seekDirection,
+      searchParams,
+      changeSeekDirection,
+      isLive,
+    }),
+    [seekDirection, searchParams, changeSeekDirection]
+  );
+
+  return (
+    <TopicMessagesContext.Provider value={contextValue}>
+      <FiltersContainer />
+      <MessagesTable />
+    </TopicMessagesContext.Provider>
+  );
+};
 
 export default Messages;
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx
index f1fc9c14bfd..c3174403556 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx
@@ -4,13 +4,14 @@ import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeader
 import { SeekDirection, TopicMessage } from 'generated-sources';
 import styled from 'styled-components';
 import { compact, concat, groupBy, map, maxBy, minBy } from 'lodash';
-import React from 'react';
+import React, { useContext } from 'react';
 import { useSelector } from 'react-redux';
-import { useHistory, useLocation } from 'react-router';
+import { useHistory } from 'react-router';
 import {
   getTopicMessges,
   getIsTopicMessagesFetching,
 } from 'redux/reducers/topicMessages/selectors';
+import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
 
 import Message from './Message';
 import * as S from './MessageContent/MessageContent.styled';
@@ -22,13 +23,9 @@ const MessagesPaginationWrapperStyled = styled.div`
 `;
 
 const MessagesTable: React.FC = () => {
-  const location = useLocation();
   const history = useHistory();
 
-  const searchParams = React.useMemo(
-    () => new URLSearchParams(location.search),
-    [location]
-  );
+  const { searchParams, isLive } = useContext(TopicMessagesContext);
 
   const messages = useSelector(getTopicMessges);
   const isFetching = useSelector(getIsTopicMessagesFetching);
@@ -94,7 +91,7 @@ const MessagesTable: React.FC = () => {
               message={message}
             />
           ))}
-          {isFetching && (
+          {(isFetching || isLive) && !messages.length && (
             <tr>
               <td colSpan={10}>
                 <PageLoader />
@@ -108,9 +105,13 @@ const MessagesTable: React.FC = () => {
           )}
         </tbody>
       </Table>
-      <MessagesPaginationWrapperStyled>
-        <S.PaginationButton onClick={handleNextClick}>Next</S.PaginationButton>
-      </MessagesPaginationWrapperStyled>
+      {!isLive && (
+        <MessagesPaginationWrapperStyled>
+          <S.PaginationButton onClick={handleNextClick}>
+            Next
+          </S.PaginationButton>
+        </MessagesPaginationWrapperStyled>
+      )}
     </>
   );
 };
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Messages.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Messages.spec.tsx
new file mode 100644
index 00000000000..05d9eb0a461
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Messages.spec.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
+import { render, EventSourceMock } from 'lib/testHelpers';
+import Messages, {
+  SeekDirectionOptions,
+  SeekDirectionOptionsObj,
+} from 'components/Topics/Topic/Details/Messages/Messages';
+import { Router } from 'react-router-dom';
+import { createMemoryHistory } from 'history';
+import { SeekDirection, SeekType } from 'generated-sources';
+import userEvent from '@testing-library/user-event';
+
+describe('Messages', () => {
+  const searchParams = `?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}&seekTo=0::9`;
+
+  const setUpComponent = (param: string = searchParams) => {
+    const history = createMemoryHistory();
+    history.push({
+      search: new URLSearchParams(param).toString(),
+    });
+    return render(
+      <Router history={history}>
+        <Messages />
+      </Router>
+    );
+  };
+
+  beforeEach(() => {
+    Object.defineProperty(window, 'EventSource', {
+      value: EventSourceMock,
+    });
+  });
+  describe('component rendering default behavior with the search params', () => {
+    beforeEach(() => {
+      setUpComponent();
+    });
+    it('should check default seekDirection if it actually take the value from the url', () => {
+      expect(screen.getByRole('listbox')).toHaveTextContent(
+        SeekDirectionOptionsObj[SeekDirection.FORWARD].label
+      );
+    });
+
+    it('should check the SeekDirection select changes', () => {
+      const seekDirectionSelect = screen.getByRole('listbox');
+      const seekDirectionOption = screen.getByRole('option');
+
+      expect(seekDirectionOption).toHaveTextContent(
+        SeekDirectionOptionsObj[SeekDirection.FORWARD].label
+      );
+
+      const labelValue1 = SeekDirectionOptions[1].label;
+      userEvent.click(seekDirectionSelect);
+      userEvent.selectOptions(seekDirectionSelect, [
+        SeekDirectionOptions[1].label,
+      ]);
+      expect(seekDirectionOption).toHaveTextContent(labelValue1);
+
+      const labelValue0 = SeekDirectionOptions[0].label;
+      userEvent.click(seekDirectionSelect);
+      userEvent.selectOptions(seekDirectionSelect, [
+        SeekDirectionOptions[0].label,
+      ]);
+      expect(seekDirectionOption).toHaveTextContent(labelValue0);
+    });
+  });
+
+  describe('Component rendering with custom Url search params', () => {
+    it('reacts to a change of seekDirection in the url which make the select pick up different value', () => {
+      setUpComponent(
+        searchParams.replace(SeekDirection.FORWARD, SeekDirection.BACKWARD)
+      );
+      expect(screen.getByRole('listbox')).toHaveTextContent(
+        SeekDirectionOptionsObj[SeekDirection.BACKWARD].label
+      );
+    });
+  });
+});
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/MessagesTable.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/MessagesTable.spec.tsx
new file mode 100644
index 00000000000..3994e1e1e4a
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/MessagesTable.spec.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
+import { render } from 'lib/testHelpers';
+import MessagesTable from 'components/Topics/Topic/Details/Messages/MessagesTable';
+import { Router } from 'react-router';
+import { createMemoryHistory } from 'history';
+import { SeekDirection, SeekType } from 'generated-sources';
+import userEvent from '@testing-library/user-event';
+import TopicMessagesContext, {
+  ContextProps,
+} from 'components/contexts/TopicMessagesContext';
+
+describe('MessagesTable', () => {
+  const searchParams = new URLSearchParams(
+    `?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}&seekTo=0::9`
+  );
+  const contextValue: ContextProps = {
+    isLive: false,
+    seekDirection: SeekDirection.FORWARD,
+    searchParams,
+    changeSeekDirection: jest.fn(),
+  };
+
+  const setUpComponent = (
+    params: URLSearchParams = searchParams,
+    ctx: ContextProps = contextValue
+  ) => {
+    const history = createMemoryHistory();
+    history.push({
+      search: params.toString(),
+    });
+    return render(
+      <Router history={history}>
+        <TopicMessagesContext.Provider value={ctx}>
+          <MessagesTable />
+        </TopicMessagesContext.Provider>
+      </Router>
+    );
+  };
+
+  describe('Default props Setup for MessagesTable component', () => {
+    beforeEach(() => {
+      setUpComponent();
+    });
+
+    it('should check the render', () => {
+      expect(screen.getByRole('table')).toBeInTheDocument();
+    });
+
+    it('should check the if no elements is rendered in the table', () => {
+      expect(screen.getByText(/No messages found/i)).toBeInTheDocument();
+    });
+
+    it('should check if next button exist and check the click after next click', () => {
+      const nextBtnElement = screen.getByText(/next/i);
+      expect(nextBtnElement).toBeInTheDocument();
+      userEvent.click(nextBtnElement);
+      expect(screen.getByText(/No messages found/i)).toBeInTheDocument();
+    });
+  });
+
+  describe('Custom Setup with different props value', () => {
+    it('should check if next click is gone during isLive Param', () => {
+      setUpComponent(searchParams, { ...contextValue, isLive: true });
+      expect(screen.queryByText(/next/i)).not.toBeInTheDocument();
+    });
+
+    it('should check the display of the loader element', () => {
+      setUpComponent(searchParams, { ...contextValue, isLive: true });
+      expect(screen.getByRole('progressbar')).toBeInTheDocument();
+    });
+  });
+});
diff --git a/kafka-ui-react-app/src/components/contexts/TopicMessagesContext.ts b/kafka-ui-react-app/src/components/contexts/TopicMessagesContext.ts
new file mode 100644
index 00000000000..642052b27e8
--- /dev/null
+++ b/kafka-ui-react-app/src/components/contexts/TopicMessagesContext.ts
@@ -0,0 +1,15 @@
+import React from 'react';
+import { SeekDirection } from 'generated-sources';
+
+export interface ContextProps {
+  seekDirection: SeekDirection;
+  searchParams: URLSearchParams;
+  changeSeekDirection(val: string): void;
+  isLive: boolean;
+}
+
+const TopicMessagesContext = React.createContext<ContextProps>(
+  {} as ContextProps
+);
+
+export default TopicMessagesContext;
diff --git a/kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts b/kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts
index 8f85d46fb60..ecab598063d 100644
--- a/kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts
+++ b/kafka-ui-react-app/src/redux/reducers/topicMessages/reducer.ts
@@ -19,7 +19,7 @@ const reducer = (state = initialState, action: Action): TopicMessagesState => {
     case getType(actions.addTopicMessage): {
       return {
         ...state,
-        messages: [...state.messages, action.payload],
+        messages: [action.payload, ...state.messages],
       };
     }
     case getType(actions.resetTopicMessages):


Test patch:
diff --git a/kafka-ui-react-app/src/lib/testHelpers.tsx b/kafka-ui-react-app/src/lib/testHelpers.tsx
index f5f5640f629..2a0a75cc549 100644
--- a/kafka-ui-react-app/src/lib/testHelpers.tsx
+++ b/kafka-ui-react-app/src/lib/testHelpers.tsx
@@ -101,3 +101,23 @@ const customRender = (
 };
 
 export { customRender as render };
+
+export class EventSourceMock {
+  url: string;
+
+  close: () => void;
+
+  open: () => void;
+
+  error: () => void;
+
+  onmessage: () => void;
+
+  constructor(url: string) {
+    this.url = url;
+    this.open = jest.fn();
+    this.error = jest.fn();
+    this.onmessage = jest.fn();
+    this.close = jest.fn();
+  }
+}