diff --git a/backend/package-lock.json b/backend/package-lock.json index 93694c69e..bfa695027 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "nr-compliance-enforcement", - "version": "0.0.1", + "version": "0.28.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nr-compliance-enforcement", - "version": "0.0.1", + "version": "0.28.1", "license": "Apache-2.0", "dependencies": { "@automapper/classes": "^8.8.1", @@ -35,6 +35,7 @@ "date-fns-tz": "^3.1.3", "dotenv": "^16.0.1", "escape-html": "^1.0.3", + "fix-esm": "^1.0.1", "form-data": "^4.0.0", "geojson": "^0.5.0", "jest-mock": "^29.6.1", @@ -51,6 +52,7 @@ "reflect-metadata": "^0.1.13", "rimraf": "^4.0.0", "rxjs": "^7.8.0", + "supercluster": "^7.1.5", "swagger-ui-express": "^4.6.0", "typeorm": "^0.3.14", "winston": "^3.9.0" @@ -61,6 +63,7 @@ "@types/jest": "^29.5.1", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", + "@types/supercluster": "^7.1.3", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", @@ -101,7 +104,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -311,86 +313,22 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -399,7 +337,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -429,35 +366,33 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -467,7 +402,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -483,68 +417,30 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -554,51 +450,37 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } @@ -607,7 +489,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -616,7 +497,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", - "dev": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.23.6", @@ -626,93 +506,34 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dependencies": { - "color-convert": "^1.9.0" + "@babel/types": "^7.26.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-async-generators": { @@ -751,6 +572,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -892,34 +724,45 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -931,20 +774,17 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1605,7 +1445,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2446,6 +2285,15 @@ "@types/node": "*" } }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/supertest": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", @@ -3964,8 +3812,7 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "0.6.0", @@ -5447,6 +5294,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-esm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz", + "integrity": "sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==", + "dependencies": { + "@babel/core": "^7.14.6", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -5686,7 +5543,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -7253,15 +7109,14 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -7363,6 +7218,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7848,7 +7708,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -10020,6 +9879,14 @@ "node": ">=4.0.0" } }, + "node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "dependencies": { + "kdbush": "^3.0.0" + } + }, "node_modules/supertest": { "version": "6.3.4", "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", @@ -10239,15 +10106,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11152,8 +11010,7 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yargs": { "version": "17.7.1", @@ -11213,7 +11070,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -11347,76 +11203,24 @@ "integrity": "sha512-f0Y/Q4dVfKH6SQyzS5bPk+hZ4pSxzVNAOPEHdEAUe3gAc6ou43DCBYERuOaVSZ3zbWJbVt1KgWVNLP1dj9OIeg==" }, "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", - "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -11438,32 +11242,30 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } } } @@ -11472,7 +11274,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, "requires": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -11484,176 +11285,84 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "requires": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==" }, "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", "requires": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/helper-validator-option": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" }, "@babel/helpers": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", - "dev": true, "requires": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.23.6", "@babel/types": "^7.23.6" } }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/types": "^7.26.0" } }, - "@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "dev": true + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -11682,6 +11391,14 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -11781,31 +11498,36 @@ "@babel/helper-plugin-utils": "^7.22.5" } }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "requires": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" + } + }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "requires": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -11813,20 +11535,17 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" } } }, "@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@bcoe/v8-coverage": { @@ -12335,7 +12054,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -12968,6 +12686,15 @@ "@types/node": "*" } }, + "@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/supertest": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", @@ -14072,8 +13799,7 @@ "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { "version": "0.6.0", @@ -15181,6 +14907,16 @@ "path-exists": "^4.0.0" } }, + "fix-esm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz", + "integrity": "sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==", + "requires": { + "@babel/core": "^7.14.6", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5" + } + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -15346,8 +15082,7 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "geojson": { "version": "0.5.0", @@ -16473,10 +16208,9 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -16562,6 +16296,11 @@ "safe-buffer": "^5.0.1" } }, + "kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -16913,7 +16652,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" } @@ -18509,6 +18247,14 @@ } } }, + "supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "requires": { + "kdbush": "^3.0.0" + } + }, "supertest": { "version": "6.3.4", "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", @@ -18663,12 +18409,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -19233,8 +18973,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { "version": "17.7.1", diff --git a/backend/package.json b/backend/package.json index 0ee417c5e..10260c0ce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -66,6 +66,7 @@ "date-fns-tz": "^3.1.3", "dotenv": "^16.0.1", "escape-html": "^1.0.3", + "fix-esm": "^1.0.1", "form-data": "^4.0.0", "geojson": "^0.5.0", "jest-mock": "^29.6.1", @@ -82,6 +83,7 @@ "reflect-metadata": "^0.1.13", "rimraf": "^4.0.0", "rxjs": "^7.8.0", + "supercluster": "^7.1.5", "swagger-ui-express": "^4.6.0", "typeorm": "^0.3.14", "winston": "^3.9.0" @@ -92,6 +94,7 @@ "@types/jest": "^29.5.1", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", + "@types/supercluster": "^7.1.3", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", diff --git a/backend/src/types/models/complaints/complaint-search-parameters.ts b/backend/src/types/models/complaints/complaint-search-parameters.ts index 16fa32175..4c7477af1 100644 --- a/backend/src/types/models/complaints/complaint-search-parameters.ts +++ b/backend/src/types/models/complaints/complaint-search-parameters.ts @@ -8,3 +8,10 @@ export interface ComplaintSearchParameters PageParameters, ComplaintFilterParameters, SearchParameters {} + +export interface ComplaintMapSearchClusteredParameters extends ComplaintSearchParameters { + bbox: string; + zoom: number; + clusters: boolean; + unmapped: boolean; +} diff --git a/backend/src/types/models/complaints/map-search-results.ts b/backend/src/types/models/complaints/map-search-results.ts index e025c79dd..5eec575bc 100644 --- a/backend/src/types/models/complaints/map-search-results.ts +++ b/backend/src/types/models/complaints/map-search-results.ts @@ -1,8 +1,8 @@ -import { WildlifeComplaintDto } from "./wildlife-complaint"; -import { AllegationComplaintDto } from "./allegation-complaint"; -import { GeneralIncidentComplaintDto } from "./gir-complaint"; - export interface MapSearchResults { - complaints: WildlifeComplaintDto[] | AllegationComplaintDto[] | GeneralIncidentComplaintDto[]; - unmappedComplaints: number; + clusters?: any; + mappedCount?: number; + unmappedCount?: number; + zoom?: number; + center?: Array; + debugLog?: string; } diff --git a/backend/src/v1/code-table/code-table.controller.spec.ts b/backend/src/v1/code-table/code-table.controller.spec.ts index 5edff2dd2..c7db5016e 100644 --- a/backend/src/v1/code-table/code-table.controller.spec.ts +++ b/backend/src/v1/code-table/code-table.controller.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from "@nestjs/testing"; import { getRepositoryToken } from "@nestjs/typeorm"; import { INestApplication } from "@nestjs/common"; -import * as request from "supertest"; +import request from "supertest"; import { authGuardMock } from "../../../test/mocks/authGuardMock"; import { roleGuardMock } from "../../../test/mocks/roleGuardMock"; diff --git a/backend/src/v1/complaint/complaint.controller.ts b/backend/src/v1/complaint/complaint.controller.ts index d96196c13..1cc5a1213 100644 --- a/backend/src/v1/complaint/complaint.controller.ts +++ b/backend/src/v1/complaint/complaint.controller.ts @@ -9,7 +9,10 @@ import { COMPLAINT_TYPE } from "../../types/models/complaints/complaint-type"; import { WildlifeComplaintDto } from "../../types/models/complaints/wildlife-complaint"; import { AllegationComplaintDto } from "../../types/models/complaints/allegation-complaint"; import { ComplaintDto } from "../../types/models/complaints/complaint"; -import { ComplaintSearchParameters } from "../../types/models/complaints/complaint-search-parameters"; +import { + ComplaintSearchParameters, + ComplaintMapSearchClusteredParameters, +} from "../../types/models/complaints/complaint-search-parameters"; import { ZoneAtAGlanceStats } from "src/types/zone_at_a_glance/zone_at_a_glance_stats"; import { GeneralIncidentComplaintDto } from "src/types/models/complaints/gir-complaint"; import { ApiKeyGuard } from "src/auth/apikey.guard"; @@ -45,7 +48,7 @@ export class ComplaintController { return await this.service.findAllByType(complaintType); } - @Get("/map/search/:complaintType") + /* @Get("/map/search/:complaintType") @Roles(Role.COS_OFFICER, Role.CEEB) mapSearch( @Param("complaintType") complaintType: COMPLAINT_TYPE, @@ -56,6 +59,19 @@ export class ComplaintController { const hasCEEBRole = hasRole(req, Role.CEEB); return this.service.mapSearch(complaintType, model, hasCEEBRole, token); + } */ + + @Get("/map/search/clustered/:complaintType") + @Roles(Role.COS_OFFICER, Role.CEEB) + mapSearchClustered( + @Param("complaintType") complaintType: COMPLAINT_TYPE, + @Query() model: ComplaintMapSearchClusteredParameters, + @Request() req, + @Token() token, + ) { + const hasCEEBRole = hasRole(req, Role.CEEB); + + return this.service.mapSearchClustered(complaintType, model, hasCEEBRole, token); } @Get("/search/:complaintType") diff --git a/backend/src/v1/complaint/complaint.service.spec.ts b/backend/src/v1/complaint/complaint.service.spec.ts index 4b0d14250..cf42abcb6 100644 --- a/backend/src/v1/complaint/complaint.service.spec.ts +++ b/backend/src/v1/complaint/complaint.service.spec.ts @@ -61,7 +61,10 @@ import { MockUpdateComplaintsRepository, } from "../../../test/mocks/mock-complaints-repositories"; import { dataSourceMockFactory } from "../../../test/mocks/datasource"; -import { ComplaintSearchParameters } from "../../types/models/complaints/complaint-search-parameters"; +import { + ComplaintSearchParameters, + ComplaintMapSearchClusteredParameters, +} from "../../types/models/complaints/complaint-search-parameters"; import { GirTypeCode } from "../gir_type_code/entities/gir_type_code.entity"; import { GirComplaint } from "../gir_complaint/entities/gir_complaint.entity"; import { ComplaintUpdatesService } from "../complaint_updates/complaint_updates.service"; @@ -316,51 +319,59 @@ describe("Testing: Complaint Service", () => { it("should return list of complaints by mapSearch for non ceeb role users", async () => { //-- arrange const _complaintType: COMPLAINT_TYPE = "HWCR"; - const payload: ComplaintSearchParameters = { - sortBy: "incident_reported_utc_timestmp", - orderBy: "DESC", + const payload: ComplaintMapSearchClusteredParameters = { zone: "CRBOTMPSN", status: "OPEN", - page: 1, - pageSize: 50, query: "bear", + zoom: 17, + clusters: true, + unmapped: true, + bbox: undefined, + page: undefined, + pageSize: undefined, + sortBy: undefined, + orderBy: undefined, }; //-- act - const results = await service.mapSearch(_complaintType, payload, false); + const results = await service.mapSearchClustered(_complaintType, payload, false); //-- assert expect(results).not.toBe(null); - const { unmappedComplaints, complaints } = results; + const { mappedCount, unmappedCount } = results; - expect(complaints.length).toBe(5); - expect(unmappedComplaints).toBe(55); + expect(mappedCount).toBe(5); + expect(unmappedCount).toBe(55); }); it("should return list of complaints by mapSearch for user with ceeb role", async () => { //-- arrange const _complaintType: COMPLAINT_TYPE = "HWCR"; - const payload: ComplaintSearchParameters = { - sortBy: "incident_reported_utc_timestmp", - orderBy: "DESC", + const payload: ComplaintMapSearchClusteredParameters = { zone: "CRBOTMPSN", status: "OPEN", - page: 1, - pageSize: 50, query: "bear", + zoom: 17, + clusters: true, + unmapped: true, + bbox: undefined, + page: undefined, + pageSize: undefined, + sortBy: undefined, + orderBy: undefined, }; //-- act - const results = await service.mapSearch(_complaintType, payload, true); + const results = await service.mapSearchClustered(_complaintType, payload, true); //-- assert expect(results).not.toBe(null); - const { unmappedComplaints, complaints } = results; + const { mappedCount, unmappedCount } = results; - expect(complaints.length).toBe(5); - expect(unmappedComplaints).toBe(55); + expect(mappedCount).toBe(5); + expect(unmappedCount).toBe(55); }); }); diff --git a/backend/src/v1/complaint/complaint.service.ts b/backend/src/v1/complaint/complaint.service.ts index 114bf62fd..d3e36eef1 100644 --- a/backend/src/v1/complaint/complaint.service.ts +++ b/backend/src/v1/complaint/complaint.service.ts @@ -5,6 +5,8 @@ import { Brackets, DataSource, QueryRunner, Repository, SelectQueryBuilder } fro import { InjectMapper } from "@automapper/nestjs"; import { Mapper } from "@automapper/core"; import { caseFileQueryFields, get } from "../../external_api/case_management"; +import Supercluster, { PointFeature } from "supercluster"; +import { GeoJsonProperties } from "geojson"; import { applyAllegationComplaintMap, @@ -39,7 +41,10 @@ import { mapGirComplaintDtoToGirComplaint, } from "../../middleware/maps/automapper-dto-to-entity-maps"; -import { ComplaintSearchParameters } from "../../types/models/complaints/complaint-search-parameters"; +import { + ComplaintSearchParameters, + ComplaintMapSearchClusteredParameters, +} from "../../types/models/complaints/complaint-search-parameters"; import { SearchResults } from "../../types/models/complaints/search-results"; import { ComplaintFilterParameters } from "../../types/models/complaints/complaint-filter-parameters"; import { REQUEST } from "@nestjs/core"; @@ -70,6 +75,7 @@ import { OfficerService } from "../officer/officer.service"; import { SpeciesCode } from "../species_code/entities/species_code.entity"; import { LinkedComplaintXrefService } from "../linked_complaint_xref/linked_complaint_xref.service"; +const WorldBounds: Array = [-180, -90, 180, 90]; type complaintAlias = HwcrComplaint | AllegationComplaint | GirComplaint; @Injectable({ scope: Scope.REQUEST }) export class ComplaintService { @@ -167,10 +173,60 @@ export class ComplaintService { } }; - private _generateQueryBuilder = ( + private readonly _generateMapQueryBuilder = ( type: COMPLAINT_TYPE, - ): SelectQueryBuilder => { - let builder: SelectQueryBuilder; + includeCosOrganization: boolean, + ): SelectQueryBuilder => { + let builder: SelectQueryBuilder; + + switch (type) { + case "ERS": + builder = this._allegationComplaintRepository + .createQueryBuilder("allegation") + .leftJoin("allegation.complaint_identifier", "complaint") + .addSelect(["complaint.complaint_identifier", "complaint.location_geometry_point"]) + .leftJoin("allegation.violation_code", "violation_code"); + break; + case "GIR": + builder = this._girComplaintRepository + .createQueryBuilder("general") + .leftJoin("general.complaint_identifier", "complaint") + .addSelect(["complaint.complaint_identifier", "complaint.location_geometry_point"]) + .leftJoin("general.gir_type_code", "gir"); + break; + case "HWCR": + default: + builder = this._wildlifeComplaintRepository + .createQueryBuilder("wildlife") + .leftJoin("wildlife.complaint_identifier", "complaint") + .addSelect(["complaint.complaint_identifier", "complaint.location_geometry_point"]) + .leftJoin("wildlife.species_code", "species_code") + .leftJoin("wildlife.hwcr_complaint_nature_code", "complaint_nature_code") + .leftJoin("wildlife.attractant_hwcr_xref", "attractants", "attractants.active_ind = true") + .leftJoin("attractants.attractant_code", "attractant_code"); + break; + } + builder + .leftJoin("complaint.complaint_status_code", "complaint_status") + .leftJoin("complaint.reported_by_code", "reported_by") + .leftJoin("complaint.complaint_update", "complaint_update") + .leftJoin("complaint.action_taken", "action_taken") + .leftJoin("complaint.owned_by_agency_code", "owned_by") + .leftJoin("complaint.linked_complaint_xref", "linked_complaint") + .leftJoin("complaint.person_complaint_xref", "delegate", "delegate.active_ind = true") + .leftJoin("delegate.person_complaint_xref_code", "delegate_code") + .leftJoin("delegate.person_guid", "person", "delegate.active_ind = true") + .leftJoin("complaint.comp_mthd_recv_cd_agcy_cd_xref", "method_xref") + .leftJoin("method_xref.complaint_method_received_code", "method_code") + .leftJoin("method_xref.agency_code", "method_agency"); + if (includeCosOrganization) { + builder.leftJoin("complaint.cos_geo_org_unit", "cos_organization"); + } + return builder; + }; + + private readonly _generateQueryBuilder = (type: COMPLAINT_TYPE): SelectQueryBuilder => { + let builder: SelectQueryBuilder; switch (type) { case "ERS": builder = this._allegationComplaintRepository @@ -1063,89 +1119,43 @@ export class ComplaintService { } }; - mapSearch = async ( + _generateFilteredMapQueryBuilder = async ( complaintType: COMPLAINT_TYPE, - model: ComplaintSearchParameters, + model: ComplaintMapSearchClusteredParameters, hasCEEBRole: boolean, token?: string, - ): Promise => { - this.logger.error("Mapping search results"); - const { orderBy, sortBy, page, pageSize, query, ...filters } = model; + ): Promise> => { + const { query, ...filters } = model; try { - let results: MapSearchResults = { complaints: [], unmappedComplaints: 0 }; - - //-- assign the users agency - // _getAgencyByUser traces agency through assigned office of the officer, which CEEB users do not have - // so the hasCEEBRole is used to assign agency for them. - const agency = hasCEEBRole ? "EPO" : (await this._getAgencyByUser()).agency_code; //-- search for complaints - let complaintBuilder = this._generateQueryBuilder(complaintType); + // Only these options require the cos_geo_org_unit_flat_vw view (cos_organization), which is very slow. + const includeCosOrganization: boolean = Boolean(query || filters.community || filters.zone || filters.region); + let builder = this._generateMapQueryBuilder(complaintType, includeCosOrganization); //-- apply search if (query) { - complaintBuilder = await this._applySearch(complaintBuilder, complaintType, query, token); - } - - //-- apply filters - if (Object.keys(filters).length !== 0) { - complaintBuilder = this._applyFilters(complaintBuilder, filters as ComplaintFilterParameters, complaintType); - } - - //-- only return complaints for the agency the user is associated with - if (agency) { - complaintBuilder.andWhere("complaint.owned_by_agency_code.agency_code = :agency", { - agency: agency, - }); - } - - //-- added this for consistency with search method - //-- return Waste and Pestivide complaints for CEEB users - if (hasCEEBRole && complaintType === "ERS") { - complaintBuilder.andWhere("violation_code.agency_code = :agency", { agency: "EPO" }); - } - - //-- filter locations without coordinates - complaintBuilder.andWhere("ST_X(complaint.location_geometry_point) <> 0"); - complaintBuilder.andWhere("ST_Y(complaint.location_geometry_point) <> 0"); - - //-- get unmapable complaints - let unMappedBuilder = this._generateQueryBuilder(complaintType); - - //-- apply search - if (query) { - unMappedBuilder = await this._applySearch(unMappedBuilder, complaintType, query, token); + builder = await this._applySearch(builder, complaintType, query, token); } - //-- apply filters + //-- apply filters if used if (Object.keys(filters).length !== 0) { - unMappedBuilder = this._applyFilters(unMappedBuilder, filters as ComplaintFilterParameters, complaintType); + builder = this._applyFilters(builder, filters as ComplaintFilterParameters, complaintType); } //-- only return complaints for the agency the user is associated with - if (agency) { - unMappedBuilder.andWhere("complaint.owned_by_agency_code.agency_code = :agency", { - agency: agency, - }); - } + const agency = hasCEEBRole ? "EPO" : (await this._getAgencyByUser()).agency_code; + agency && builder.andWhere("complaint.owned_by_agency_code.agency_code = :agency", { agency }); - //-- added this for consistency with search method //-- return Waste and Pestivide complaints for CEEB users if (hasCEEBRole && complaintType === "ERS") { - unMappedBuilder.andWhere("violation_code.agency_code = :agency", { agency: "EPO" }); + builder.andWhere("violation_code.agency_code = :agency", { agency: "EPO" }); } - //-- filter locations without coordinates - unMappedBuilder.andWhere("ST_X(complaint.location_geometry_point) = 0"); - unMappedBuilder.andWhere("ST_Y(complaint.location_geometry_point) = 0"); - // -- filter by complaint identifiers returned by case management if actionTaken filter is present if (hasCEEBRole && filters.actionTaken) { const complaintIdentifiers = await this._getComplaintsByActionTaken(token, filters.actionTaken); - complaintBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { - complaint_identifiers: complaintIdentifiers, - }); - unMappedBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { + builder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { complaint_identifiers: complaintIdentifiers, }); } @@ -1158,55 +1168,145 @@ export class ComplaintService { filters.outcomeAnimalStartDate, filters.outcomeAnimalEndDate, ); - complaintBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { - complaint_identifiers: complaintIdentifiers, - }); - unMappedBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { + builder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { complaint_identifiers: complaintIdentifiers, }); } - //-- run queries + return builder; + } catch (error) { + this.logger.error(error); + } + }; + + _getUnmappedComplaintsCount = async ( + complaintType: COMPLAINT_TYPE, + model: ComplaintMapSearchClusteredParameters, + hasCEEBRole: boolean, + token?: string, + ): Promise => { + try { + const builder = await this._generateFilteredMapQueryBuilder(complaintType, model, hasCEEBRole, token); + + //-- filter for locations without coordinates + builder.andWhere("ST_X(complaint.location_geometry_point) = 0"); + builder.andWhere("ST_Y(complaint.location_geometry_point) = 0"); + + return builder.getCount(); + } catch (error) { + this.logger.error(error); + } + }; + + _getClusteredComplaints = async ( + complaintType: COMPLAINT_TYPE, + model: ComplaintMapSearchClusteredParameters, + hasCEEBRole: boolean, + token?: string, + ): Promise>> => { + try { + const complaintBuilder = await this._generateFilteredMapQueryBuilder(complaintType, model, hasCEEBRole, token); + + //-- filter locations without coordinates + complaintBuilder.andWhere("complaint.location_geometry_point is not null"); + complaintBuilder.andWhere("ST_X(complaint.location_geometry_point) <> 0"); + complaintBuilder.andWhere("ST_Y(complaint.location_geometry_point) <> 0"); + + //-- filter locations by bounding box if provided, otherwise default to the world + // geometry ST_MakeEnvelope(float xmin, float ymin, float xmax, float ymax, integer srid=unknown); + const bbox = model.bbox ? model.bbox.split(",") : WorldBounds; + complaintBuilder.andWhere( + `complaint.location_geometry_point && ST_MakeEnvelope(${bbox[0]}, ${bbox[1]}, ${bbox[2]}, ${bbox[3]}, 4326)`, + ); + + //-- run mapped query const mappedComplaints = await complaintBuilder.getMany(); - const unmappedComplaints = await unMappedBuilder.getCount(); - results = { ...results, unmappedComplaints }; - //-- map results - switch (complaintType) { - case "ERS": { - const items = this.mapper.mapArray( - mappedComplaints as Array, - "AllegationComplaint", - "AllegationComplaintDto", - ); - results.complaints = items; - break; - } - case "GIR": { - const items = this.mapper.mapArray( - mappedComplaints as Array, - "GirComplaint", - "GeneralIncidentComplaintDto", - ); + // convert to supercluster PointFeature array + const points: Array> = mappedComplaints.map((item) => { + return { + type: "Feature", + properties: { + cluster: false, + id: item.complaint_identifier.complaint_identifier, + }, + geometry: item.complaint_identifier.location_geometry_point, + } as PointFeature; + }); - results.complaints = items; - break; - } - case "HWCR": - default: { - const items = this.mapper.mapArray( - mappedComplaints as Array, - "HwcrComplaint", - "WildlifeComplaintDto", - ); + return points; + } catch (error) { + this.logger.error(error); + } + }; - results.complaints = items; - break; + mapSearchClustered = async ( + complaintType: COMPLAINT_TYPE, + model: ComplaintMapSearchClusteredParameters, + hasCEEBRole: boolean, + token?: string, + ): Promise => { + try { + let results: MapSearchResults = {}; + // Get unmappable complaints if requested + if (model.unmapped) { + // run query and append to results + const unmappedCount = await this._getUnmappedComplaintsCount(complaintType, model, hasCEEBRole, token); + results = { ...results, unmappedCount }; + } + + if (model.clusters) { + const points = await this._getClusteredComplaints(complaintType, model, hasCEEBRole, token); + + results.mappedCount = points.length; + + // load into Supercluster + const index = new Supercluster({ + log: false, + radius: 160, + maxZoom: 16, + }); + index.load(points); + + // cluster the results + const bbox = model.bbox ? model.bbox.split(",") : WorldBounds; + let clusters = index.getClusters( + [Number(bbox[0]), Number(bbox[1]), Number(bbox[2]), Number(bbox[3])], + model.zoom, + ); + + // If we are doing a global search and there is only one cluster, try to explode it to at least 2 clusters + // then return the center and zoom level so the client can then zoom to the clusters at an appropriate zoom level + if (!model.bbox && clusters.length === 1) { + const center = [clusters[0].geometry.coordinates[1], clusters[0].geometry.coordinates[0]]; + const expansionZoom = index.getClusterExpansionZoom(clusters[0].properties.cluster_id); + // If we can expand the cluster, do so. If not, it's a single point and we don't need to do anything + if (expansionZoom) { + clusters = index.getClusters( + [Number(bbox[0]), Number(bbox[1]), Number(bbox[2]), Number(bbox[3])], + expansionZoom + 2, // At least 2 clusters plus an arbitrary 2 steps of zoom + ); + } + results.zoom = expansionZoom || 18; // If we can't expand the cluster, fully zoom to the point + results.center = center; + } else if (!model.bbox && clusters.length === 0) { + // If we are doing a global search and there are no clusters, return the center of BC + if (clusters.length === 0) { + results.zoom = 5; + results.center = [55.0, -125.0]; // Center of BC + } } + + clusters.forEach((cluster) => { + cluster.properties.zoom = index.getClusterExpansionZoom(cluster.properties.cluster_id); + }); + + // set the results + results.clusters = clusters; } return results; } catch (error) { - this.logger.error(error.response); + this.logger.error(error); throw new HttpException("Unable to Perform Search", HttpStatus.BAD_REQUEST); } }; diff --git a/backend/src/v1/office/office.controller.v2.spec.ts b/backend/src/v1/office/office.controller.v2.spec.ts index c8c76c6db..816391661 100644 --- a/backend/src/v1/office/office.controller.v2.spec.ts +++ b/backend/src/v1/office/office.controller.v2.spec.ts @@ -15,7 +15,7 @@ import { JwtRoleGuard } from "../../auth/jwtrole.guard"; import { MockOfficeRepository } from "../../../test/mocks/mock-office-repository"; import { dataSourceMockFactory } from "../../../test/mocks/datasource"; -import * as request from "supertest"; +import request from "supertest"; import { UUID } from "crypto"; import { CreateOfficeDto } from "./dto/create-office.dto"; import { AgencyCodeDto } from "../agency_code/dto/agency_code.dto"; diff --git a/backend/src/v1/officer/officer.service.ts b/backend/src/v1/officer/officer.service.ts index b3c7dca16..df0180621 100644 --- a/backend/src/v1/officer/officer.service.ts +++ b/backend/src/v1/officer/officer.service.ts @@ -34,7 +34,8 @@ export class OfficerService { .leftJoinAndSelect("officer.office_guid", "office") .leftJoinAndSelect("officer.person_guid", "person") .leftJoinAndSelect("office.agency_code", "agency") - .leftJoinAndSelect("office.cos_geo_org_unit", "cos_geo_org_unit") + // This view is slow, no need to join and select for the properties that are mapped in this call + //.leftJoinAndSelect("office.cos_geo_org_unit", "cos_geo_org_unit") .leftJoinAndSelect("office.agency_code", "agency_code") .orderBy("person.last_name", "ASC") .getMany(); diff --git a/backend/src/v1/person/person.controller.spec.ts b/backend/src/v1/person/person.controller.spec.ts index 32741e4ec..d3beaa4b4 100644 --- a/backend/src/v1/person/person.controller.spec.ts +++ b/backend/src/v1/person/person.controller.spec.ts @@ -11,7 +11,7 @@ import { dataSourceMockFactory } from "../../../test/mocks/datasource"; import { PersonRepositoryMockFactory } from "../../../test/mocks/personRepositoryMockFactory"; import { getRepositoryToken } from "@nestjs/typeorm"; import { INestApplication } from "@nestjs/common"; -import * as request from "supertest"; +import request from "supertest"; describe("PersonController", () => { let controller: PersonController; diff --git a/backend/test/app.e2e-spec.ts b/backend/test/app.e2e-spec.ts index e74b0b2e2..35e03caf7 100644 --- a/backend/test/app.e2e-spec.ts +++ b/backend/test/app.e2e-spec.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { Test } from "@nestjs/testing"; import { INestApplication } from "@nestjs/common"; import { AppModule } from "../src/app.module"; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 6ba0771be..4b22ccdd9 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "commonjs", + "esModuleInterop": true, "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index 1888211fb..85b291419 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -94,7 +94,7 @@ spec: timeoutSeconds: 5 resources: # this is optional limits: - cpu: 150m + cpu: 450m memory: 150Mi requests: cpu: 50m diff --git a/frontend/cypress/e2e/complaints-on-map-view.cy.ts b/frontend/cypress/e2e/complaints-on-map-view.cy.ts index 035ce7306..69d3d997a 100644 --- a/frontend/cypress/e2e/complaints-on-map-view.cy.ts +++ b/frontend/cypress/e2e/complaints-on-map-view.cy.ts @@ -22,7 +22,7 @@ describe("Complaints on map tests", () => { cy.get("#map_toggle_id").should("exist").click({ force: true }); // wait for the map to load - cy.waitForSpinner(); + cy.wait(1000); // verify default filters cy.get("#comp-status-filter").should("exist"); @@ -101,13 +101,13 @@ describe("Complaints on map tests", () => { cy.get("#map_toggle_id").click({ force: true }); // wait for the map to load - cy.waitForSpinner(); + cy.wait(1000); cy.get("#comp-filter-btn").click({ force: true }); cy.selectItemById("community-select-id", "Kelowna"); // wait for the map to load - cy.waitForSpinner(); + cy.wait(1000); cy.get("div.leaflet-container").should("exist"); @@ -146,7 +146,7 @@ describe("Complaints on map tests", () => { cy.get("#map_toggle_id").should("exist").click({ force: true }); // wait for the map to load - cy.waitForSpinner(); + cy.wait(1000); // verify default filters cy.get("#comp-status-filter").should("exist"); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0192dba9f..7ee1620a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,7 +34,6 @@ "@types/react-datepicker": "^4.11.2", "@types/react-dom": "^18.0.2", "@types/react-leaflet": "^3.0.0", - "@types/react-leaflet-markercluster": "^3.0.1", "@types/react-phone-number-input": "^3.0.14", "@types/react-transition-group": "^4.4.6", "@types/redux-persist": "^4.3.1", @@ -73,6 +72,7 @@ "nrs-ce-common-types": "^1.0.10", "omggif": "^1.0.10", "os-browserify": "^0.3.0", + "overlapping-marker-spiderfier-leaflet": "^0.2.7", "path-browserify": "^1.0.1", "path-to-regexp": "^8.0.0", "prop-types-extra": "^1.1.1", @@ -89,7 +89,6 @@ "react-is": "^16.13.1", "react-leaflet": "^4.2.1", "react-leaflet-cluster": "^2.1.0", - "react-leaflet-markercluster": "^3.0.0-rc1", "react-lifecycles-compat": "^3.0.4", "react-phone-number-input": "^3.3.6", "react-redux": "^8.0.1", @@ -4778,14 +4777,6 @@ "@types/geojson": "*" } }, - "node_modules/@types/leaflet.markercluster": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.2.tgz", - "integrity": "sha512-Yfi5R0Fb0xc/qotTuqJDAX65XKRt6DauzIdsMTcgrrhgf8DpBOrqPV/jw8/zUjY8FetRd6QdnefNdNJKmG/+zA==", - "dependencies": { - "@types/leaflet": "*" - } - }, "node_modules/@types/lodash": { "version": "4.14.195", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", @@ -4897,26 +4888,6 @@ "react-leaflet": "*" } }, - "node_modules/@types/react-leaflet-markercluster": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/react-leaflet-markercluster/-/react-leaflet-markercluster-3.0.4.tgz", - "integrity": "sha512-YhN2Jts1CI31LXv+defPHvHrbIbp88ZcMwZwUUf4iRnZ/FJ1jDgb41yOuqXrnyAdwfGkm5BU4q6eBUFnh0J4Vw==", - "dependencies": { - "@types/leaflet": "*", - "@types/leaflet.markercluster": "*", - "@types/react": "*", - "@types/react-leaflet": "^2.8.3" - } - }, - "node_modules/@types/react-leaflet-markercluster/node_modules/@types/react-leaflet": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.8.3.tgz", - "integrity": "sha512-MeBQnVQe6ikw8dkuZE4F96PvMdQeilZG6/ekk5XxhkSzU3lofedULn3UR/6G0uIHjbRazi4DA8LnLACX0bPhBg==", - "dependencies": { - "@types/leaflet": "*", - "@types/react": "*" - } - }, "node_modules/@types/react-phone-number-input": { "version": "3.0.14", "resolved": "https://registry.npmjs.org/@types/react-phone-number-input/-/react-phone-number-input-3.0.14.tgz", @@ -8194,9 +8165,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { "ms": "^2.1.3" }, @@ -8626,9 +8597,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", - "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==" + "version": "1.5.72", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz", + "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==" }, "node_modules/emittery": { "version": "0.13.1", @@ -9326,9 +9297,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.0.tgz", - "integrity": "sha512-tCNp4fR79Le3dYTPB0dKEv7yFyvGkUCa+Z3yuTrrNGGOxBlXo9Pn0PEgroOZikUQOGjxoGMVKNjrOHcYEdfszg==", + "version": "50.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.1.tgz", + "integrity": "sha512-UWyaYi6iURdSfdVVqvfOs2vdCVz0J40O/z/HTsv2sFjdjmdlUI/qlKLOTmwbPQ2tAfQnE5F9vqx+B+poF71DBQ==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.49.0", @@ -15362,9 +15333,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -15447,9 +15418,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -18288,6 +18259,11 @@ "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", "dev": true }, + "node_modules/overlapping-marker-spiderfier-leaflet": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/overlapping-marker-spiderfier-leaflet/-/overlapping-marker-spiderfier-leaflet-0.2.7.tgz", + "integrity": "sha512-U2biV2Ge0SU+4IEmq4BZOglvzA8Aj8G7/hp5v6lBnF9Kd3/Xf6ZEPsJyPOExtLvMxOqlrlTAfl55s6JJDND7Ew==" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -19954,6 +19930,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/postcss/node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -20582,83 +20567,6 @@ "react-leaflet": "^4.0.0" } }, - "node_modules/react-leaflet-markercluster": { - "version": "3.0.0-rc1", - "resolved": "https://registry.npmjs.org/react-leaflet-markercluster/-/react-leaflet-markercluster-3.0.0-rc1.tgz", - "integrity": "sha512-wr8ERtx73sY0uVoQAM1v1vsA5Vsbdgyqc88h+Eo2kYRwNdkVTEOoUTnAh3CgGuOyP0Y9QLd2dKGupGkufpwryQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dependencies": { - "@react-leaflet/core": "^1.0.2", - "leaflet": "^1.6.0", - "leaflet.markercluster": "^1.4.1", - "react-leaflet": "^3.0.0" - }, - "peerDependencies": { - "leaflet": "^1.6.0", - "leaflet.markercluster": "^1.4.1", - "react-leaflet": "^3.0.0" - } - }, - "node_modules/react-leaflet-markercluster/node_modules/@react-leaflet/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-1.1.1.tgz", - "integrity": "sha512-7PGLWa9MZ5x/cWy8EH2VzI4T8q5WpuHbixzCDXqixP/WyqwIrg5NDUPgYuFnB4IEIZF+6nA265mYzswFo/h1Pw==", - "peerDependencies": { - "leaflet": "^1.7.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" - } - }, - "node_modules/react-leaflet-markercluster/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-leaflet-markercluster/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/react-leaflet-markercluster/node_modules/react-leaflet": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-3.2.5.tgz", - "integrity": "sha512-Z3KZ+4SijsRbbrt2I1a3ZDY6+V6Pm91eYTdxTN18G6IOkFRsJo1BuSPLFnyFrlF3WDjQFPEcTPkEgD1VEeAoBg==", - "dependencies": { - "@react-leaflet/core": "^1.1.1" - }, - "peerDependencies": { - "leaflet": "^1.7.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" - } - }, - "node_modules/react-leaflet-markercluster/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -23185,9 +23093,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "engines": { "node": ">=0.10.0" } diff --git a/frontend/package.json b/frontend/package.json index 277a5079b..ac9077f08 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,6 @@ "@types/react-datepicker": "^4.11.2", "@types/react-dom": "^18.0.2", "@types/react-leaflet": "^3.0.0", - "@types/react-leaflet-markercluster": "^3.0.1", "@types/react-phone-number-input": "^3.0.14", "@types/react-transition-group": "^4.4.6", "@types/redux-persist": "^4.3.1", @@ -67,6 +66,7 @@ "nrs-ce-common-types": "^1.0.10", "omggif": "^1.0.10", "os-browserify": "^0.3.0", + "overlapping-marker-spiderfier-leaflet": "^0.2.7", "path-browserify": "^1.0.1", "path-to-regexp": "^8.0.0", "prop-types-extra": "^1.1.1", @@ -83,7 +83,6 @@ "react-is": "^16.13.1", "react-leaflet": "^4.2.1", "react-leaflet-cluster": "^2.1.0", - "react-leaflet-markercluster": "^3.0.0-rc1", "react-lifecycles-compat": "^3.0.4", "react-phone-number-input": "^3.3.6", "react-redux": "^8.0.1", diff --git a/frontend/src/app/components/common/search-input.tsx b/frontend/src/app/components/common/search-input.tsx index 3ca9b83a5..bf53692e2 100644 --- a/frontend/src/app/components/common/search-input.tsx +++ b/frontend/src/app/components/common/search-input.tsx @@ -1,11 +1,10 @@ import { ChangeEvent, FC, KeyboardEvent, useContext, useState, useEffect } from "react"; import { InputGroup } from "react-bootstrap"; import { ComplaintFilterContext } from "@providers/complaint-filter-provider"; -import { getComplaints, getMappedComplaints } from "@store/reducers/complaints"; +import { getComplaints } from "@store/reducers/complaints"; import { generateComplaintRequestPayload } from "@components/containers/complaints/complaint-list"; import { useAppDispatch } from "@hooks/hooks"; import { SORT_TYPES } from "@constants/sort-direction"; -import { generateMapComplaintRequestPayload } from "@components/containers/complaints/complaint-map"; type Props = { complaintType: string; @@ -43,12 +42,6 @@ const SearchInput: FC = ({ complaintType, viewType, searchQuery, applySea payload = { ...payload, query: input }; dispatch(getComplaints(complaintType, payload)); - } else { - let payload = generateMapComplaintRequestPayload(complaintType, filters, "", ""); - - payload = { ...payload, query: input }; - - dispatch(getMappedComplaints(complaintType, payload)); } } }; diff --git a/frontend/src/app/components/containers/complaints/complaint-filter.tsx b/frontend/src/app/components/containers/complaints/complaint-filter.tsx index b4e01203f..722fe4f15 100644 --- a/frontend/src/app/components/containers/complaints/complaint-filter.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-filter.tsx @@ -24,7 +24,7 @@ import Option from "@apptypes/app/option"; import { listActiveFilters } from "@store/reducers/app"; import UserService from "@service/user-service"; import Roles from "@apptypes/app/roles"; -import { FilterDate } from "../../common/filter-date"; +import { FilterDate } from "@components/common/filter-date"; type Props = { type: string; diff --git a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx new file mode 100644 index 000000000..4e4a65de4 --- /dev/null +++ b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx @@ -0,0 +1,174 @@ +import { FC, useState, useContext, useEffect, useCallback } from "react"; +import { useAppDispatch } from "@hooks/hooks"; +import COMPLAINT_TYPES from "@apptypes/app/complaint-types"; +import { ComplaintFilterContext } from "@providers/complaint-filter-provider"; +import { ComplaintFilters } from "@apptypes/complaints/complaint-filters/complaint-filters"; +import { ComplaintRequestPayload } from "@/app/types/complaints/complaint-filters/complaint-request-payload"; +import LeafletMapWithServerSideClustering from "@components/mapping/leaflet-map-with-server-side-clustering"; +import { generateApiParameters, get } from "@common/api"; +import config from "@/config"; +import { setMappedComplaintsCount } from "@/app/store/reducers/complaints"; + +type Props = { + type: string; + searchQuery: string; +}; + +export const generateMapComplaintRequestPayload = ( + complaintType: string, + filters: ComplaintFilters, +): ComplaintRequestPayload => { + const { + region, + zone, + community, + officer, + startDate, + endDate, + status, + species, + natureOfComplaint, + violationType, + complaintMethod, + actionTaken, + outcomeAnimal, + outcomeAnimalStartDate, + outcomeAnimalEndDate, + } = filters; + + let common = { + sortColumn: "", // sort or order has no bearing on map data + sortOrder: "", // sort or order has no bearing on map data + regionCodeFilter: region, + zoneCodeFilter: zone, + areaCodeFilter: community, + officerFilter: officer, + startDateFilter: startDate, + endDateFilter: endDate, + complaintStatusFilter: status, + actionTakenFilter: actionTaken, + outcomeAnimalStartDateFilter: outcomeAnimalStartDate, + outcomeAnimalEndDateFilter: outcomeAnimalEndDate, + }; + + switch (complaintType) { + case COMPLAINT_TYPES.ERS: + return { + ...common, + violationFilter: violationType, + complaintMethodFilter: complaintMethod, + } as ComplaintRequestPayload; + case COMPLAINT_TYPES.HWCR: + default: + return { + ...common, + speciesCodeFilter: species, + natureOfComplaintFilter: natureOfComplaint, + outcomeAnimalFilter: outcomeAnimal, + } as ComplaintRequestPayload; + } +}; + +export const ComplaintMapWithServerSideClustering: FC = ({ type, searchQuery }) => { + const dispatch = useAppDispatch(); + + const [loadingMapData, setLoadingMapData] = useState(false); + const [clusters, setClusters] = useState>([]); + const [defaultClusterView, setDefaultClusterView] = useState(); + const [unmappedCount, setUnmappedCount] = useState(0); + + //-- the state from the context is not the same state as used in the rest of the application + //-- this is self-contained, rename the state locally to make clear + const { state: filters } = useContext(ComplaintFilterContext); + + const fetchMapData = useCallback( + async ( + filters: ComplaintFilters, + searchQuery: string, + unmapped: boolean, + clusters: boolean, + zoom: number = 0, + bbox?: { + west?: number; + south?: number; + east?: number; + north?: number; + }, + ) => { + setLoadingMapData(true); + let payload = generateMapComplaintRequestPayload(type, filters); + + let parms: any = { + bbox: bbox ? `${bbox.west},${bbox.south},${bbox.east},${bbox.north}` : undefined, // If the bbox is not provided, return all complaint clusters + zoom: zoom, + region: payload.regionCodeFilter?.value, + zone: payload.zoneCodeFilter?.value, + community: payload.areaCodeFilter?.value, + officerAssigned: payload.officerFilter?.value, + natureOfComplaint: payload.natureOfComplaintFilter?.value, + speciesCode: payload.speciesCodeFilter?.value, + incidentReportedStart: payload.startDateFilter, + incidentReportedEnd: payload.endDateFilter, + violationCode: payload.violationFilter?.value, + status: payload.complaintStatusFilter?.value, + complaintMethod: payload.complaintMethodFilter?.value, + actionTaken: payload.actionTakenFilter?.value, + outcomeAnimal: payload.outcomeAnimalFilter?.value, + outcomeAnimalStartDate: payload.outcomeAnimalStartDateFilter, + outcomeAnimalEndDate: payload.outcomeAnimalEndDateFilter, + query: searchQuery, + }; + + // For a boolean any value including "false" is interpreted as true by our API + if (unmapped) { + parms = { ...parms, unmapped: true }; + } + if (clusters) { + parms = { ...parms, clusters: true }; + } + + let parameters = generateApiParameters(`${config.API_BASE_URL}/v1/complaint/map/search/clustered/${type}`, parms); + + const response: any = await get(dispatch, parameters, {}, false); + if (response) { + response.unmappedCount != null && setUnmappedCount(response.unmappedCount); + // If there is no bounding box, update totals + bbox === undefined && + dispatch( + setMappedComplaintsCount({ + ...(response.mappedCount != null && { mapped: response.mappedCount }), + ...(response.unmappedCount != null && { unmapped: response.unmappedCount }), + }), + ); + response.clusters && setClusters(response.clusters); + if (response.zoom && response.center) { + setDefaultClusterView({ zoom: response.zoom, center: response.center }); + } + } + setLoadingMapData(false); + }, + [dispatch, type], + ); + + useEffect(() => { + //Update map when filters or searchQuery change + fetchMapData(filters, searchQuery, false, true); + fetchMapData(filters, searchQuery, true, false); + }, [fetchMapData, filters, searchQuery]); + + const handleMapMoved = (zoom: number, west?: number, south?: number, east?: number, north?: number) => { + setDefaultClusterView(undefined); // Clear the default cluster view when the map is moved + fetchMapData(filters, searchQuery, false, true, zoom, { west, south, east, north }); + }; + + return ( + + ); +}; diff --git a/frontend/src/app/components/containers/complaints/complaint-map.tsx b/frontend/src/app/components/containers/complaints/complaint-map.tsx deleted file mode 100644 index 63c872f42..000000000 --- a/frontend/src/app/components/containers/complaints/complaint-map.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { FC, useState, useContext, useEffect } from "react"; -import { useAppDispatch, useAppSelector } from "@hooks/hooks"; -import COMPLAINT_TYPES from "@apptypes/app/complaint-types"; -import { SORT_TYPES } from "@constants/sort-direction"; -import { ComplaintFilterContext } from "@providers/complaint-filter-provider"; -import { ComplaintFilters } from "@apptypes/complaints/complaint-filters/complaint-filters"; -import { ComplaintRequestPayload } from "@/app/types/complaints/complaint-filters/complaint-request-payload"; -import LeafletMapWithMultiplePoints from "@components/mapping/leaflet-map-with-multiple-points"; -import { - getMappedComplaints, - selectMappedComplaints, - selectTotalUnmappedComplaints, - setMappedComplaints, -} from "@store/reducers/complaints"; - -type Props = { - type: string; - searchQuery: string; -}; - -export const generateMapComplaintRequestPayload = ( - complaintType: string, - filters: ComplaintFilters, - sortKey: string, - sortDirection: string, -): ComplaintRequestPayload => { - const { - region, - zone, - community, - officer, - startDate, - endDate, - status, - species, - natureOfComplaint, - violationType, - complaintMethod, - actionTaken, - outcomeAnimal, - outcomeAnimalStartDate, - outcomeAnimalEndDate, - } = filters; - - const common = { - sortColumn: sortKey, - sortOrder: sortDirection, - regionCodeFilter: region, - zoneCodeFilter: zone, - areaCodeFilter: community, - officerFilter: officer, - startDateFilter: startDate, - endDateFilter: endDate, - complaintStatusFilter: status, - actionTakenFilter: actionTaken, - }; - - switch (complaintType) { - case COMPLAINT_TYPES.ERS: - return { - ...common, - violationFilter: violationType, - complaintMethodFilter: complaintMethod, - } as ComplaintRequestPayload; - case COMPLAINT_TYPES.HWCR: - default: - return { - ...common, - speciesCodeFilter: species, - natureOfComplaintFilter: natureOfComplaint, - outcomeAnimalFilter: outcomeAnimal, - outcomeAnimalStartDateFilter: outcomeAnimalStartDate, - outcomeAnimalEndDateFilter: outcomeAnimalEndDate, - } as ComplaintRequestPayload; - } -}; - -export const ComplaintMap: FC = ({ type, searchQuery }) => { - const dispatch = useAppDispatch(); - - const complaints = useAppSelector(selectMappedComplaints); - const unmappedComplaints = useAppSelector(selectTotalUnmappedComplaints); - - //-- the state from the context is not the same state as used in the rest of the application - //-- this is self-contained, rename the state locally to make clear - const { state: filters } = useContext(ComplaintFilterContext); - - const [sortKey, setSortKey] = useState("incident_reported_utc_timestmp"); - const [sortDirection, setSortDirection] = useState(SORT_TYPES.DESC); - - useEffect(() => { - //Update map when filters change - let payload = generateMapComplaintRequestPayload(type, filters, sortKey, sortDirection); - - if (searchQuery) { - payload = { ...payload, query: searchQuery }; - } - - dispatch(getMappedComplaints(type, payload)); - }, [filters]); - - useEffect(() => { - //when the search Query is cleared refresh the map - if (!searchQuery) { - let payload = generateMapComplaintRequestPayload(type, filters, sortKey, sortDirection); - payload = { ...payload, query: searchQuery }; - - dispatch(getMappedComplaints(type, payload)); - } - }, [searchQuery]); - - useEffect(() => { - //-- when the component unmounts clear the complaint from redux - return () => { - dispatch(setMappedComplaints({ type: { type }, data: [] })); - }; - }, []); - - return ( - - ); -}; diff --git a/frontend/src/app/components/containers/complaints/complaints.tsx b/frontend/src/app/components/containers/complaints/complaints.tsx index aaba36ed7..91ecbf986 100644 --- a/frontend/src/app/components/containers/complaints/complaints.tsx +++ b/frontend/src/app/components/containers/complaints/complaints.tsx @@ -19,7 +19,7 @@ import { setActiveComplaintsViewType, } from "../../../store/reducers/app"; -import { ComplaintMap } from "./complaint-map"; +import { ComplaintMapWithServerSideClustering } from "./complaint-map-with-server-side-clustering"; import { useNavigate } from "react-router-dom"; import { ComplaintListTabs } from "./complaint-list-tabs"; import { COMPLAINT_TYPES, CEEB_TYPES } from "@apptypes/app/complaint-types"; @@ -45,7 +45,7 @@ export const Complaints: FC = ({ defaultComplaintType }) => { if (!storedComplaintType) dispatch(setActiveTab(defaultComplaintType)); }, [storedComplaintType, dispatch, defaultComplaintType]); const [complaintType, setComplaintType] = useState( - UserService.hasRole([Roles.CEEB]) ? CEEB_TYPES.ERS : storedComplaintType ?? defaultComplaintType, + UserService.hasRole([Roles.CEEB]) ? CEEB_TYPES.ERS : (storedComplaintType ?? defaultComplaintType), ); const storedComplaintViewType = useAppSelector(selectActiveComplaintsViewType); @@ -154,7 +154,7 @@ export const Complaints: FC = ({ defaultComplaintType }) => { searchQuery={search} /> ) : ( - diff --git a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/edit-outcome.tsx b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/edit-outcome.tsx index 1df54a955..2c534c448 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/edit-outcome.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/edit-outcome.tsx @@ -543,9 +543,13 @@ export const EditOutcome: FC = ({ id, index, outcome, assignedOfficer: of placeholder={"Select"} value={getDropdownOption(data.outcome, outcomes)} onChange={(evt) => { - showEditWarning(() => { + if (data?.outcome) { + showEditWarning(() => { + updateModel("outcome", evt?.value); + }); + } else { updateModel("outcome", evt?.value); - }); + } }} defaultOption={getDropdownOption(data.outcome, outcomes)} /> @@ -591,9 +595,13 @@ export const EditOutcome: FC = ({ id, index, outcome, assignedOfficer: of id="equipment-day-set" maxDate={new Date()} onChange={(input: Date) => { - showEditWarning(() => { + if (data?.date) { + showEditWarning(() => { + handleOutcomeDateChange(input); + }); + } else { handleOutcomeDateChange(input); - }); + } }} selectedDate={data?.date} placeholder={"Select"} diff --git a/frontend/src/app/components/mapping/leaflet-map-with-multiple-points.tsx b/frontend/src/app/components/mapping/leaflet-map-with-multiple-points.tsx deleted file mode 100644 index afaac287f..000000000 --- a/frontend/src/app/components/mapping/leaflet-map-with-multiple-points.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { MapContainer, TileLayer, Marker } from "react-leaflet"; -import MarkerClusterGroup from "react-leaflet-cluster"; -import "leaflet/dist/leaflet.css"; -import "react-leaflet-markercluster/dist/styles.min.css"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import ReactDOMServer from "react-dom/server"; -import { faMapMarkerAlt } from "@fortawesome/free-solid-svg-icons"; -import Leaflet, { LatLngExpression, Map } from "leaflet"; -import { ComplaintSummaryPopup } from "./complaint-summary-popup"; -import { useAppDispatch } from "@hooks/hooks"; -import { getComplaintById, setComplaint } from "@store/reducers/complaints"; -import { isEqual } from "lodash"; -import { BsInfoCircleFill } from "react-icons/bs"; -import { ComplaintMapItem } from "@apptypes/app/complaints/complaint-map-item"; -import { from } from "linq-to-typescript"; -import { MapGestureHandler } from "./map-gesture-handler"; -import { Alert } from "react-bootstrap"; - -interface MapProps { - complaintType: string; - markers: Array; - unmappedComplaints: number; -} - -const LeafletMapWithMultiplePoints: React.FC = ({ complaintType, markers, unmappedComplaints }) => { - const iconHTML = ReactDOMServer.renderToString(); - const mapRef = useRef(null); - const [markersState, setMarkersState] = useState>(markers); - - useEffect(() => { - if (mapRef.current && markersState.length > 0) { - // Calculate the bounds of all markers - const bounds = Leaflet.latLngBounds( - markersState.map((marker) => [marker.latitude, marker.longitude] as LatLngExpression), - ); - - // Fit the map to the bounds - mapRef.current.fitBounds(bounds, { padding: [35, 35] }); - } - }, [markersState]); - - // redux will update the location store when the complaint details are retrieved. We don't want this to trigger - // a re-render of the map to fit the markers on screen. So, let's compare the new markers against the marker state. - // If they're the same, don't re-center the map and don't zoom out. - useEffect(() => { - if (!isEqual(markersState, markers)) { - setMarkersState(markers); - } - }, [markers, markersState]); - - const customMarkerIcon = new Leaflet.DivIcon({ - html: iconHTML, - className: "map-marker", - iconSize: [40, 0], // Adjust icon size as needed - iconAnchor: [20, 40], // Adjust icon anchor point - }); - - const dispatch = useAppDispatch(); - - const handlePopupOpen = (id: string) => (e: L.PopupEvent) => { - dispatch(getComplaintById(id, complaintType)); - }; - - const handlePopupClose = (e: L.LeafletEvent) => { - dispatch(setComplaint(null)); - }; - - const renderInformationBanner = () => { - const showBar = () => { - if (unmappedComplaints >= 1) { - return true; - } else if (!from(markers).any()) { - return true; - } else { - return false; - } - }; - - const isPluralized = unmappedComplaints === 1 ? "" : "s"; - - if (showBar()) { - const bannerType = unmappedComplaints >= 1 ? "unmapped" : "no-results"; - const info = - unmappedComplaints >= 1 - ? `${unmappedComplaints} complaint${isPluralized} could not be mapped` - : "No complaints found using your current filters. Remove or change your filters to see complaints."; - - return ( - - - {info} - - ); - } - - return <>; - }; - - return ( -
- {renderInformationBanner()} - - - - - {markers.map((marker) => ( - - - - ))} - - -
- ); -}; - -export default LeafletMapWithMultiplePoints; diff --git a/frontend/src/app/components/mapping/leaflet-map-with-server-side-clustering.tsx b/frontend/src/app/components/mapping/leaflet-map-with-server-side-clustering.tsx new file mode 100644 index 000000000..679b83bcc --- /dev/null +++ b/frontend/src/app/components/mapping/leaflet-map-with-server-side-clustering.tsx @@ -0,0 +1,226 @@ +import React, { useEffect, useRef, useState } from "react"; +import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet"; +import "leaflet/dist/leaflet.css"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import ReactDOMServer from "react-dom/server"; +import { faMapMarkerAlt } from "@fortawesome/free-solid-svg-icons"; +import Leaflet, { LatLngExpression, Map } from "leaflet"; +import { ComplaintSummaryPopup } from "./complaint-summary-popup"; +import { useAppDispatch, useAppSelector } from "@hooks/hooks"; +import { getComplaintById, setComplaint } from "@store/reducers/complaints"; +import { from } from "linq-to-typescript"; +import { MapGestureHandler } from "./map-gesture-handler"; +import { Alert, Spinner } from "react-bootstrap"; +import { isLoading } from "@store/reducers/app"; +import Spiderfy from "./spiderfy"; + +interface MapProps { + complaintType: string; + handleMapMoved: (zoom: number, west: number, south: number, east: number, north: number) => void; + loadingMapData: boolean; + clusters: Array; + defaultClusterView: any; + unmappedCount: number; +} + +const LeafletMapWithServerSideClustering: React.FC = ({ + complaintType, + loadingMapData, + handleMapMoved, + clusters, + defaultClusterView, + unmappedCount, +}) => { + const loading = useAppSelector(isLoading); + + const iconHTML = ReactDOMServer.renderToString(); + const mapRef = useRef(null); + + const [popupOpen, setPopupOpen] = useState(false); + + const customMarkerIcon = new Leaflet.DivIcon({ + html: iconHTML, + className: "map-marker", + iconSize: [40, 0], // Adjust icon size as needed + iconAnchor: [20, 40], // Adjust icon anchor point + }); + + const dispatch = useAppDispatch(); + + const handlePopupOpen = (id: string) => { + dispatch(getComplaintById(id, complaintType)); + setPopupOpen(true); + }; + + const handlePopupClose = () => { + dispatch(setComplaint(null)); + setPopupOpen(false); + refreshMapData(); + }; + + useEffect(() => { + setPopupOpen(false); + if (defaultClusterView) { + if (clusters.length > 0) { + // Calculate the bounds of all markers + const bounds = Leaflet.latLngBounds( + clusters.map( + (marker) => [marker.geometry.coordinates[1], marker.geometry.coordinates[0]] as LatLngExpression, + ), + ); + + // Fit the map to the bounds + mapRef?.current?.fitBounds(bounds, { padding: [35, 35] }); + } else if (defaultClusterView.center && defaultClusterView.zoom) { + mapRef?.current?.setView(defaultClusterView.center, defaultClusterView.zoom); + } + } + }, [clusters, defaultClusterView]); + + const renderInformationBanner = () => { + const isPluralized = unmappedCount === 1 ? "" : "s"; + + const bannerType = unmappedCount >= 1 ? "unmapped" : "no-results"; + const info = + unmappedCount >= 1 + ? `${unmappedCount} complaint${isPluralized} could not be mapped` + : "No complaints found using your current filters. Remove or change your filters to see complaints."; + + return ( + + + {info} + + ); + }; + + const refreshMapData = () => { + const bounds = mapRef.current?.getBounds(); + if (bounds && mapRef?.current) { + handleMapMoved( + mapRef.current.getZoom(), + bounds.getWest(), + bounds.getSouth(), + bounds.getEast(), + bounds.getNorth(), + ); + } + }; + + const ServerSideClusteringHandler = () => { + useMapEvents({ + moveend: () => { + if (!popupOpen && !loading) { + refreshMapData(); + } + }, + }); + + return null; + }; + + const showInfoBar = unmappedCount >= 1 || !from(clusters).any(); + + return ( +
+ {showInfoBar && renderInformationBanner()} + {loadingMapData && !loading && ( + + )} + + + + + + {clusters.map((cluster) => { + const [longitude, latitude] = cluster.geometry.coordinates; + const { + id: clusterId, + cluster: isCluster, + point_count: pointCount, + point_count_abbreviated: pointCountAbbreviated, + zoom: clusterZoom, + } = cluster.properties; + let size; + if (pointCount < 5) { + size = "small"; + } else if (pointCount < 50) { + size = "medium"; + } else { + size = "large"; + } + + const customClusterIcon = new Leaflet.DivIcon({ + html: `
${pointCountAbbreviated}
`, + className: `marker-cluster marker-cluster-${size}`, + iconSize: [40, 40], + }); + + return isCluster ? ( + { + // What even is this logic + const flyToZoom = + (clusterZoom && Math.min(clusterZoom, 18)) || + Math.min(clusterZoom, Math.min((mapRef?.current?.getZoom() || 18) + 2, 18)); + + // If we're not fully zoomed in, zoom in to the cluster + mapRef.current?.flyTo([latitude, longitude], flyToZoom, { + duration: 0.5, + easeLinearity: 0.5, + }); + }, + }} + /> + ) : ( + { + event.target.closePopup(); + }, + }} + > + + + ); + })} +
+
+
+ ); +}; + +export default LeafletMapWithServerSideClustering; diff --git a/frontend/src/app/components/mapping/spiderfy.tsx b/frontend/src/app/components/mapping/spiderfy.tsx new file mode 100644 index 000000000..6dc8c8482 --- /dev/null +++ b/frontend/src/app/components/mapping/spiderfy.tsx @@ -0,0 +1,70 @@ +import React, { useEffect } from "react"; +import { useMap } from "react-leaflet"; +import L from "leaflet"; + +// Hacky, but there is no ES6 module for this library +import "overlapping-marker-spiderfier-leaflet/dist/oms"; +declare global { + interface Window { + OverlappingMarkerSpiderfier: any; + } +} + +interface SpiderfyProps { + onSpiderfy?: (markers: any[]) => void; + onUnspiderfy?: (markers: any) => void; + onClick?: (marker: any) => void; + handlePopupOpen?: (id: string) => void; + handlePopupClose?: () => void; + children?: React.ReactNode; +} + +const Spiderfy = (props: SpiderfyProps) => { + const map = useMap(); + // Get the OverlappingMarkerSpiderfier from the window object + const OverlappingMarkerSpiderfier = window.OverlappingMarkerSpiderfier; + // Create a new instance of the OverlappingMarkerSpiderfier whenever we get a new map instnace + const oms = React.useMemo(() => new OverlappingMarkerSpiderfier(map), [OverlappingMarkerSpiderfier, map]); + + // When the props change, we need to clear then rebind the event listeners again + useEffect(() => { + oms.clearListeners("spiderfy"); + oms.addListener("spiderfy", (markers: any[]) => { + markers.forEach((m) => m.closePopup()); //force to close popup + if (props.onSpiderfy) props.onSpiderfy(markers); + }); + + oms.clearListeners("unspiderfy"); + oms.addListener("unspiderfy", (markers: any) => { + if (props.onUnspiderfy) props.onUnspiderfy(markers); + }); + + oms.clearListeners("click"); + oms.addListener("click", (marker: any) => { + // Hacky, but event listeners here always run first before the map click event listener resulting in the popup never opening + // so we will delay by 100ms + setTimeout(() => { + // Hacky, but alt is being used to store the complaint id. Maybe later we could create + // our own custom marker class to add but it's not clear that will work with the spiderfier library used here + props.handlePopupOpen && props.handlePopupOpen(marker.options.alt); + marker.openPopup(); + marker.addOneTimeEventListener("popupclose", props.handlePopupClose); + }, 100); + + if (props.onClick) props.onClick(marker); + }); + }, [oms, props]); + + useEffect(() => { + map.eachLayer((layer) => { + // Hacky, but "RiseOnHover" is being used to identify non-clustered markers that should be spiderfied. + if (layer instanceof L.Marker && layer?.options?.riseOnHover) { + oms.addMarker(layer); + } + }); + }, [oms, map, props.children]); + + return
{props.children}
; +}; + +export default Spiderfy; diff --git a/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx b/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx index 8466bd337..59d778259 100644 --- a/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx +++ b/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx @@ -3,7 +3,7 @@ //-- where unable to use the global redux modals //-- -import { FC } from "react"; +import { FC, useEffect, useState } from "react"; import { Button, Col, Modal, Row } from "react-bootstrap"; type props = { @@ -25,11 +25,22 @@ export const StandaloneConfirmCancelModal: FC = ({ confirm, cancel, }) => { + const [showModal, setShowModal] = useState(false); + + useEffect(() => { + setShowModal(show); + }, [show]); + + const handleClose = () => { + cancel(); + }; + return ( {title && ( diff --git a/frontend/src/app/store/migrations.ts b/frontend/src/app/store/migrations.ts index dca1ae84d..1702f4505 100644 --- a/frontend/src/app/store/migrations.ts +++ b/frontend/src/app/store/migrations.ts @@ -21,6 +21,7 @@ import { AddCat1TypeAndLocationType } from "./migrations/migration-21"; import { AddActiveComplaintsViewType } from "./migrations/migration-22"; import { AssessmentTypeUpdates } from "./migrations/migration-23"; import { AddComsEnrolledInd } from "./migrations/migration-24"; +import { UpdateMapLogicForClustering } from "./migrations/migration-25"; const BaseMigration = { 0: (state: any) => { @@ -56,6 +57,7 @@ migration = { ...AddActiveComplaintsViewType, ...AssessmentTypeUpdates, ...AddComsEnrolledInd, + ...UpdateMapLogicForClustering, }; export default migration; diff --git a/frontend/src/app/store/migrations/migration-25.ts b/frontend/src/app/store/migrations/migration-25.ts new file mode 100644 index 000000000..5a0626dba --- /dev/null +++ b/frontend/src/app/store/migrations/migration-25.ts @@ -0,0 +1,12 @@ +// Refresh the profile for coms access indicator +export const UpdateMapLogicForClustering = { + 25: (state: any) => { + return { + ...state, + complaints: { + ...state.complaints, + mappedComplaintsCount: { mapped: 0, unmapped: 0 }, + }, + }; + }, +}; diff --git a/frontend/src/app/store/reducers/complaints.ts b/frontend/src/app/store/reducers/complaints.ts index 8d9eb94a9..3e6c58f84 100644 --- a/frontend/src/app/store/reducers/complaints.ts +++ b/frontend/src/app/store/reducers/complaints.ts @@ -18,9 +18,6 @@ import { ToggleSuccess, ToggleError } from "@common/toast"; import { ComplaintSearchResults } from "@apptypes/api-params/complaint-results"; import { Coordinates } from "@apptypes/app/coordinate-type"; -import { MapSearchResults } from "@apptypes/complaints/map-return"; -import { ComplaintMapItem } from "@apptypes/app/complaints/complaint-map-item"; - import { WildlifeComplaint as WildlifeComplaintDto } from "@apptypes/app/complaints/wildlife-complaint"; import { AllegationComplaint as AllegationComplaintDto } from "@apptypes/app/complaints/allegation-complaint"; import { Complaint as ComplaintDto } from "@apptypes/app/complaints/complaint"; @@ -63,7 +60,7 @@ const initialState: ComplaintState = { allegation: { assigned: 0, unassigned: 0, total: 0, offices: [] }, }, - mappedItems: { items: [], unmapped: 0 }, + mappedComplaintsCount: { mapped: 0, unmapped: 0 }, webeocUpdates: [], actions: [], @@ -209,21 +206,22 @@ export const complaintSlice = createSlice({ return { ...state, complaintItems: updatedItems }; } }, - setMappedComplaints: (state, action) => { - const { mappedItems } = state; + setMappedComplaintsCount: (state, action) => { + const { mappedComplaintsCount } = current(state); const { - payload: { complaints, unmappedComplaints }, + payload: { mapped, unmapped }, } = action; const update = { - ...mappedItems, - items: complaints, - unmapped: unmappedComplaints, + mapped: mapped ?? mappedComplaintsCount.mapped, + unmapped: unmapped ?? mappedComplaintsCount.unmapped, }; - return { ...state, mappedItems: update }; + return { + ...state, + mappedComplaintsCount: update, + }; }, - setWebEOCUpdates: (state, action: PayloadAction) => { return { ...state, webeocUpdates: action.payload }; }, @@ -268,7 +266,7 @@ export const { updateGeneralComplaintByRow, updateAllegationComplaintByRow, updateGeneralIncidentComplaintByRow, - setMappedComplaints, + setMappedComplaintsCount, setWebEOCUpdates, setRelatedData, setActions, @@ -349,67 +347,6 @@ export const getComplaints = } }; -export const getMappedComplaints = - (complaintType: string, payload: ComplaintFilters): AppThunk => - async (dispatch, getState) => { - const { - sortColumn, - sortOrder, - regionCodeFilter, - areaCodeFilter, - zoneCodeFilter, - officerFilter, - natureOfComplaintFilter, - speciesCodeFilter, - startDateFilter, - endDateFilter, - violationFilter, - complaintStatusFilter, - complaintMethodFilter, - actionTakenFilter, - outcomeAnimalFilter, - outcomeAnimalStartDateFilter, - outcomeAnimalEndDateFilter, - page, - pageSize, - query, - } = payload; - - try { - dispatch(setComplaint(null)); - dispatch(setComplaintSearchParameters(payload)); - - let parameters = generateApiParameters(`${config.API_BASE_URL}/v1/complaint/map/search/${complaintType}`, { - sortBy: sortColumn, - orderBy: sortOrder, - region: regionCodeFilter?.value, - zone: zoneCodeFilter?.value, - community: areaCodeFilter?.value, - officerAssigned: officerFilter?.value, - natureOfComplaint: natureOfComplaintFilter?.value, - speciesCode: speciesCodeFilter?.value, - incidentReportedStart: startDateFilter, - incidentReportedEnd: endDateFilter, - violationCode: violationFilter?.value, - status: complaintStatusFilter?.value, - complaintMethod: complaintMethodFilter?.value, - actionTaken: actionTakenFilter?.value, - outcomeAnimal: outcomeAnimalFilter?.value, - outcomeAnimalStartDate: outcomeAnimalStartDateFilter, - outcomeAnimalEndDate: outcomeAnimalEndDateFilter, - page: page, - pageSize: pageSize, - query: query, - }); - - const response = await get(dispatch, parameters); - - dispatch(setMappedComplaints(response)); - } catch (error) { - console.log(`Unable to retrieve ${complaintType} complaints: ${error}`); - } - }; - export const getZoneAtAGlanceStats = (zone: string, type: ComplaintType): AppThunk => async (dispatch) => { @@ -823,45 +760,10 @@ export const selectComplaintsByType = export const selectTotalMappedComplaints = (state: RootState): number => { const { complaints: { - mappedItems: { items, unmapped }, - }, - } = state; - if (!items && !unmapped) return 0; - return items.length + unmapped; -}; - -export const selectTotalUnmappedComplaints = (state: RootState): number => { - const { - complaints: { - mappedItems: { unmapped }, + mappedComplaintsCount: { mapped, unmapped }, }, } = state; - - return unmapped; -}; - -export const selectMappedComplaints = (state: RootState): Array => { - const { - complaints: { - mappedItems: { items }, - }, - } = state; - - if (items) { - const records = items.map(({ id, location: { coordinates } }) => { - const record: ComplaintMapItem = { - id, - latitude: coordinates[Coordinates.Latitude], - longitude: coordinates[Coordinates.Longitude], - }; - - return record; - }); - - return records; - } else { - return new Array(); - } + return mapped + unmapped; }; export const selectComplaint = ( diff --git a/frontend/src/app/store/store.ts b/frontend/src/app/store/store.ts index 78085adba..41e8b0690 100644 --- a/frontend/src/app/store/store.ts +++ b/frontend/src/app/store/store.ts @@ -19,7 +19,7 @@ const persistConfig = { storage, blacklist: ["app"], whitelist: ["codeTables", "officers"], - version: 24, // This needs to be incremented every time a new migration is added + version: 25, // This needs to be incremented every time a new migration is added debug: true, migrate: createMigrate(migration, { debug: false }), }; diff --git a/frontend/src/app/types/complaints/complaint-filters/complaint-request-payload.ts b/frontend/src/app/types/complaints/complaint-filters/complaint-request-payload.ts index 256e760af..2d1f0506f 100644 --- a/frontend/src/app/types/complaints/complaint-filters/complaint-request-payload.ts +++ b/frontend/src/app/types/complaints/complaint-filters/complaint-request-payload.ts @@ -13,5 +13,10 @@ export interface ComplaintRequestPayload { startDateFilter?: Date; endDateFilter?: Date; complaintStatusFilter?: Option; + complaintMethodFilter?: Option; + actionTakenFilter?: Option; + outcomeAnimalFilter?: Option; + outcomeAnimalStartDateFilter?: Date; + outcomeAnimalEndDateFilter?: Date; query?: string; } diff --git a/frontend/src/app/types/complaints/map-return.ts b/frontend/src/app/types/complaints/map-return.ts index 3edf9325d..5eec575bc 100644 --- a/frontend/src/app/types/complaints/map-return.ts +++ b/frontend/src/app/types/complaints/map-return.ts @@ -1,9 +1,8 @@ -import { WildlifeComplaint } from "@apptypes/app/complaints/wildlife-complaint"; -import { AllegationComplaint as AllegationComplaintModel } from "@apptypes/app/complaints/allegation-complaint"; -import { AllegationComplaint } from "./allegation-complaint"; -import { HwcrComplaint } from "./hwcr-complaint"; - export interface MapSearchResults { - complaints: HwcrComplaint[] | AllegationComplaint[] | Array | Array; - unmappedComplaints: number; + clusters?: any; + mappedCount?: number; + unmappedCount?: number; + zoom?: number; + center?: Array; + debugLog?: string; } diff --git a/frontend/src/app/types/state/complaint-state.ts b/frontend/src/app/types/state/complaint-state.ts index 70411cf7d..503ef8b57 100644 --- a/frontend/src/app/types/state/complaint-state.ts +++ b/frontend/src/app/types/state/complaint-state.ts @@ -15,7 +15,7 @@ export interface ComplaintState { complaint: WildlifeComplaintDto | AllegationComplaintDto | GeneralInformationComplaintDto | null; zoneAtGlance: ZoneAtAGlanceState; complaintLocation: Feature | null; - mappedItems: MappedComplaintsState; + mappedComplaintsCount: MappedComplaintsCountState; webeocUpdates: WebEOCComplaintUpdateDTO[]; actions: ActionTaken[]; webeocChangeCount: number; @@ -28,8 +28,8 @@ export interface ComplaintCollection { general: Array; } -export interface MappedComplaintsState { - items: Array | Array | Array; +export interface MappedComplaintsCountState { + mapped: number; unmapped: number; } diff --git a/frontend/src/assets/sass/maps.scss b/frontend/src/assets/sass/maps.scss index 22145670b..5c37e18ae 100644 --- a/frontend/src/assets/sass/maps.scss +++ b/frontend/src/assets/sass/maps.scss @@ -182,3 +182,51 @@ color: #fff; } } + +.marker-cluster-small { + background-color: rgba(181, 226, 140, 0.6); +} + +.marker-cluster-small div { + background-color: rgba(110, 204, 57, 0.6); +} + +.marker-cluster-medium { + background-color: rgba(241, 211, 87, 0.6); +} + +.marker-cluster-medium div { + background-color: rgba(240, 194, 12, 0.6); +} + +.marker-cluster-large { + background-color: rgba(253, 156, 115, 0.6); +} + +.marker-cluster-large div { + background-color: rgba(241, 128, 23, 0.6); +} + +.marker-cluster { + background-clip: padding-box; + border-radius: 20px; +} + +.marker-cluster div { + width: 30px; + height: 30px; + margin-left: 5px; + margin-top: 5px; + + text-align: center; + border-radius: 15px; + font: + 12px "Helvetica Neue", + Arial, + Helvetica, + sans-serif; +} + +.marker-cluster span { + line-height: 30px; +} diff --git a/webeoc/package-lock.json b/webeoc/package-lock.json index ad3f2265c..4907510f3 100644 --- a/webeoc/package-lock.json +++ b/webeoc/package-lock.json @@ -1,12 +1,12 @@ { "name": "webeoc", - "version": "0.0.1", + "version": "0.28.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webeoc", - "version": "0.0.1", + "version": "0.28.1", "license": "UNLICENSED", "dependencies": { "@nestjs/axios": "^3.0.3", diff --git a/webeoc/test/app.e2e-spec.ts b/webeoc/test/app.e2e-spec.ts index dde7438bb..c8e59103f 100644 --- a/webeoc/test/app.e2e-spec.ts +++ b/webeoc/test/app.e2e-spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from "@nestjs/testing"; import { INestApplication } from "@nestjs/common"; -import * as request from "supertest"; +import request from "supertest"; import { AppModule } from "./../src/app.module"; describe("AppController (e2e)", () => {