Topic messages filtering paginating (#72)
* Add filtering and pagination for topic messages * Add delay to search query, momoize some functions * Add partition selection
This commit is contained in:
parent
2cb630dc18
commit
be2d38133d
7 changed files with 258 additions and 57 deletions
|
@ -57,7 +57,8 @@
|
|||
"node": {
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
||||
"paths": ["src"]
|
||||
}
|
||||
},
|
||||
"typescript": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
99
kafka-ui-react-app/package-lock.json
generated
99
kafka-ui-react-app/package-lock.json
generated
|
@ -1556,6 +1556,11 @@
|
|||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@rooks/use-outside-click": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@rooks/use-outside-click/-/use-outside-click-3.6.0.tgz",
|
||||
"integrity": "sha512-DDxdcD9bDDArV2tBmh5okaJNee/7EWaC5DsPrjTxIhhvXPpUatizcn2qYLcvX7y1vYpd64Wyqvkb87E6fsIfEQ=="
|
||||
},
|
||||
"@samverschueren/stream-to-observable": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
|
||||
|
@ -1889,6 +1894,11 @@
|
|||
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.149",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
|
||||
|
@ -5133,7 +5143,6 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
|
@ -6054,6 +6063,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-import-resolver-typescript": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz",
|
||||
"integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"is-glob": "^4.0.1",
|
||||
"resolve": "^1.12.0",
|
||||
"tiny-glob": "^0.2.6",
|
||||
"tsconfig-paths": "^3.9.0"
|
||||
}
|
||||
},
|
||||
"eslint-loader": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz",
|
||||
|
@ -7726,6 +7747,11 @@
|
|||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"globalyzer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz",
|
||||
"integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA=="
|
||||
},
|
||||
"globby": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz",
|
||||
|
@ -7755,6 +7781,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"globrex": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
|
||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
|
||||
},
|
||||
"globule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz",
|
||||
|
@ -7766,6 +7797,11 @@
|
|||
"minimatch": "~3.0.2"
|
||||
}
|
||||
},
|
||||
"goober": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/goober/-/goober-1.8.0.tgz",
|
||||
"integrity": "sha512-9ZFoOkBccexjqIgcwlhq7C/eCSkgTZX0BdNUkOnBFLedrJgo3R8lp9ckd/qqtngtF/JDyXSxJzwMU98kNjZ4Mw=="
|
||||
},
|
||||
"got": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
|
||||
|
@ -8826,8 +8862,7 @@
|
|||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
|
||||
},
|
||||
"is-finite": {
|
||||
"version": "1.0.2",
|
||||
|
@ -8854,7 +8889,6 @@
|
|||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
|
@ -11213,8 +11247,7 @@
|
|||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "3.1.3",
|
||||
|
@ -11365,8 +11398,7 @@
|
|||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"multicast-dns": {
|
||||
"version": "6.2.3",
|
||||
|
@ -12427,8 +12459,7 @@
|
|||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
|
@ -14313,6 +14344,15 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
||||
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
|
||||
},
|
||||
"react-multi-select-component": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/react-multi-select-component/-/react-multi-select-component-2.0.12.tgz",
|
||||
"integrity": "sha512-QcOc8zQgz9AQQkX51EuDokqPi8BIRGBQdvnn1im3d1gsSIIY2W09jkvd9+/ByVk6NiL4XjygJtwCGJSGQcr3+A==",
|
||||
"requires": {
|
||||
"@rooks/use-outside-click": "^3.6.0",
|
||||
"goober": "^1.8.0"
|
||||
}
|
||||
},
|
||||
"react-onclickoutside": {
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
|
||||
|
@ -15102,7 +15142,6 @@
|
|||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.2.tgz",
|
||||
"integrity": "sha512-cAVTI2VLHWYsGOirfeYVVQ7ZDejtQ9fp4YhYckWDEkFfqbVjaT11iM8k6xSAfGFMM+gDpZjMnFssPu8we+mqFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
|
@ -16875,8 +16914,7 @@
|
|||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
|
||||
"dev": true
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
|
||||
},
|
||||
"strip-comments": {
|
||||
"version": "1.0.2",
|
||||
|
@ -17351,6 +17389,15 @@
|
|||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-glob": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.6.tgz",
|
||||
"integrity": "sha512-A7ewMqPu1B5PWwC3m7KVgAu96Ch5LA0w4SnEN/LbDREj/gAD0nPWboRbn8YoP9ISZXqeNAlMvKSKoEuhcfK3Pw==",
|
||||
"requires": {
|
||||
"globalyzer": "^0.1.0",
|
||||
"globrex": "^0.1.1"
|
||||
}
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
||||
|
@ -17477,6 +17524,27 @@
|
|||
"integrity": "sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA==",
|
||||
"dev": true
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
|
||||
"integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==",
|
||||
"requires": {
|
||||
"@types/json5": "^0.0.29",
|
||||
"json5": "^1.0.1",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
|
@ -17806,6 +17874,11 @@
|
|||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"use-debounce": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-3.4.3.tgz",
|
||||
"integrity": "sha512-nxy+opOxDccWfhMl36J5BSCTpvcj89iaQk2OZWLAtBJQj7ISCtx1gh+rFbdjGfMl6vtCZf6gke/kYvrkVfHMoA=="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"bulma-switch": "^2.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.14.0",
|
||||
"eslint-import-resolver-typescript": "^2.0.0",
|
||||
"immer": "^6.0.5",
|
||||
"lodash": "^4.17.15",
|
||||
"pretty-ms": "^6.0.1",
|
||||
|
@ -15,12 +16,14 @@
|
|||
"react-datepicker": "^3.0.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hook-form": "^4.5.5",
|
||||
"react-multi-select-component": "^2.0.12",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"typesafe-actions": "^5.1.0"
|
||||
"typesafe-actions": "^5.1.0",
|
||||
"use-debounce": "^3.4.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,jsx,tsx}": [
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import {
|
||||
ClusterName,
|
||||
SeekType,
|
||||
SeekTypes,
|
||||
TopicMessage,
|
||||
TopicMessageQueryParams,
|
||||
TopicName,
|
||||
TopicPartition,
|
||||
} from 'redux/interfaces';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import { format } from 'date-fns';
|
||||
|
@ -15,7 +17,11 @@ import CustomParamButton, {
|
|||
CustomParamButtonType,
|
||||
} from 'components/Topics/shared/Form/CustomParams/CustomParamButton';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
clusterName: ClusterName;
|
||||
|
@ -27,6 +33,7 @@ interface Props {
|
|||
queryParams: Partial<TopicMessageQueryParams>
|
||||
) => void;
|
||||
messages: TopicMessage[];
|
||||
partitions: TopicPartition[];
|
||||
}
|
||||
|
||||
interface FilterProps {
|
||||
|
@ -47,6 +54,7 @@ const Messages: React.FC<Props> = ({
|
|||
clusterName,
|
||||
topicName,
|
||||
messages,
|
||||
partitions,
|
||||
fetchTopicMessages,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||
|
@ -54,23 +62,51 @@ const Messages: React.FC<Props> = ({
|
|||
null
|
||||
);
|
||||
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
||||
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
||||
SeekTypes.OFFSET
|
||||
);
|
||||
const [searchOffset, setSearchOffset] = React.useState<string>('0');
|
||||
const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
|
||||
[]
|
||||
);
|
||||
const [queryParams, setQueryParams] = React.useState<
|
||||
Partial<TopicMessageQueryParams>
|
||||
>({ limit: 100 });
|
||||
const [debouncedCallback] = useDebouncedCallback(
|
||||
(query: any) => setQueryParams({ ...queryParams, ...query }),
|
||||
1000
|
||||
);
|
||||
|
||||
const prevSearchTimestamp = usePrevious(searchTimestamp);
|
||||
|
||||
const getUniqueDataForEachPartition: FilterProps[] = React.useMemo(() => {
|
||||
const map = messages.map((message) => [
|
||||
message.partition,
|
||||
{
|
||||
partition: message.partition,
|
||||
offset: message.offset,
|
||||
},
|
||||
]);
|
||||
// @ts-ignore
|
||||
return [...new Map(map).values()];
|
||||
}, [messages]);
|
||||
const partitionUniqs: FilterProps[] = partitions.map((p) => ({
|
||||
offset: 0,
|
||||
partition: p.partition,
|
||||
}));
|
||||
const messageUniqs: FilterProps[] = _.map(
|
||||
_.groupBy(messages, 'partition'),
|
||||
(v) => _.maxBy(v, 'offset')
|
||||
).map((v) => ({
|
||||
offset: v ? v.offset : 0,
|
||||
partition: v ? v.partition : 0,
|
||||
}));
|
||||
|
||||
return _.map(
|
||||
_.groupBy(_.concat(partitionUniqs, messageUniqs), 'partition'),
|
||||
(v) => _.maxBy(v, 'offset') as FilterProps
|
||||
);
|
||||
}, [messages, partitions]);
|
||||
|
||||
const getSeekToValuesForPartitions = (partition: any) => {
|
||||
const foundedValues = filterProps.find(
|
||||
(prop) => prop.partition === partition.value
|
||||
);
|
||||
if (selectedSeekType === SeekTypes.OFFSET) {
|
||||
return foundedValues ? foundedValues.offset : 0;
|
||||
}
|
||||
return searchTimestamp ? searchTimestamp.getTime() : null;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchTopicMessages(clusterName, topicName, queryParams);
|
||||
|
@ -78,20 +114,13 @@ const Messages: React.FC<Props> = ({
|
|||
|
||||
React.useEffect(() => {
|
||||
setFilterProps(getUniqueDataForEachPartition);
|
||||
}, [messages]);
|
||||
}, [messages, partitions]);
|
||||
|
||||
const handleDelayedQuery = useCallback(
|
||||
debounce(
|
||||
(query: string) => setQueryParams({ ...queryParams, q: query }),
|
||||
1000
|
||||
),
|
||||
[]
|
||||
);
|
||||
const handleQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const query = event.target.value;
|
||||
|
||||
setSearchQuery(query);
|
||||
handleDelayedQuery(query);
|
||||
debouncedCallback({ q: query });
|
||||
};
|
||||
|
||||
const handleDateTimeChange = () => {
|
||||
|
@ -103,7 +132,7 @@ const Messages: React.FC<Props> = ({
|
|||
setQueryParams({
|
||||
...queryParams,
|
||||
seekType: SeekTypes.TIMESTAMP,
|
||||
seekTo: filterProps.map((p) => `${p.partition}::${timestamp}`),
|
||||
seekTo: selectedPartitions.map((p) => `${p.value}::${timestamp}`),
|
||||
});
|
||||
} else {
|
||||
setSearchTimestamp(null);
|
||||
|
@ -113,6 +142,33 @@ const Messages: React.FC<Props> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleSeekTypeChange = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
setSelectedSeekType(event.target.value as SeekType);
|
||||
};
|
||||
|
||||
const handleOffsetChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const offset = event.target.value || '0';
|
||||
setSearchOffset(offset);
|
||||
debouncedCallback({
|
||||
seekType: SeekTypes.OFFSET,
|
||||
seekTo: selectedPartitions.map((p) => `${p.value}::${offset}`),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePartitionsChange = (options: Option[]) => {
|
||||
setSelectedPartitions(options);
|
||||
|
||||
debouncedCallback({
|
||||
seekType: options.length > 0 ? selectedSeekType : undefined,
|
||||
seekTo:
|
||||
options.length > 0
|
||||
? options.map((p) => `${p.value}::${getSeekToValuesForPartitions(p)}`)
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const getTimestampDate = (timestamp: number) => {
|
||||
return format(new Date(timestamp * 1000), 'MM.dd.yyyy HH:mm:ss');
|
||||
};
|
||||
|
@ -150,9 +206,13 @@ const Messages: React.FC<Props> = ({
|
|||
const onNext = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const seekTo: string[] = filterProps.map(
|
||||
(p) => `${p.partition}::${p.offset}`
|
||||
);
|
||||
const seekTo: string[] = filterProps
|
||||
.filter(
|
||||
(value) =>
|
||||
selectedPartitions.findIndex((p) => p.value === value.partition) > -1
|
||||
)
|
||||
.map((p) => `${p.partition}::${p.offset}`);
|
||||
|
||||
setQueryParams({
|
||||
...queryParams,
|
||||
seekType: SeekTypes.OFFSET,
|
||||
|
@ -160,6 +220,15 @@ const Messages: React.FC<Props> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const filterOptions = (options: Option[], filter: any) => {
|
||||
if (!filter) {
|
||||
return options;
|
||||
}
|
||||
return options.filter(
|
||||
({ value }) => value.toString() && value.toString() === filter
|
||||
);
|
||||
};
|
||||
|
||||
const getTopicMessagesTable = () => {
|
||||
return messages.length > 0 ? (
|
||||
<div>
|
||||
|
@ -199,23 +268,70 @@ const Messages: React.FC<Props> = ({
|
|||
);
|
||||
};
|
||||
|
||||
return isFetched ? (
|
||||
if (!isFetched) {
|
||||
return <PageLoader isFullHeight={false} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="columns">
|
||||
<div className="column is-one-quarter">
|
||||
<div className="column is-one-fifth">
|
||||
<label className="label">Partitions</label>
|
||||
<MultiSelect
|
||||
options={partitions.map((p) => ({
|
||||
label: `Partition #${p.partition.toString()}`,
|
||||
value: p.partition,
|
||||
}))}
|
||||
filterOptions={filterOptions}
|
||||
value={selectedPartitions}
|
||||
onChange={handlePartitionsChange}
|
||||
labelledBy="Select partitions"
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-one-fifth">
|
||||
<label className="label">Seek Type</label>
|
||||
<div className="select is-block">
|
||||
<select
|
||||
id="selectSeekType"
|
||||
name="selectSeekType"
|
||||
onChange={handleSeekTypeChange}
|
||||
defaultValue={SeekTypes.OFFSET}
|
||||
value={selectedSeekType}
|
||||
>
|
||||
<option value={SeekTypes.OFFSET}>Offset</option>
|
||||
<option value={SeekTypes.TIMESTAMP}>Timestamp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-one-fifth">
|
||||
{selectedSeekType === SeekTypes.OFFSET ? (
|
||||
<>
|
||||
<label className="label">Offset</label>
|
||||
<input
|
||||
id="searchOffset"
|
||||
name="searchOffset"
|
||||
type="text"
|
||||
className="input"
|
||||
value={searchOffset}
|
||||
onChange={handleOffsetChange}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<label className="label">Timestamp</label>
|
||||
<DatePicker
|
||||
selected={searchTimestamp}
|
||||
onChange={(date) => setSearchTimestamp(date)}
|
||||
onCalendarClose={handleDateTimeChange}
|
||||
isClearable
|
||||
showTimeInput
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="MMMM d, yyyy h:mm aa"
|
||||
className="input"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="column is-two-quarters is-offset-one-quarter">
|
||||
<div className="column is-two-fifths">
|
||||
<label className="label">Search</label>
|
||||
<input
|
||||
id="searchText"
|
||||
|
@ -228,10 +344,8 @@ const Messages: React.FC<Props> = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>{getTopicMessagesTable()}</div>
|
||||
{getTopicMessagesTable()}
|
||||
</div>
|
||||
) : (
|
||||
<PageLoader isFullHeight={false} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { RouteComponentProps, withRouter } from 'react-router-dom';
|
|||
import { fetchTopicMessages } from 'redux/actions';
|
||||
import {
|
||||
getIsTopicMessagesFetched,
|
||||
getPartitionsByTopicName,
|
||||
getTopicMessages,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
|
||||
|
@ -33,6 +34,7 @@ const mapStateToProps = (
|
|||
topicName,
|
||||
isFetched: getIsTopicMessagesFetched(state),
|
||||
messages: getTopicMessages(state),
|
||||
partitions: getPartitionsByTopicName(state, topicName),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
|
|
@ -59,7 +59,9 @@ export const getTopicMessages = (
|
|||
const value = entry[1];
|
||||
if (value) {
|
||||
if (Array.isArray(value)) {
|
||||
searchParams += value.map((v) => `${key}=${v}&`);
|
||||
value.forEach((v) => {
|
||||
searchParams += `${key}=${v}&`;
|
||||
});
|
||||
} else {
|
||||
searchParams += `${key}=${value}&`;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,12 @@ export const getTopicByName = createSelector(
|
|||
(topics, topicName) => topics[topicName]
|
||||
);
|
||||
|
||||
export const getPartitionsByTopicName = createSelector(
|
||||
getTopicMap,
|
||||
getTopicName,
|
||||
(topics, topicName) => topics[topicName].partitions
|
||||
);
|
||||
|
||||
export const getFullTopic = createSelector(getTopicByName, (topic) =>
|
||||
topic && topic.config && !!topic.partitionCount ? topic : undefined
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue