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": {
|
"node": {
|
||||||
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
||||||
"paths": ["src"]
|
"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==",
|
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
||||||
"dev": true
|
"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": {
|
"@samverschueren/stream-to-observable": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
|
"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==",
|
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
|
||||||
"dev": true
|
"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": {
|
"@types/lodash": {
|
||||||
"version": "4.14.149",
|
"version": "4.14.149",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
|
||||||
|
@ -5133,7 +5143,6 @@
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "^2.1.1"
|
"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": {
|
"eslint-loader": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz",
|
"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==",
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"globalyzer": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA=="
|
||||||
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz",
|
"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": {
|
"globule": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz",
|
||||||
|
@ -7766,6 +7797,11 @@
|
||||||
"minimatch": "~3.0.2"
|
"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": {
|
"got": {
|
||||||
"version": "9.6.0",
|
"version": "9.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
|
||||||
|
@ -8826,8 +8862,7 @@
|
||||||
"is-extglob": {
|
"is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-finite": {
|
"is-finite": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -8854,7 +8889,6 @@
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
}
|
}
|
||||||
|
@ -11213,8 +11247,7 @@
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
|
@ -11365,8 +11398,7 @@
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"multicast-dns": {
|
"multicast-dns": {
|
||||||
"version": "6.2.3",
|
"version": "6.2.3",
|
||||||
|
@ -12427,8 +12459,7 @@
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
|
@ -14313,6 +14344,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
||||||
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
|
"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": {
|
"react-onclickoutside": {
|
||||||
"version": "6.9.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
|
||||||
|
@ -15102,7 +15142,6 @@
|
||||||
"version": "1.12.2",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.2.tgz",
|
||||||
"integrity": "sha512-cAVTI2VLHWYsGOirfeYVVQ7ZDejtQ9fp4YhYckWDEkFfqbVjaT11iM8k6xSAfGFMM+gDpZjMnFssPu8we+mqFw==",
|
"integrity": "sha512-cAVTI2VLHWYsGOirfeYVVQ7ZDejtQ9fp4YhYckWDEkFfqbVjaT11iM8k6xSAfGFMM+gDpZjMnFssPu8we+mqFw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"path-parse": "^1.0.6"
|
"path-parse": "^1.0.6"
|
||||||
}
|
}
|
||||||
|
@ -16875,8 +16914,7 @@
|
||||||
"strip-bom": {
|
"strip-bom": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
|
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"strip-comments": {
|
"strip-comments": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -17351,6 +17389,15 @@
|
||||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
||||||
"dev": true
|
"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": {
|
"tiny-invariant": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
||||||
|
@ -17477,6 +17524,27 @@
|
||||||
"integrity": "sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA==",
|
"integrity": "sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA==",
|
||||||
"dev": true
|
"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": {
|
"tslib": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
|
@ -17806,6 +17874,11 @@
|
||||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||||
"dev": true
|
"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": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"bulma-switch": "^2.0.0",
|
"bulma-switch": "^2.0.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^2.14.0",
|
"date-fns": "^2.14.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.0.0",
|
||||||
"immer": "^6.0.5",
|
"immer": "^6.0.5",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"pretty-ms": "^6.0.1",
|
"pretty-ms": "^6.0.1",
|
||||||
|
@ -15,12 +16,14 @@
|
||||||
"react-datepicker": "^3.0.0",
|
"react-datepicker": "^3.0.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-hook-form": "^4.5.5",
|
"react-hook-form": "^4.5.5",
|
||||||
|
"react-multi-select-component": "^2.0.12",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^7.1.3",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"typesafe-actions": "^5.1.0"
|
"typesafe-actions": "^5.1.0",
|
||||||
|
"use-debounce": "^3.4.3"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,ts,jsx,tsx}": [
|
"*.{js,ts,jsx,tsx}": [
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
ClusterName,
|
ClusterName,
|
||||||
|
SeekType,
|
||||||
SeekTypes,
|
SeekTypes,
|
||||||
TopicMessage,
|
TopicMessage,
|
||||||
TopicMessageQueryParams,
|
TopicMessageQueryParams,
|
||||||
TopicName,
|
TopicName,
|
||||||
|
TopicPartition,
|
||||||
} from 'redux/interfaces';
|
} from 'redux/interfaces';
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
@ -15,7 +17,11 @@ import CustomParamButton, {
|
||||||
CustomParamButtonType,
|
CustomParamButtonType,
|
||||||
} from 'components/Topics/shared/Form/CustomParams/CustomParamButton';
|
} 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 {
|
interface Props {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
|
@ -27,6 +33,7 @@ interface Props {
|
||||||
queryParams: Partial<TopicMessageQueryParams>
|
queryParams: Partial<TopicMessageQueryParams>
|
||||||
) => void;
|
) => void;
|
||||||
messages: TopicMessage[];
|
messages: TopicMessage[];
|
||||||
|
partitions: TopicPartition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterProps {
|
interface FilterProps {
|
||||||
|
@ -47,6 +54,7 @@ const Messages: React.FC<Props> = ({
|
||||||
clusterName,
|
clusterName,
|
||||||
topicName,
|
topicName,
|
||||||
messages,
|
messages,
|
||||||
|
partitions,
|
||||||
fetchTopicMessages,
|
fetchTopicMessages,
|
||||||
}) => {
|
}) => {
|
||||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||||
|
@ -54,23 +62,51 @@ const Messages: React.FC<Props> = ({
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
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<
|
const [queryParams, setQueryParams] = React.useState<
|
||||||
Partial<TopicMessageQueryParams>
|
Partial<TopicMessageQueryParams>
|
||||||
>({ limit: 100 });
|
>({ limit: 100 });
|
||||||
|
const [debouncedCallback] = useDebouncedCallback(
|
||||||
|
(query: any) => setQueryParams({ ...queryParams, ...query }),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
const prevSearchTimestamp = usePrevious(searchTimestamp);
|
const prevSearchTimestamp = usePrevious(searchTimestamp);
|
||||||
|
|
||||||
const getUniqueDataForEachPartition: FilterProps[] = React.useMemo(() => {
|
const getUniqueDataForEachPartition: FilterProps[] = React.useMemo(() => {
|
||||||
const map = messages.map((message) => [
|
const partitionUniqs: FilterProps[] = partitions.map((p) => ({
|
||||||
message.partition,
|
offset: 0,
|
||||||
{
|
partition: p.partition,
|
||||||
partition: message.partition,
|
}));
|
||||||
offset: message.offset,
|
const messageUniqs: FilterProps[] = _.map(
|
||||||
},
|
_.groupBy(messages, 'partition'),
|
||||||
]);
|
(v) => _.maxBy(v, 'offset')
|
||||||
// @ts-ignore
|
).map((v) => ({
|
||||||
return [...new Map(map).values()];
|
offset: v ? v.offset : 0,
|
||||||
}, [messages]);
|
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(() => {
|
React.useEffect(() => {
|
||||||
fetchTopicMessages(clusterName, topicName, queryParams);
|
fetchTopicMessages(clusterName, topicName, queryParams);
|
||||||
|
@ -78,20 +114,13 @@ const Messages: React.FC<Props> = ({
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setFilterProps(getUniqueDataForEachPartition);
|
setFilterProps(getUniqueDataForEachPartition);
|
||||||
}, [messages]);
|
}, [messages, partitions]);
|
||||||
|
|
||||||
const handleDelayedQuery = useCallback(
|
|
||||||
debounce(
|
|
||||||
(query: string) => setQueryParams({ ...queryParams, q: query }),
|
|
||||||
1000
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const handleQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const query = event.target.value;
|
const query = event.target.value;
|
||||||
|
|
||||||
setSearchQuery(query);
|
setSearchQuery(query);
|
||||||
handleDelayedQuery(query);
|
debouncedCallback({ q: query });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateTimeChange = () => {
|
const handleDateTimeChange = () => {
|
||||||
|
@ -103,7 +132,7 @@ const Messages: React.FC<Props> = ({
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
seekType: SeekTypes.TIMESTAMP,
|
seekType: SeekTypes.TIMESTAMP,
|
||||||
seekTo: filterProps.map((p) => `${p.partition}::${timestamp}`),
|
seekTo: selectedPartitions.map((p) => `${p.value}::${timestamp}`),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setSearchTimestamp(null);
|
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) => {
|
const getTimestampDate = (timestamp: number) => {
|
||||||
return format(new Date(timestamp * 1000), 'MM.dd.yyyy HH:mm:ss');
|
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>) => {
|
const onNext = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const seekTo: string[] = filterProps.map(
|
const seekTo: string[] = filterProps
|
||||||
(p) => `${p.partition}::${p.offset}`
|
.filter(
|
||||||
);
|
(value) =>
|
||||||
|
selectedPartitions.findIndex((p) => p.value === value.partition) > -1
|
||||||
|
)
|
||||||
|
.map((p) => `${p.partition}::${p.offset}`);
|
||||||
|
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
seekType: SeekTypes.OFFSET,
|
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 = () => {
|
const getTopicMessagesTable = () => {
|
||||||
return messages.length > 0 ? (
|
return messages.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
|
@ -199,23 +268,70 @@ const Messages: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return isFetched ? (
|
if (!isFetched) {
|
||||||
|
return <PageLoader isFullHeight={false} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="columns">
|
<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>
|
<label className="label">Timestamp</label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={searchTimestamp}
|
selected={searchTimestamp}
|
||||||
onChange={(date) => setSearchTimestamp(date)}
|
onChange={(date) => setSearchTimestamp(date)}
|
||||||
onCalendarClose={handleDateTimeChange}
|
onCalendarClose={handleDateTimeChange}
|
||||||
isClearable
|
|
||||||
showTimeInput
|
showTimeInput
|
||||||
timeInputLabel="Time:"
|
timeInputLabel="Time:"
|
||||||
dateFormat="MMMM d, yyyy h:mm aa"
|
dateFormat="MMMM d, yyyy h:mm aa"
|
||||||
className="input"
|
className="input"
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-two-quarters is-offset-one-quarter">
|
<div className="column is-two-fifths">
|
||||||
<label className="label">Search</label>
|
<label className="label">Search</label>
|
||||||
<input
|
<input
|
||||||
id="searchText"
|
id="searchText"
|
||||||
|
@ -228,10 +344,8 @@ const Messages: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{getTopicMessagesTable()}</div>
|
{getTopicMessagesTable()}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<PageLoader isFullHeight={false} />
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||||
import { fetchTopicMessages } from 'redux/actions';
|
import { fetchTopicMessages } from 'redux/actions';
|
||||||
import {
|
import {
|
||||||
getIsTopicMessagesFetched,
|
getIsTopicMessagesFetched,
|
||||||
|
getPartitionsByTopicName,
|
||||||
getTopicMessages,
|
getTopicMessages,
|
||||||
} from 'redux/reducers/topics/selectors';
|
} from 'redux/reducers/topics/selectors';
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ const mapStateToProps = (
|
||||||
topicName,
|
topicName,
|
||||||
isFetched: getIsTopicMessagesFetched(state),
|
isFetched: getIsTopicMessagesFetched(state),
|
||||||
messages: getTopicMessages(state),
|
messages: getTopicMessages(state),
|
||||||
|
partitions: getPartitionsByTopicName(state, topicName),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
|
|
@ -59,7 +59,9 @@ export const getTopicMessages = (
|
||||||
const value = entry[1];
|
const value = entry[1];
|
||||||
if (value) {
|
if (value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
searchParams += value.map((v) => `${key}=${v}&`);
|
value.forEach((v) => {
|
||||||
|
searchParams += `${key}=${v}&`;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
searchParams += `${key}=${value}&`;
|
searchParams += `${key}=${value}&`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,12 @@ export const getTopicByName = createSelector(
|
||||||
(topics, topicName) => topics[topicName]
|
(topics, topicName) => topics[topicName]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getPartitionsByTopicName = createSelector(
|
||||||
|
getTopicMap,
|
||||||
|
getTopicName,
|
||||||
|
(topics, topicName) => topics[topicName].partitions
|
||||||
|
);
|
||||||
|
|
||||||
export const getFullTopic = createSelector(getTopicByName, (topic) =>
|
export const getFullTopic = createSelector(getTopicByName, (topic) =>
|
||||||
topic && topic.config && !!topic.partitionCount ? topic : undefined
|
topic && topic.config && !!topic.partitionCount ? topic : undefined
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue