diff --git a/package-lock.json b/package-lock.json index 3ce59313..b739adc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -614,33 +614,210 @@ } }, "@openstapps/core": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.38.0.tgz", - "integrity": "sha512-wFseZk5UtlLzVYoVPu+2YVLb0Ow7Lh4xVVW5Nsoew9Bft59sVZdroSPIPXqWWApQ40R0CVWxwxng0soO4bZRbw==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.41.0.tgz", + "integrity": "sha512-cWzyVVUrQCrHk5EEuoWLfLt6cKLcDwoLY1UPxqschqeDmI+zlqBA0vnOsmAgVXZxv2+msFRYbkCiUd3Mofdzvg==", "requires": { "@openstapps/core-tools": "0.16.0", "@types/geojson": "1.0.6", "@types/json-patch": "0.0.30", "@types/json-schema": "7.0.6", - "@types/node": "10.17.14", + "@types/node": "10.17.44", "fast-clone": "1.5.13", - "http-status-codes": "2.1.2", + "http-status-codes": "2.1.4", "json-patch": "0.7.0", "json-schema": "0.2.5", "ts-optchain": "0.1.8" }, "dependencies": { + "@openstapps/core-tools": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@openstapps/core-tools/-/core-tools-0.16.0.tgz", + "integrity": "sha512-agcRBjavO7TlgjDvoJEnUgXzDOW5w6us2VENZ73DUC+8QKc9okdjDuVIMy2oBrKGgOxJBg0G/xKqQN9CARqqAg==", + "requires": { + "@krlwlfrt/async-pool": "0.3.0", + "@openstapps/logger": "0.5.0", + "@types/glob": "7.1.1", + "@types/got": "9.6.9", + "@types/json-schema": "7.0.6", + "@types/mustache": "4.0.0", + "@types/node": "10.17.21", + "ajv": "6.11.0", + "better-ajv-errors": "0.6.7", + "chai": "4.2.0", + "commander": "4.1.1", + "deepmerge": "4.2.2", + "del": "5.1.0", + "flatted": "2.0.1", + "glob": "7.1.6", + "got": "10.5.5", + "humanize-string": "2.1.0", + "json-schema": "0.2.5", + "mustache": "4.0.0", + "plantuml-encoder": "1.4.0", + "toposort": "2.0.2", + "ts-json-schema-generator": "0.60.0", + "ts-node": "8.6.2", + "typedoc": "0.18.0", + "typescript": "3.8.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.21.tgz", + "integrity": "sha512-PQKsydPxYxF1DsAFWmunaxd3sOi3iMt6Zmx/tgaagHYmwJ/9cRH91hQkeJZaUGWbvn0K5HlSVEXkn5U/llWPpQ==" + } + } + }, + "@sindresorhus/is": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-1.2.0.tgz", + "integrity": "sha512-mwhXGkRV5dlvQc4EgPDxDxO6WuMBVymGFd1CA+2Y+z5dG9MNspoQ+AWjl/Ld1MnpCL8AKbosZlDVohqcIwuWsw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/got": { + "version": "9.6.9", + "resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.9.tgz", + "integrity": "sha512-w+ZE+Ovp6fM+1sHwJB7RN3f3pTJHZkyABuULqbtknqezQyWadFEp5BzOXaZzRqAw2md6/d3ybxQJt+BNgpvzOg==", + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, "@types/node": { - "version": "10.17.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.14.tgz", - "integrity": "sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw==" + "version": "10.17.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.44.tgz", + "integrity": "sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "10.5.5", + "resolved": "https://registry.npmjs.org/got/-/got-10.5.5.tgz", + "integrity": "sha512-B13HHkCkTA7KxyxTrFoZfrurBX1fZxjMTKpmIfoVzh0Xfs9aZV7xEfI6EKuERQOIPbomh5LE4xDkfK6o2VXksw==", + "requires": { + "@sindresorhus/is": "^1.0.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", + "lowercase-keys": "^2.0.0", + "mimic-response": "^2.0.0", + "p-cancelable": "^2.0.0", + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.9.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "to-readable-stream": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", + "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==" + }, + "ts-node": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", + "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + } + }, + "type-fest": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.9.0.tgz", + "integrity": "sha512-j55pzONIdg7rdtJTRZPKIbV0FosUqYdhHK1aAYJIrUvejv1VVyBokrILE8KQDT4emW/1Ev9tx+yZG+AxuSBMmA==" } } }, "@openstapps/core-tools": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@openstapps/core-tools/-/core-tools-0.16.0.tgz", - "integrity": "sha512-agcRBjavO7TlgjDvoJEnUgXzDOW5w6us2VENZ73DUC+8QKc9okdjDuVIMy2oBrKGgOxJBg0G/xKqQN9CARqqAg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@openstapps/core-tools/-/core-tools-0.17.0.tgz", + "integrity": "sha512-YL0++bjhBEVb5qMHYdzCUgUb23ZGbqgkpCIHMUY5CuEZb2TUvP5HO1s1/7s/VpVVzE8lSqFqPGmeSBpHgs0K3g==", "requires": { "@krlwlfrt/async-pool": "0.3.0", "@openstapps/logger": "0.5.0", @@ -729,17 +906,6 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } - }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -775,22 +941,6 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } - } - }, "keyv": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", @@ -838,33 +988,6 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.9.0.tgz", "integrity": "sha512-j55pzONIdg7rdtJTRZPKIbV0FosUqYdhHK1aAYJIrUvejv1VVyBokrILE8KQDT4emW/1Ev9tx+yZG+AxuSBMmA==" - }, - "typedoc": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.18.0.tgz", - "integrity": "sha512-UgDQwapCGQCCdYhEQzQ+kGutmcedklilgUGf62Vw6RdI29u6FcfAXFQfRTiJEbf16aK3YnkB20ctQK1JusCRbA==", - "requires": { - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "highlight.js": "^10.0.0", - "lodash": "^4.17.15", - "lunr": "^2.3.8", - "marked": "^1.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shelljs": "^0.8.4", - "typedoc-default-themes": "^0.10.2" - } - }, - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" - }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" } } }, @@ -2425,9 +2548,9 @@ "dev": true }, "core-js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", - "integrity": "sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.0.tgz", + "integrity": "sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==" }, "core-util-is": { "version": "1.0.2", @@ -3512,9 +3635,9 @@ } }, "http-status-codes": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.2.tgz", - "integrity": "sha512-zpZ1nBcoR0j1FLQ7xbXXBy1z/yUfAi+0a5IZBoZnmOseYkaljdzQ17ZeVXFlK23IbLxMJn6aWI0uU92DQQrG0g==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", + "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==" }, "humanize-string": { "version": "2.1.0", @@ -6111,7 +6234,6 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.18.0.tgz", "integrity": "sha512-UgDQwapCGQCCdYhEQzQ+kGutmcedklilgUGf62Vw6RdI29u6FcfAXFQfRTiJEbf16aK3YnkB20ctQK1JusCRbA==", - "dev": true, "requires": { "fs-extra": "^9.0.1", "handlebars": "^4.7.6", @@ -6129,7 +6251,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -6141,7 +6262,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -6150,16 +6270,14 @@ "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" } } }, @@ -6174,8 +6292,7 @@ "typescript": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" }, "tz-offset": { "version": "0.0.1", diff --git a/package.json b/package.json index f25ab788..1866b04b 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ }, "dependencies": { "@elastic/elasticsearch": "5.6.20", - "@openstapps/core": "0.38.0", - "@openstapps/core-tools": "0.16.0", + "@openstapps/core": "0.41.0", + "@openstapps/core-tools": "0.17.0", "@openstapps/logger": "0.5.0", "@types/node": "10.14.12", "commander": "2.20.0", diff --git a/src/storage/elasticsearch/common.ts b/src/storage/elasticsearch/common.ts index b12a1a6b..8ad3c5c3 100644 --- a/src/storage/elasticsearch/common.ts +++ b/src/storage/elasticsearch/common.ts @@ -283,6 +283,53 @@ export interface ESTermFilter { }; } +export interface ESGenericRange { + /** + * Greater than field + */ + gt?: T; + + /** + * Greater or equal than field + */ + gte?: T; + + /** + * Less than field + */ + lt?: T; + + /** + * Less or equal than field + */ + lte?: T; +} + +interface ESGenericRangeFilter> { + /** + * Range filter definition + */ + range: { + [fieldName: string]: T; + }; +} + +export interface ESDateRange extends ESGenericRange { + /** + * Optional date format override + */ + format?: string; + + /** + * Optional timezone specifier + */ + time_zone?: string; +} + +export type ESNumericRangeFilter = ESGenericRangeFilter>; +export type ESDateRangeFilter = ESGenericRangeFilter; +export type ESRangeFilter = ESNumericRangeFilter | ESDateRangeFilter; + /** * Checks if the parameter is of type ESTermsFilter * @param agg the value to check @@ -435,9 +482,9 @@ export interface ESFunctionScoreQueryFunction { } /** - * An elasticsearch ducet sort + * An elasticsearch generic sort */ -export interface ESDucetSort { +export interface ESGenericSort { [field: string]: string; } diff --git a/src/storage/elasticsearch/query.ts b/src/storage/elasticsearch/query.ts index 1211e3ec..975f355b 100644 --- a/src/storage/elasticsearch/query.ts +++ b/src/storage/elasticsearch/query.ts @@ -26,13 +26,19 @@ import { SCThingsField, SCThingType, } from '@openstapps/core'; -import {ElasticsearchConfig, ScriptSort} from './common'; +import { + ElasticsearchConfig, + ESDateRange, + ESDateRangeFilter, ESGenericRange, ESNumericRangeFilter, + ESRangeFilter, + ScriptSort, +} from './common'; import { ESBooleanFilter, ESBooleanFilterArguments, - ESDucetSort, ESFunctionScoreQuery, ESFunctionScoreQueryFunction, + ESGenericSort, ESGeoDistanceFilter, ESGeoDistanceFilterArguments, ESGeoDistanceSort, @@ -89,7 +95,8 @@ export function buildBooleanFilter(booleanFilter: SCSearchBooleanFilter): ESBool * Converts Array of Filters to elasticsearch query-syntax * @param filter A search filter for the retrieval of the data */ -export function buildFilter(filter: SCSearchFilter): ESTermFilter | ESGeoDistanceFilter | ESBooleanFilter { +export function buildFilter(filter: SCSearchFilter): + ESTermFilter | ESGeoDistanceFilter | ESBooleanFilter | ESRangeFilter { switch (filter.type) { case 'value': @@ -109,8 +116,7 @@ export function buildFilter(filter: SCSearchFilter): ESTermFilter | ESGeoDistanc }; } = {}; startRangeFilter[filter.arguments.fromField] = { - lte: typeof filter.arguments.time !== 'undefined' - ? filter.arguments.time : 'now', + lte: filter.arguments.time ?? 'now', }; const endRangeFilter: { @@ -122,8 +128,7 @@ export function buildFilter(filter: SCSearchFilter): ESTermFilter | ESGeoDistanc }; } = {}; endRangeFilter[filter.arguments.toField] = { - gte: typeof filter.arguments.time !== 'undefined' - ? filter.arguments.time : 'now', + gte: filter.arguments.time ?? 'now', }; return { @@ -176,6 +181,42 @@ export function buildFilter(filter: SCSearchFilter): ESTermFilter | ESGeoDistanc return { bool: buildBooleanFilter(filter), }; + case 'numeric range': + const numericRangeObject: ESGenericRange = {}; + if (filter.arguments.bounds.lowerBound?.mode === 'exclusive') { + numericRangeObject.gt = filter.arguments.bounds.lowerBound.limit; + } else if (filter.arguments.bounds.lowerBound?.mode === 'inclusive') { + numericRangeObject.gte = filter.arguments.bounds.lowerBound.limit; + } + if (filter.arguments.bounds.upperBound?.mode === 'exclusive') { + numericRangeObject.lt = filter.arguments.bounds.upperBound.limit; + } else if (filter.arguments.bounds.upperBound?.mode === 'inclusive') { + numericRangeObject.lte = filter.arguments.bounds.upperBound.limit; + } + + const numericRangeFilter: ESNumericRangeFilter = {range: {}}; + numericRangeFilter.range[filter.arguments.field] = numericRangeObject; + + return numericRangeFilter; + case 'date range': + const dateRangeObject: ESDateRange = {}; + if (filter.arguments.bounds.lowerBound?.mode === 'exclusive') { + dateRangeObject.lt = filter.arguments.bounds.lowerBound.limit; + } else if (filter.arguments.bounds.lowerBound?.mode === 'inclusive') { + dateRangeObject.lte = filter.arguments.bounds.lowerBound.limit; + } + if (filter.arguments.bounds.upperBound?.mode === 'exclusive') { + dateRangeObject.gt = filter.arguments.bounds.upperBound.limit; + } else if (filter.arguments.bounds.upperBound?.mode === 'inclusive') { + dateRangeObject.gte = filter.arguments.bounds.upperBound.limit; + } + dateRangeObject.format = filter.arguments.format; + dateRangeObject.time_zone = filter.arguments.timeZone; + + const dateRangeFilter: ESDateRangeFilter = {range: {}}; + dateRangeFilter.range[filter.arguments.field] = dateRangeObject; + + return dateRangeFilter; } } @@ -403,14 +444,19 @@ export function buildQuery( */ export function buildSort( sorts: SCSearchSort[], -): Array { +): Array { return sorts.map((sort) => { switch (sort.type) { - case 'ducet': - const ducetSort: ESDucetSort = {}; - ducetSort[`${sort.arguments.field}.sort`] = sort.order; + case 'generic': + const esGenericSort: ESGenericSort = {}; + esGenericSort[sort.arguments.field] = sort.order; - return ducetSort; + return esGenericSort; + case 'ducet': + const esDucetSort: ESGenericSort = {}; + esDucetSort[`${sort.arguments.field}.sort`] = sort.order; + + return esDucetSort; case 'distance': const args: ESGeoDistanceSortArguments = { mode: 'avg', diff --git a/test/storage/elasticsearch/query.spec.ts b/test/storage/elasticsearch/query.spec.ts index 572e649b..96c7545c 100644 --- a/test/storage/elasticsearch/query.spec.ts +++ b/test/storage/elasticsearch/query.spec.ts @@ -15,16 +15,18 @@ */ import { SCConfigFile, - SCSearchBooleanFilter, - SCSearchFilter, + SCSearchBooleanFilter, SCSearchDateRangeFilter, + SCSearchFilter, SCSearchNumericRangeFilter, SCSearchQuery, SCSearchSort, SCThingType } from '@openstapps/core'; -import { expect } from 'chai'; +import {expect} from 'chai'; +import {ESDateRangeFilter} from '../../../src/storage/elasticsearch/common'; +import {ESNumericRangeFilter} from '../../../src/storage/elasticsearch/common'; import {configFile} from '../../../src/common'; import { - ElasticsearchConfig, ESBooleanFilter, ESDucetSort, ESGeoDistanceFilter, + ElasticsearchConfig, ESBooleanFilter, ESGenericSort, ESGeoDistanceFilter, ESGeoDistanceSort, ESTermFilter, ScriptSort @@ -55,7 +57,7 @@ describe('Query', function () { }, type: 'boolean' }; - const booleanFilters: {[key: string]: SCSearchBooleanFilter} = { + const booleanFilters: { [key: string]: SCSearchBooleanFilter } = { and: booleanFilter, or: {...booleanFilter, arguments: {...booleanFilter.arguments, operation: 'or'}}, not: {...booleanFilter, arguments: {...booleanFilter.arguments, operation: 'not'}}, @@ -95,34 +97,34 @@ describe('Query', function () { describe('buildQuery', function () { const params: SCSearchQuery = { - query: 'mathematics', - from: 30, - size: 5, - sort: [ - { - type: 'ducet', - order: 'desc', - arguments: - { - field: 'name' - } - }, - { - type: 'ducet', - order: 'desc', - arguments: - { - field: 'categories' - } - }, - ], - filter: { - type: 'value', - arguments: { - field: 'type', - value: SCThingType.AcademicEvent - } - } + query: 'mathematics', + from: 30, + size: 5, + sort: [ + { + type: 'ducet', + order: 'desc', + arguments: + { + field: 'name' + } + }, + { + type: 'ducet', + order: 'desc', + arguments: + { + field: 'categories' + } + }, + ], + filter: { + type: 'value', + arguments: { + field: 'type', + value: SCThingType.AcademicEvent + } + } }; let esConfig: ElasticsearchConfig = { name: 'elasticsearch', @@ -143,7 +145,7 @@ describe('Query', function () { fuzziness: 'AUTO', cutoffFrequency: 0.0, tieBreaker: 0, - } + }; const config: SCConfigFile = { ...configFile }; @@ -187,7 +189,7 @@ describe('Query', function () { }); describe('buildFilter', function () { - const searchFilters: {[key: string]: SCSearchFilter} = { + const searchFilters: { [key: string]: SCSearchFilter } = { value: { type: 'value', arguments: { @@ -246,6 +248,107 @@ describe('Query', function () { expect(filter).to.be.eql(expectedFilter); }); + it('should build numeric range filters', function () { + for (const upperMode of ['inclusive', 'exclusive', null]) { + for (const lowerMode of ['inclusive', 'exclusive', null]) { + const expectedFilter: ESNumericRangeFilter = { + range: { + price: { + + } + } + } + + const rawFilter: SCSearchNumericRangeFilter = { + type: 'numeric range', + arguments: { + bounds: {}, + field: 'price' + } + }; + + const setBound = (location: 'upperBound' | 'lowerBound', bound: string | null) => { + let out: number | null = null; + if (bound != null) { + out = Math.random(); + rawFilter.arguments.bounds[location] = { + mode: bound as 'inclusive' | 'exclusive', + limit: out, + } + // @ts-ignore implicit any + expectedFilter.range.price[`${location === 'upperBound' ? 'g' : 'l'}${bound === 'inclusive' ? 'te' : 't'}`] = out; + } + } + setBound('upperBound', upperMode); + setBound('lowerBound', lowerMode); + + const filter = buildFilter(rawFilter) as ESNumericRangeFilter; + expect(filter).to.deep.equal(expectedFilter); + for (const bound of ['g', 'l']) { + // @ts-ignore implicit any + const inclusiveExists = typeof filter.range.price[`${bound}t`] !== 'undefined'; + // @ts-ignore implicit any + const exclusiveExists = typeof filter.range.price[`${bound}te`] !== 'undefined'; + + // only one should exist at the same time + expect(inclusiveExists && exclusiveExists).to.be.false + } + } + } + }); + + it('should build date range filters', function () { + for (const upperMode of ['inclusive', 'exclusive', null]) { + for (const lowerMode of ['inclusive', 'exclusive', null]) { + const expectedFilter: ESDateRangeFilter = { + range: { + price: { + format: 'thisIsADummyFormat', + time_zone: 'thisIsADummyTimeZone', + } + } + } + + const rawFilter: SCSearchDateRangeFilter = { + type: 'date range', + arguments: { + bounds: {}, + field: 'price', + format: 'thisIsADummyFormat', + timeZone: 'thisIsADummyTimeZone', + } + }; + + const setBound = (location: 'upperBound' | 'lowerBound', bound: string | null) => { + let out: string | null = null; + if (bound != null) { + out = `${location} ${bound} ${upperMode} ${lowerMode}`; + rawFilter.arguments.bounds[location] = { + mode: bound as 'inclusive' | 'exclusive', + limit: out, + } + // @ts-ignore implicit any + expectedFilter.range.price[`${location === 'upperBound' ? 'g' : 'l'}${bound === 'inclusive' ? 'te' : 't'}`] = out; + } + } + setBound('upperBound', upperMode); + setBound('lowerBound', lowerMode); + + const filter = buildFilter(rawFilter) as ESNumericRangeFilter; + expect(filter).to.deep.equal(expectedFilter); + for (const bound of ['g', 'l']) { + // @ts-ignore implicit any + const inclusiveExists = typeof filter.range.price[`${bound}t`] !== 'undefined'; + // @ts-ignore implicit any + const exclusiveExists = typeof filter.range.price[`${bound}te`] !== 'undefined'; + + // only one should exist at the same time + expect(inclusiveExists && exclusiveExists).to.be.false + } + } + } + }); + it('should build availability filter', function () { const filter = buildFilter(searchFilters.availability); const expectedFilter: ESBooleanFilter = { @@ -366,7 +469,7 @@ describe('Query', function () { must_not: [], should: [] } - } + }; expect(filter).to.be.eql(expectedFilter); }); @@ -381,6 +484,13 @@ describe('Query', function () { field: 'name' }, }, + { + type: 'generic', + order: 'desc', + arguments: { + field: 'name', + } + }, { type: 'distance', order: 'desc', @@ -398,11 +508,14 @@ describe('Query', function () { } }, ]; - let sorts: Array = []; - const expectedSorts: {[key: string]: ESDucetSort | ESGeoDistanceSort | ScriptSort} = { + let sorts: Array = []; + const expectedSorts: { [key: string]: ESGenericSort | ESGeoDistanceSort | ScriptSort } = { ducet: { 'name.sort': 'desc' }, + generic: { + 'name': 'desc' + }, distance: { _geo_distance: { mode: 'avg', @@ -430,12 +543,19 @@ describe('Query', function () { expect(sorts[0]).to.be.eql(expectedSorts.ducet); }); + it('should build generic sort', function () { + expect(sorts[1]).to.be.eql(expectedSorts.generic); + }) + it('should build distance sort', function () { - expect(sorts[1]).to.be.eql(expectedSorts.distance); + expect(sorts[2]).to.be.eql(expectedSorts.distance); }); it('should build price sort', function () { - const priceSortNoScript = {...sorts[2], _script: {...(sorts[2] as ScriptSort)._script, script: (expectedSorts.price as ScriptSort)._script.script}} + const priceSortNoScript = { + ...sorts[3], + _script: {...(sorts[3] as ScriptSort)._script, script: (expectedSorts.price as ScriptSort)._script.script} + }; expect(priceSortNoScript).to.be.eql(expectedSorts.price); }); });