Add filtering and pagination for topic messages (#66)

* Add filtering and pagination for topic messages

* Add delay to search query, momoize some functions
This commit is contained in:
maxim_tereshin 2020-07-02 14:29:12 +05:00 committed by GitHub
parent 84807405d6
commit f71c601d0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 349 additions and 133 deletions

View file

@ -24,6 +24,7 @@
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/ban-ts-ignore": "off",
"import/extensions": [
"error",
"ignorePackages",

View file

@ -1916,8 +1916,7 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/q": {
"version": "1.5.4",
@ -1929,12 +1928,21 @@
"version": "16.9.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.17.tgz",
"integrity": "sha512-UP27In4fp4sWF5JgyV6pwVPAQM83Fj76JOcg02X5BZcpSu5Wx+fP9RMqc2v0ssBoQIFvD5JdKY41gjJJKmw6Bg==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-datepicker": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.0.2.tgz",
"integrity": "sha512-xW04NZRF+9ZnzOD3XrlIzBEKgUsN6LVgZJJsXH8NIUlVjyPh+sdtLPfVoDp+GQzGq1M0TuMLNZsv0sJ3N9XwDA==",
"requires": {
"@types/react": "*",
"date-fns": "^2.0.1",
"popper.js": "^1.14.1"
}
},
"@types/react-dom": {
"version": "16.9.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
@ -3369,7 +3377,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
@ -4717,6 +4726,15 @@
"sha.js": "^2.4.8"
}
},
"create-react-context": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
"integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
"requires": {
"gud": "^1.0.0",
"warning": "^4.0.3"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@ -5040,8 +5058,7 @@
"csstype": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz",
"integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==",
"dev": true
"integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA=="
},
"currently-unhandled": {
"version": "0.4.1",
@ -5152,7 +5169,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"dev": true,
"requires": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
@ -5203,7 +5219,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"requires": {
"object-keys": "^1.0.12"
}
@ -5787,7 +5802,6 @@
"version": "1.17.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0-next.1.tgz",
"integrity": "sha512-7MmGr03N7Rnuid6+wyhD9sHNE2n4tFSwExnU2lQl3lIo2ShXWGePY80zYaoMOmILWv57H0amMjZGHNzzGG70Rw==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
@ -5806,7 +5820,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
@ -6917,7 +6930,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
@ -7505,8 +7519,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"functional-red-black-tree": {
"version": "1.0.1",
@ -7839,7 +7852,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@ -7870,8 +7882,7 @@
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
"dev": true
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
},
"has-unicode": {
"version": "2.0.1",
@ -8709,8 +8720,7 @@
"is-arguments": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
"dev": true
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA=="
},
"is-arrayish": {
"version": "0.2.1",
@ -8737,8 +8747,7 @@
"is-callable": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
},
"is-ci": {
"version": "2.0.0",
@ -8775,8 +8784,7 @@
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
},
"is-descriptor": {
"version": "0.1.6",
@ -8942,7 +8950,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
"integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
@ -8990,7 +8997,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"dev": true,
"requires": {
"has-symbols": "^1.0.1"
}
@ -11864,14 +11870,12 @@
"object-inspect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
"dev": true
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
},
"object-is": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz",
"integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
@ -11881,7 +11885,6 @@
"version": "1.17.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
"integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
@ -11899,14 +11902,12 @@
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
},
"string.prototype.trimleft": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -11917,7 +11918,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -11929,8 +11929,7 @@
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object-path": {
"version": "0.11.4",
@ -11951,7 +11950,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"function-bind": "^1.1.1",
@ -12595,6 +12593,11 @@
"ts-pnp": "^1.1.2"
}
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"portfinder": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
@ -14021,6 +14024,18 @@
"whatwg-fetch": "^3.0.0"
}
},
"react-datepicker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.0.0.tgz",
"integrity": "sha512-Yrxan1tERAiWS0EzitpiaiXOIz0APTUtV75uWbaS+jSaKoGCR6wUN2FDwr1ACGlnEoGhR9QQ2Vq3odnWtgJsOA==",
"requires": {
"classnames": "^2.2.6",
"date-fns": "^2.0.1",
"prop-types": "^15.7.2",
"react-onclickoutside": "^6.9.0",
"react-popper": "^1.3.4"
}
},
"react-dev-utils": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
@ -14298,6 +14313,25 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
},
"react-onclickoutside": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
"integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A=="
},
"react-popper": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
"integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
"requires": {
"@babel/runtime": "^7.1.2",
"create-react-context": "^0.3.0",
"deep-equal": "^1.1.1",
"popper.js": "^1.14.4",
"prop-types": "^15.6.1",
"typed-styles": "^0.0.7",
"warning": "^4.0.2"
}
},
"react-redux": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz",
@ -14817,7 +14851,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1"
@ -16156,7 +16189,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
@ -16679,7 +16713,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
@ -16689,7 +16722,6 @@
"version": "1.17.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
"integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
@ -16707,14 +16739,12 @@
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
},
"string.prototype.trimleft": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -16725,7 +16755,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -16738,7 +16767,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
"integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
@ -16748,7 +16776,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
"integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
@ -16758,7 +16785,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
@ -16768,7 +16794,6 @@
"version": "1.17.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
"integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
@ -16786,14 +16811,12 @@
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
},
"string.prototype.trimleft": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -16804,7 +16827,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
@ -17522,6 +17544,11 @@
"mime-types": "~2.1.24"
}
},
"typed-styles": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@ -17966,6 +17993,14 @@
"makeerror": "1.0.x"
}
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"watchpack": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",

View file

@ -3,14 +3,16 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/react-datepicker": "^3.0.2",
"bulma": "^0.8.0",
"bulma-switch": "^2.0.0",
"classnames": "^2.2.6",
"immer": "^6.0.5",
"date-fns": "^2.14.0",
"immer": "^6.0.5",
"lodash": "^4.17.15",
"pretty-ms": "^6.0.1",
"react": "^16.12.0",
"react-datepicker": "^3.0.0",
"react-dom": "^16.12.0",
"react-hook-form": "^4.5.5",
"react-redux": "^7.1.3",

View file

@ -28,3 +28,7 @@ $navbar-width: 250px;
overflow-y: scroll;
}
}
.react-datepicker-wrapper {
display: flex !important;
}

View file

@ -1,16 +1,47 @@
import React from 'react';
import { ClusterName, TopicMessage, TopicName } from 'redux/interfaces';
import React, { useCallback, useEffect, useRef } from 'react';
import {
ClusterName,
SeekTypes,
TopicMessage,
TopicMessageQueryParams,
TopicName,
} from 'redux/interfaces';
import PageLoader from 'components/common/PageLoader/PageLoader';
import { format } from 'date-fns';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import CustomParamButton, {
CustomParamButtonType,
} from 'components/Topics/shared/Form/CustomParams/CustomParamButton';
import { debounce } from 'lodash';
interface Props {
clusterName: ClusterName;
topicName: TopicName;
isFetched: boolean;
fetchTopicMessages: (clusterName: ClusterName, topicName: TopicName) => void;
fetchTopicMessages: (
clusterName: ClusterName,
topicName: TopicName,
queryParams: Partial<TopicMessageQueryParams>
) => void;
messages: TopicMessage[];
}
interface FilterProps {
offset: number;
partition: number;
}
function usePrevious(value: any) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const Messages: React.FC<Props> = ({
isFetched,
clusterName,
@ -18,73 +49,132 @@ const Messages: React.FC<Props> = ({
messages,
fetchTopicMessages,
}) => {
const [searchQuery, setSearchQuery] = React.useState<string>('');
const [searchTimestamp, setSearchTimestamp] = React.useState<Date | null>(
null
);
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
const [queryParams, setQueryParams] = React.useState<
Partial<TopicMessageQueryParams>
>({ limit: 100 });
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]);
React.useEffect(() => {
fetchTopicMessages(clusterName, topicName);
}, [fetchTopicMessages, clusterName, topicName]);
fetchTopicMessages(clusterName, topicName, queryParams);
}, [fetchTopicMessages, clusterName, topicName, queryParams]);
const [searchText, setSearchText] = React.useState<string>('');
React.useEffect(() => {
setFilterProps(getUniqueDataForEachPartition);
}, [messages]);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(event.target.value);
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);
};
const handleDateTimeChange = () => {
if (searchTimestamp !== prevSearchTimestamp) {
if (searchTimestamp) {
const timestamp: number = searchTimestamp.getTime();
setSearchTimestamp(searchTimestamp);
setQueryParams({
...queryParams,
seekType: SeekTypes.TIMESTAMP,
seekTo: filterProps.map((p) => `${p.partition}::${timestamp}`),
});
} else {
setSearchTimestamp(null);
const { seekTo, seekType, ...queryParamsWithoutSeek } = queryParams;
setQueryParams(queryParamsWithoutSeek);
}
}
};
const getTimestampDate = (timestamp: number) => {
return format(new Date(timestamp * 1000), 'MM.dd.yyyy HH:mm:ss');
};
const getMessageContentHeaders = () => {
const getMessageContentHeaders = React.useMemo(() => {
const message = messages[0];
const headers: JSX.Element[] = [];
const content = JSON.parse(message.content);
try {
const content =
typeof message.content !== 'object'
? JSON.parse(message.content)
: message.content;
Object.keys(content).forEach((k) =>
headers.push(<th>{`content.${k}`}</th>)
headers.push(<th key={Math.random()}>{`content.${k}`}</th>)
);
} catch (e) {
headers.push(<th>Content</th>);
}
return headers;
};
}, [messages]);
const getMessageContentBody = (content: string) => {
const c = JSON.parse(content);
const getMessageContentBody = (content: any) => {
const columns: JSX.Element[] = [];
Object.values(c).map((v) => columns.push(<td>{JSON.stringify(v)}</td>));
try {
const c = typeof content !== 'object' ? JSON.parse(content) : content;
Object.values(c).map((v) =>
columns.push(<td key={Math.random()}>{JSON.stringify(v)}</td>)
);
} catch (e) {
columns.push(<td>{content}</td>);
}
return columns;
};
return (
// eslint-disable-next-line no-nested-ternary
isFetched ? (
messages.length > 0 ? (
const onNext = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
const seekTo: string[] = filterProps.map(
(p) => `${p.partition}::${p.offset}`
);
setQueryParams({
...queryParams,
seekType: SeekTypes.OFFSET,
seekTo,
});
};
const getTopicMessagesTable = () => {
return messages.length > 0 ? (
<div>
<div className="columns">
<div className="column is-half is-offset-half">
<input
id="searchText"
type="text"
name="searchText"
className="input"
placeholder="Search"
value={searchText}
onChange={handleInputChange}
/>
</div>
</div>
<table className="table is-striped is-fullwidth">
<thead>
<tr>
<th>Timestamp</th>
<th>Offset</th>
<th>Partition</th>
{getMessageContentHeaders()}
{getMessageContentHeaders}
</tr>
</thead>
<tbody>
{messages
.filter(
(message) =>
!searchText || message?.content?.indexOf(searchText) >= 0
)
.map((message) => (
<tr key={message.timestamp}>
{messages.map((message) => (
<tr key={`${message.timestamp}${Math.random()}`}>
<td>{getTimestampDate(message.timestamp)}</td>
<td>{message.offset}</td>
<td>{message.partition}</td>
@ -93,13 +183,55 @@ const Messages: React.FC<Props> = ({
))}
</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>
)
);
};
return isFetched ? (
<div>
<div className="columns">
<div className="column is-one-quarter">
<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">
<label className="label">Search</label>
<input
id="searchText"
type="text"
name="searchText"
className="input"
placeholder="Search"
value={searchQuery}
onChange={handleQueryChange}
/>
</div>
</div>
<div>{getTopicMessagesTable()}</div>
</div>
) : (
<PageLoader isFullHeight={false} />
)
);
};

View file

@ -1,5 +1,10 @@
import { connect } from 'react-redux';
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
import {
ClusterName,
RootState,
TopicMessageQueryParams,
TopicName,
} from 'redux/interfaces';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { fetchTopicMessages } from 'redux/actions';
import {
@ -31,8 +36,11 @@ const mapStateToProps = (
});
const mapDispatchToProps = {
fetchTopicMessages: (clusterName: ClusterName, topicName: TopicName) =>
fetchTopicMessages(clusterName, topicName),
fetchTopicMessages: (
clusterName: ClusterName,
topicName: TopicName,
queryParams: Partial<TopicMessageQueryParams>
) => fetchTopicMessages(clusterName, topicName, queryParams),
};
export default withRouter(

View file

@ -3,6 +3,7 @@ import React from 'react';
export enum CustomParamButtonType {
plus = 'fa-plus',
minus = 'fa-minus',
chevronRight = 'fa-chevron-right',
}
interface Props {

View file

@ -1,11 +1,7 @@
import React from 'react';
import { useFormContext, ErrorMessage } from 'react-hook-form';
import { TopicFormCustomParam } from 'redux/interfaces';
import CustomParamSelect from 'components/Topics/shared/Form/CustomParams/CustomParamSelect';
import CustomParamValue from 'components/Topics/shared/Form/CustomParams/CustomParamValue';
import CustomParamAction from 'components/Topics/shared/Form/CustomParams/CustomParamAction';
import { INDEX_PREFIX } from './CustomParams';
import CustomParamOptions from './CustomParamOptions';
interface Props {
isDisabled: boolean;

View file

@ -1,6 +1,5 @@
import React from 'react';
import { useFormContext, ErrorMessage } from 'react-hook-form';
import { camelCase } from 'lodash';
import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
interface Props {

View file

@ -7,6 +7,7 @@ import {
TopicFormData,
TopicName,
Topic,
TopicMessageQueryParams,
} from 'redux/interfaces';
import * as actions from './actions';
@ -59,11 +60,16 @@ export const fetchTopicList = (
export const fetchTopicMessages = (
clusterName: ClusterName,
topicName: TopicName
topicName: TopicName,
queryParams: Partial<TopicMessageQueryParams>
): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchTopicMessagesAction.request());
try {
const messages = await api.getTopicMessages(clusterName, topicName);
const messages = await api.getTopicMessages(
clusterName,
topicName,
queryParams
);
dispatch(actions.fetchTopicMessagesAction.success(messages));
} catch (e) {
dispatch(actions.fetchTopicMessagesAction.failure());

View file

@ -9,6 +9,7 @@ import {
TopicFormCustomParam,
TopicFormFormattedParams,
TopicFormCustomParams,
TopicMessageQueryParams,
} from 'redux/interfaces';
import { BASE_URL, BASE_PARAMS } from 'lib/constants';
@ -49,11 +50,28 @@ export const getTopics = (clusterName: ClusterName): Promise<Topic[]> =>
export const getTopicMessages = (
clusterName: ClusterName,
topicName: TopicName
): Promise<TopicMessage[]> =>
fetch(`${BASE_URL}/clusters/${clusterName}/topics/${topicName}/messages`, {
topicName: TopicName,
queryParams: Partial<TopicMessageQueryParams>
): Promise<TopicMessage[]> => {
let searchParams = '';
Object.entries({ ...queryParams }).forEach((entry) => {
const key = entry[0];
const value = entry[1];
if (value) {
if (Array.isArray(value)) {
searchParams += value.map((v) => `${key}=${v}&`);
} else {
searchParams += `${key}=${value}&`;
}
}
});
return fetch(
`${BASE_URL}/clusters/${clusterName}/topics/${topicName}/messages?${searchParams}`,
{
...BASE_PARAMS,
}).then((res) => res.json());
}
).then((res) => res.json());
};
export const postTopic = (
clusterName: ClusterName,

View file

@ -57,7 +57,21 @@ export interface TopicMessage {
timestampType: string;
key: string;
headers: Record<string, string>;
content: string;
content: any;
}
export enum SeekTypes {
OFFSET = 'OFFSET',
TIMESTAMP = 'TIMESTAMP',
}
export type SeekType = keyof typeof SeekTypes;
export interface TopicMessageQueryParams {
q: string;
limit: number;
seekType: SeekType;
seekTo: string[];
}
export interface TopicFormCustomParam {