MessagesTable.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import PageLoader from 'components/common/PageLoader/PageLoader';
  2. import CustomParamButton from 'components/Topics/shared/Form/CustomParams/CustomParamButton';
  3. import {
  4. Partition,
  5. SeekDirection,
  6. TopicMessage,
  7. TopicMessageConsuming,
  8. } from 'generated-sources';
  9. import { compact, concat, groupBy, map, maxBy, minBy } from 'lodash';
  10. import React from 'react';
  11. import { useSelector } from 'react-redux';
  12. import { useHistory, useLocation } from 'react-router';
  13. import { ClusterName, TopicName } from 'redux/interfaces';
  14. import {
  15. getTopicMessges,
  16. getIsTopicMessagesFetching,
  17. } from 'redux/reducers/topicMessages/selectors';
  18. import Message from './Message';
  19. export interface MessagesProps {
  20. clusterName: ClusterName;
  21. topicName: TopicName;
  22. messages: TopicMessage[];
  23. phaseMessage?: string;
  24. partitions: Partition[];
  25. meta: TopicMessageConsuming;
  26. addMessage(message: TopicMessage): void;
  27. resetMessages(): void;
  28. updatePhase(phase: string): void;
  29. updateMeta(meta: TopicMessageConsuming): void;
  30. }
  31. const MessagesTable: React.FC = () => {
  32. const location = useLocation();
  33. const history = useHistory();
  34. const searchParams = React.useMemo(
  35. () => new URLSearchParams(location.search),
  36. [location, history]
  37. );
  38. const messages = useSelector(getTopicMessges);
  39. const isFetching = useSelector(getIsTopicMessagesFetching);
  40. const handleNextClick = React.useCallback(() => {
  41. const seekTo = searchParams.get('seekTo');
  42. if (seekTo) {
  43. const selectedPartitions = seekTo.split(',').map((item) => {
  44. const [partition] = item.split('::');
  45. return { offset: 0, partition: parseInt(partition, 10) };
  46. });
  47. const messageUniqs = map(groupBy(messages, 'partition'), (v) =>
  48. searchParams.get('seekDirection') === SeekDirection.BACKWARD
  49. ? minBy(v, 'offset')
  50. : maxBy(v, 'offset')
  51. ).map((message) => ({
  52. offset: message?.offset || 0,
  53. partition: message?.partition || 0,
  54. }));
  55. const nextSeekTo = compact(
  56. map(
  57. groupBy(concat(selectedPartitions, messageUniqs), 'partition'),
  58. (v) => maxBy(v, 'offset')
  59. )
  60. )
  61. .map(({ offset, partition }) => `${partition}::${offset}`)
  62. .join(',');
  63. searchParams.set('seekTo', nextSeekTo);
  64. history.push({
  65. search: `?${searchParams.toString()}`,
  66. });
  67. }
  68. }, [searchParams, history, messages]);
  69. return (
  70. <>
  71. <table className="table is-fullwidth">
  72. <thead>
  73. <tr>
  74. <th style={{ width: 40 }}> </th>
  75. <th style={{ width: 70 }}>Offset</th>
  76. <th style={{ width: 90 }}>Partition</th>
  77. <th>Key</th>
  78. <th style={{ width: 170 }}>Timestamp</th>
  79. <th>Content</th>
  80. <th> </th>
  81. </tr>
  82. </thead>
  83. <tbody>
  84. {messages.map((message: TopicMessage) => (
  85. <Message
  86. key={[
  87. message.offset,
  88. message.timestamp,
  89. message.key,
  90. message.partition,
  91. ].join('-')}
  92. message={message}
  93. />
  94. ))}
  95. {isFetching && (
  96. <tr>
  97. <td colSpan={10}>
  98. <PageLoader />
  99. </td>
  100. </tr>
  101. )}
  102. {messages.length === 0 && !isFetching && (
  103. <tr>
  104. <td colSpan={10}>No messages found</td>
  105. </tr>
  106. )}
  107. </tbody>
  108. </table>
  109. <div className="columns">
  110. <div className="column is-full">
  111. <CustomParamButton
  112. className="is-link is-pulled-right"
  113. type="fa-chevron-right"
  114. onClick={handleNextClick}
  115. btnText="Next"
  116. />
  117. </div>
  118. </div>
  119. </>
  120. );
  121. };
  122. export default MessagesTable;