Browse Source

Messages component refactoring (#174)

* ISSUE-169 Sort topics (#172)

* Messages Table component created

* Messages component refactored

* MessageItem component fixed

* MessageItem component updated

* MessageItem component refactored

Co-authored-by: German Osin <german.osin@gmail.com>
Co-authored-by: Oleg Shuralev <workshur@gmail.com>
Guzel738 4 years ago
parent
commit
10a27ba60e

+ 52 - 0
kafka-ui-react-app/src/components/Topics/Details/Messages/MessageItem.tsx

@@ -0,0 +1,52 @@
+import React from 'react';
+import { format } from 'date-fns';
+import JSONTree from 'react-json-tree';
+import { TopicMessage } from 'generated-sources';
+
+interface MessageItemProp {
+  partition: TopicMessage['partition'];
+  offset: TopicMessage['offset'];
+  timestamp: TopicMessage['timestamp'];
+  content: TopicMessage['content'];
+}
+
+const MessageItem: React.FC<MessageItemProp> = ({
+  partition,
+  offset,
+  timestamp,
+  content,
+}) => (
+  <tr key="{timestamp}">
+    <td style={{ width: 200 }}>
+      {timestamp ? format(timestamp, 'yyyy-MM-dd HH:mm:ss') : null}
+    </td>
+    <td style={{ width: 150 }}>{offset}</td>
+    <td style={{ width: 100 }}>{partition}</td>
+    <td key="{content}" style={{ wordBreak: 'break-word' }}>
+      {content && (
+        <JSONTree
+          data={content}
+          hideRoot
+          invertTheme={false}
+          theme={{
+            tree: ({ style }) => ({
+              style: {
+                ...style,
+                backgroundColor: undefined,
+                marginLeft: 0,
+                marginTop: 0,
+              },
+            }),
+            value: ({ style }) => ({
+              style: { ...style, marginLeft: 0 },
+            }),
+            base0D: '#3273dc',
+            base0B: '#363636',
+          }}
+        />
+      )}
+    </td>
+  </tr>
+);
+
+export default MessageItem;

+ 2 - 88
kafka-ui-react-app/src/components/Topics/Details/Messages/Messages.tsx

@@ -6,19 +6,15 @@ import {
 } from 'redux/interfaces';
 import { TopicMessage, Partition, SeekType } from 'generated-sources';
 import PageLoader from 'components/common/PageLoader/PageLoader';
-import { format } from 'date-fns';
 import DatePicker from 'react-datepicker';
-import JSONTree from 'react-json-tree';
 import 'react-datepicker/dist/react-datepicker.css';
-import CustomParamButton, {
-  CustomParamButtonType,
-} from 'components/Topics/shared/Form/CustomParams/CustomParamButton';
 
 import MultiSelect from 'react-multi-select-component';
 
 import * as _ from 'lodash';
 import { useDebouncedCallback } from 'use-debounce';
 import { Option } from 'react-multi-select-component/dist/lib/interfaces';
+import MessagesTable from './MessagesTable';
 
 export interface Props {
   clusterName: ClusterName;
@@ -174,42 +170,6 @@ const Messages: React.FC<Props> = ({
     fetchTopicMessages(clusterName, topicName, queryParams);
   }, [clusterName, topicName, queryParams]);
 
-  const getFormattedDate = (date: Date) => {
-    if (!date) return null;
-    return format(date, 'yyyy-MM-dd HH:mm:ss');
-  };
-
-  const getMessageContentBody = (content: Record<string, unknown>) => {
-    try {
-      const contentObj =
-        typeof content !== 'object' ? JSON.parse(content) : content;
-      return (
-        <JSONTree
-          data={contentObj}
-          hideRoot
-          invertTheme={false}
-          theme={{
-            tree: ({ style }) => ({
-              style: {
-                ...style,
-                backgroundColor: undefined,
-                marginLeft: 0,
-                marginTop: 0,
-              },
-            }),
-            value: ({ style }) => ({
-              style: { ...style, marginLeft: 0 },
-            }),
-            base0D: '#3273dc',
-            base0B: '#363636',
-          }}
-        />
-      );
-    } catch (e) {
-      return content;
-    }
-  };
-
   const onNext = (event: React.MouseEvent<HTMLButtonElement>) => {
     event.preventDefault();
 
@@ -236,52 +196,6 @@ const Messages: React.FC<Props> = ({
     );
   };
 
-  const getTopicMessagesTable = () => {
-    return messages.length > 0 ? (
-      <div>
-        <table className="table is-striped is-fullwidth">
-          <thead>
-            <tr>
-              <th>Timestamp</th>
-              <th>Offset</th>
-              <th>Partition</th>
-              <th>Content</th>
-            </tr>
-          </thead>
-          <tbody>
-            {messages.map((message) => (
-              <tr key={`${message.timestamp}${Math.random()}`}>
-                <td style={{ width: 200 }}>
-                  {getFormattedDate(message.timestamp)}
-                </td>
-                <td style={{ width: 150 }}>{message.offset}</td>
-                <td style={{ width: 100 }}>{message.partition}</td>
-                <td key={Math.random()} style={{ wordBreak: 'break-word' }}>
-                  {message.content &&
-                    getMessageContentBody(
-                      message.content as Record<string, unknown>
-                    )}
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-        <div className="columns">
-          <div className="column is-full">
-            <CustomParamButton
-              className="is-link is-pulled-right"
-              type={CustomParamButtonType.chevronRight}
-              onClick={onNext}
-              btnText="Next"
-            />
-          </div>
-        </div>
-      </div>
-    ) : (
-      <div>No messages at selected topic</div>
-    );
-  };
-
   if (!isFetched) {
     return <PageLoader isFullHeight={false} />;
   }
@@ -366,7 +280,7 @@ const Messages: React.FC<Props> = ({
           />
         </div>
       </div>
-      {getTopicMessagesTable()}
+      <MessagesTable messages={messages} onNext={onNext} />
     </div>
   );
 };

+ 57 - 0
kafka-ui-react-app/src/components/Topics/Details/Messages/MessagesTable.tsx

@@ -0,0 +1,57 @@
+import React from 'react';
+import { TopicMessage } from 'generated-sources';
+import CustomParamButton, {
+  CustomParamButtonType,
+} from '../../shared/Form/CustomParams/CustomParamButton';
+import MessageItem from './MessageItem';
+
+interface MessagesTableProp {
+  messages: TopicMessage[];
+  onNext(event: React.MouseEvent<HTMLButtonElement>): void;
+}
+
+const MessagesTable: React.FC<MessagesTableProp> = ({ messages, onNext }) => {
+  if (!messages.length) {
+    return <div>No messages at selected topic</div>;
+  }
+
+  return (
+    <div>
+      <table className="table is-striped is-fullwidth">
+        <thead>
+          <tr>
+            <th>Timestamp</th>
+            <th>Offset</th>
+            <th>Partition</th>
+            <th>Content</th>
+          </tr>
+        </thead>
+        <tbody>
+          {messages.map(
+            ({ partition, offset, timestamp, content }: TopicMessage) => (
+              <MessageItem
+                key={timestamp.toString()}
+                partition={partition}
+                offset={offset}
+                timestamp={timestamp}
+                content={content as Record<string, unknown>}
+              />
+            )
+          )}
+        </tbody>
+      </table>
+      <div className="columns">
+        <div className="column is-full">
+          <CustomParamButton
+            className="is-link is-pulled-right"
+            type={CustomParamButtonType.chevronRight}
+            onClick={onNext}
+            btnText="Next"
+          />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default MessagesTable;

+ 27 - 5
kafka-ui-react-app/src/redux/reducers/topics/reducer.ts

@@ -1,5 +1,5 @@
 import { v4 } from 'uuid';
-import { Topic } from 'generated-sources';
+import { Topic, TopicMessage } from 'generated-sources';
 import { Action, TopicsState } from 'redux/interfaces';
 import ActionType from 'redux/actionType';
 
@@ -41,6 +41,31 @@ const addToTopicList = (state: TopicsState, payload: Topic): TopicsState => {
   return newState;
 };
 
+const transformTopicMessages = (
+  state: TopicsState,
+  messages: TopicMessage[]
+): TopicsState => ({
+  ...state,
+  messages: messages.map((mes) => {
+    const { content } = mes;
+    let parsedContent = content;
+
+    if (content) {
+      try {
+        parsedContent =
+          typeof content !== 'object' ? JSON.parse(content) : content;
+      } catch (_) {
+        // do nothing
+      }
+    }
+
+    return {
+      ...mes,
+      content: parsedContent,
+    };
+  }),
+});
+
 const reducer = (state = initialState, action: Action): TopicsState => {
   switch (action.type) {
     case ActionType.GET_TOPICS__SUCCESS:
@@ -57,10 +82,7 @@ const reducer = (state = initialState, action: Action): TopicsState => {
         },
       };
     case ActionType.GET_TOPIC_MESSAGES__SUCCESS:
-      return {
-        ...state,
-        messages: action.payload,
-      };
+      return transformTopicMessages(state, action.payload);
     case ActionType.GET_TOPIC_CONFIG__SUCCESS:
       return {
         ...state,