|
@@ -1,6 +1,6 @@
|
|
|
import { Table } from 'components/common/table/Table/Table.styled';
|
|
|
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
|
|
-import { ConsumerGroupTopicPartition } from 'generated-sources';
|
|
|
+import { ConsumerGroupTopicPartition, SortOrder } from 'generated-sources';
|
|
|
import React from 'react';
|
|
|
|
|
|
import { ContentBox, TopicContentWrapper } from './TopicContent.styled';
|
|
@@ -9,7 +9,125 @@ interface Props {
|
|
|
consumers: ConsumerGroupTopicPartition[];
|
|
|
}
|
|
|
|
|
|
+type OrderByKey = keyof ConsumerGroupTopicPartition;
|
|
|
+interface Headers {
|
|
|
+ title: string;
|
|
|
+ orderBy: OrderByKey | undefined;
|
|
|
+}
|
|
|
+
|
|
|
+const TABLE_HEADERS_MAP: Headers[] = [
|
|
|
+ { title: 'Partition', orderBy: 'partition' },
|
|
|
+ { title: 'Consumer ID', orderBy: 'consumerId' },
|
|
|
+ { title: 'Host', orderBy: 'host' },
|
|
|
+ { title: 'Messages Behind', orderBy: 'messagesBehind' },
|
|
|
+ { title: 'Current Offset', orderBy: 'currentOffset' },
|
|
|
+ { title: 'End offset', orderBy: 'endOffset' },
|
|
|
+];
|
|
|
+
|
|
|
+const ipV4ToNum = (ip?: string) => {
|
|
|
+ if (typeof ip === 'string' && ip.length !== 0) {
|
|
|
+ const withoutSlash = ip.indexOf('/') !== -1 ? ip.slice(1) : ip;
|
|
|
+ return Number(
|
|
|
+ withoutSlash
|
|
|
+ .split('.')
|
|
|
+ .map((octet) => `000${octet}`.slice(-3))
|
|
|
+ .join('')
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+};
|
|
|
+
|
|
|
+type ComparatorFunction<T> = (
|
|
|
+ valueA: T,
|
|
|
+ valueB: T,
|
|
|
+ order: SortOrder,
|
|
|
+ property?: keyof T
|
|
|
+) => number;
|
|
|
+
|
|
|
+const numberComparator: ComparatorFunction<ConsumerGroupTopicPartition> = (
|
|
|
+ valueA,
|
|
|
+ valueB,
|
|
|
+ order,
|
|
|
+ property
|
|
|
+) => {
|
|
|
+ if (property !== undefined) {
|
|
|
+ return order === SortOrder.ASC
|
|
|
+ ? Number(valueA[property]) - Number(valueB[property])
|
|
|
+ : Number(valueB[property]) - Number(valueA[property]);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+};
|
|
|
+
|
|
|
+const ipComparator: ComparatorFunction<ConsumerGroupTopicPartition> = (
|
|
|
+ valueA,
|
|
|
+ valueB,
|
|
|
+ order
|
|
|
+) =>
|
|
|
+ order === SortOrder.ASC
|
|
|
+ ? ipV4ToNum(valueA.host) - ipV4ToNum(valueB.host)
|
|
|
+ : ipV4ToNum(valueB.host) - ipV4ToNum(valueA.host);
|
|
|
+
|
|
|
+const consumerIdComparator: ComparatorFunction<ConsumerGroupTopicPartition> = (
|
|
|
+ valueA,
|
|
|
+ valueB,
|
|
|
+ order
|
|
|
+) => {
|
|
|
+ if (valueA.consumerId && valueB.consumerId) {
|
|
|
+ if (order === SortOrder.ASC) {
|
|
|
+ if (valueA.consumerId?.toLowerCase() > valueB.consumerId?.toLowerCase()) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (order === SortOrder.DESC) {
|
|
|
+ if (valueB.consumerId?.toLowerCase() > valueA.consumerId?.toLowerCase()) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+};
|
|
|
+
|
|
|
const TopicContents: React.FC<Props> = ({ consumers }) => {
|
|
|
+ const [orderBy, setOrderBy] = React.useState<OrderByKey>('partition');
|
|
|
+ const [sortOrder, setSortOrder] = React.useState<SortOrder>(SortOrder.DESC);
|
|
|
+
|
|
|
+ const handleOrder = React.useCallback((columnName: string | null) => {
|
|
|
+ if (typeof columnName === 'string') {
|
|
|
+ setOrderBy(columnName as OrderByKey);
|
|
|
+ setSortOrder((prevOrder) =>
|
|
|
+ prevOrder === SortOrder.DESC ? SortOrder.ASC : SortOrder.DESC
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const sortedConsumers = React.useMemo(() => {
|
|
|
+ if (orderBy && sortOrder) {
|
|
|
+ const isNumberProperty =
|
|
|
+ orderBy === 'partition' ||
|
|
|
+ orderBy === 'currentOffset' ||
|
|
|
+ orderBy === 'endOffset' ||
|
|
|
+ orderBy === 'messagesBehind';
|
|
|
+
|
|
|
+ let comparator: ComparatorFunction<ConsumerGroupTopicPartition>;
|
|
|
+ if (isNumberProperty) {
|
|
|
+ comparator = numberComparator;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (orderBy === 'host') {
|
|
|
+ comparator = ipComparator;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (orderBy === 'consumerId') {
|
|
|
+ comparator = consumerIdComparator;
|
|
|
+ }
|
|
|
+
|
|
|
+ return consumers.sort((a, b) => comparator(a, b, sortOrder, orderBy));
|
|
|
+ }
|
|
|
+ return consumers;
|
|
|
+ }, [orderBy, sortOrder, consumers]);
|
|
|
+
|
|
|
return (
|
|
|
<TopicContentWrapper>
|
|
|
<td colSpan={3}>
|
|
@@ -17,16 +135,20 @@ const TopicContents: React.FC<Props> = ({ consumers }) => {
|
|
|
<Table isFullwidth>
|
|
|
<thead>
|
|
|
<tr>
|
|
|
- <TableHeaderCell title="Partition" />
|
|
|
- <TableHeaderCell title="Consumer ID" />
|
|
|
- <TableHeaderCell title="Host" />
|
|
|
- <TableHeaderCell title="Messages behind" />
|
|
|
- <TableHeaderCell title="Current offset" />
|
|
|
- <TableHeaderCell title="End offset" />
|
|
|
+ {TABLE_HEADERS_MAP.map((header) => (
|
|
|
+ <TableHeaderCell
|
|
|
+ key={header.orderBy}
|
|
|
+ title={header.title}
|
|
|
+ orderBy={orderBy}
|
|
|
+ sortOrder={sortOrder}
|
|
|
+ orderValue={header.orderBy}
|
|
|
+ handleOrderBy={handleOrder}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
- {consumers.map((consumer) => (
|
|
|
+ {sortedConsumers.map((consumer) => (
|
|
|
<tr key={consumer.partition}>
|
|
|
<td>{consumer.partition}</td>
|
|
|
<td>{consumer.consumerId}</td>
|