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 0e4884ce1c..7b6f133f2a 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 84aaa0c762..379a48f68e 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 = ({
clusterName,
@@ -88,16 +85,14 @@ const Filters: React.FC = ({
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(null);
- const searchParams = React.useMemo(
- () => new URLSearchParams(location.search),
- [location]
- );
-
const [selectedPartitions, setSelectedPartitions] = React.useState
-
+
Loading messages.
{
- 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 09418a5346..4e45378202 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) =>
+const defaultContextValue: ContextProps = {
+ isLive: false,
+ seekDirection: SeekDirection.FORWARD,
+ searchParams: new URLSearchParams(''),
+ changeSeekDirection: jest.fn(),
+};
+
+const setupWrapper = (
+ props: Partial = {},
+ ctx: ContextProps = defaultContextValue
+) => {
render(
-
+
+
+
);
+};
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 e685720f5f..dde0eaa7ff 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 = () => (
-
-
-
-
-);
+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(
+ (searchParams.get('seekDirection') as SeekDirection) ||
+ defaultSeekValue.value
+ );
+
+ const [isLive, setIsLive] = useState(
+ 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 (
+
+
+
+
+ );
+};
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 f1fc9c14bf..c317440355 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 && (
@@ -108,9 +105,13 @@ const MessagesTable: React.FC = () => {
)}
-
- Next
-
+ {!isLive && (
+
+
+ Next
+
+
+ )}
>
);
};
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 0000000000..05d9eb0a46
--- /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(
+
+
+
+ );
+ };
+
+ 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 0000000000..3994e1e1e4
--- /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(
+
+
+
+
+
+ );
+ };
+
+ 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 0000000000..642052b27e
--- /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(
+ {} as ContextProps
+);
+
+export default TopicMessagesContext;
diff --git a/kafka-ui-react-app/src/lib/testHelpers.tsx b/kafka-ui-react-app/src/lib/testHelpers.tsx
index f5f5640f62..2a0a75cc54 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();
+ }
+}
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 8f85d46fb6..ecab598063 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):
|