From d691230e5a26c35ec826272b49263a2ef0e58fca Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 17:29:27 -0600 Subject: [PATCH 1/6] fix: creating combined "range" query for Elasticsearch Elasticsearch range queries should be combined like this: { range: { field: { gte: 10, lte: 20 } } } rather than two separate range queries Per this doc: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html --- src/server/es/__tests__/filter.test.js | 15 +++++++++++++ src/server/es/filter.js | 31 ++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/server/es/__tests__/filter.test.js b/src/server/es/__tests__/filter.test.js index ea7f3c8e..376d2268 100644 --- a/src/server/es/__tests__/filter.test.js +++ b/src/server/es/__tests__/filter.test.js @@ -158,6 +158,21 @@ describe('Transfer GraphQL filter to ES filter, filter unit', () => { expect(resultESFilter3).toEqual(expectedESFilter); }); + test('could transfer graphql filter to ES filter object, range ">=" and "<=" operator', async () => { + await esInstance.initialize(); + // <=, lte, LTE + const gqlFilter1 = [{ '<=': { file_count: 20 } }, { '>=': { file_count: 10 } }]; + const gqlFilter2 = [{ lte: { file_count: 20 } }, { gte: { file_count: 10 } }]; + const gqlFilter3 = [{ LTE: { file_count: 20 } }, { GTE: { file_count: 10 } }]; + const resultESFilter1 = getFilterObj(esInstance, esIndex, gqlFilter1); + const resultESFilter2 = getFilterObj(esInstance, esIndex, gqlFilter2); + const resultESFilter3 = getFilterObj(esInstance, esIndex, gqlFilter3); + const expectedESFilter = { range: { file_count: { lte: 20, gte: 10 } } }; + expect(resultESFilter1).toEqual(expectedESFilter); + expect(resultESFilter2).toEqual(expectedESFilter); + expect(resultESFilter3).toEqual(expectedESFilter); + }); + test('could transfer graphql filter to ES filter object, "search" operator', async () => { await esInstance.initialize(); const keyword = 'male'; diff --git a/src/server/es/filter.js b/src/server/es/filter.js index 23b6d559..fd3a90c5 100644 --- a/src/server/es/filter.js +++ b/src/server/es/filter.js @@ -18,6 +18,18 @@ const fromPathToNode = (esInstance, esIndex, path) => { return node; }; +const mergeRangeOperations = (a, b) => { + let merged = Object.assign({}, a, b); + + for (let key in merged) { + if (typeof merged[key] === 'object' && merged[key] !== null) { + merged[key] = deepMerge(a[key], b[key]); + } + } + + return merged; +} + const getNumericTextType = ( esInstance, esIndex, @@ -66,7 +78,7 @@ const getFilterItemForString = (op, pField, value, path) => { // if using missingDataAlias, we need to remove the missingDataAlias from filter values // and then add a must_not exists bool func to compensate missingDataAlias if (config.esConfig.aggregationIncludeMissingData - && value.includes(config.esConfig.missingDataAlias)) { + && value.includes(config.esConfig.missingDataAlias)) { const newValue = value.filter((element) => element !== config.esConfig.missingDataAlias); return { bool: { @@ -233,14 +245,29 @@ const getFilterObj = ( if (topLevelOpLowerCase === 'and' || topLevelOpLowerCase === 'or') { const boolConnectOp = topLevelOpLowerCase === 'and' ? 'must' : 'should'; const boolItemsList = []; + + const filterRange = []; graphqlFilterObj[topLevelOp].forEach((filterItem) => { const filterObj = getFilterObj( esInstance, esIndex, filterItem, aggsField, filterSelf, defaultAuthFilter, objPath, ); if (filterObj) { - boolItemsList.push(filterObj); + if ("range" in filterObj) { + filterRange.push(filterObj); + } else { + boolItemsList.push(filterObj); + } } }); + + if (filterRange.length === 1) { + boolItemsList.push(filterRange[0]); + } + + if (filterRange.length === 2) { + boolItemsList.push(mergeRangeOperations(filterRange[0], filterRange[1])); + } + if (boolItemsList.length === 0) { resultFilterObj = null; } else { From 789753174ea3a6ec34404163ae47679a5dcdcc95 Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 17:37:51 -0600 Subject: [PATCH 2/6] fix: test for range merge --- src/server/es/__tests__/filter.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/es/__tests__/filter.test.js b/src/server/es/__tests__/filter.test.js index 376d2268..f240ba5f 100644 --- a/src/server/es/__tests__/filter.test.js +++ b/src/server/es/__tests__/filter.test.js @@ -161,9 +161,9 @@ describe('Transfer GraphQL filter to ES filter, filter unit', () => { test('could transfer graphql filter to ES filter object, range ">=" and "<=" operator', async () => { await esInstance.initialize(); // <=, lte, LTE - const gqlFilter1 = [{ '<=': { file_count: 20 } }, { '>=': { file_count: 10 } }]; - const gqlFilter2 = [{ lte: { file_count: 20 } }, { gte: { file_count: 10 } }]; - const gqlFilter3 = [{ LTE: { file_count: 20 } }, { GTE: { file_count: 10 } }]; + const gqlFilter1 = { and: [{ '<=': { file_count: 20 } }, { '>=': { file_count: 10 } }] }; + const gqlFilter2 = { and: [{ lte: { file_count: 20 } }, { gte: { file_count: 10 } }] }; + const gqlFilter3 = { and: [{ LTE: { file_count: 20 } }, { GTE: { file_count: 10 } }] }; const resultESFilter1 = getFilterObj(esInstance, esIndex, gqlFilter1); const resultESFilter2 = getFilterObj(esInstance, esIndex, gqlFilter2); const resultESFilter3 = getFilterObj(esInstance, esIndex, gqlFilter3); From a64c13276e0c66fde5d247bb30f39d17ba226617 Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 17:41:43 -0600 Subject: [PATCH 3/6] fix: incorrect function name --- src/server/es/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/es/filter.js b/src/server/es/filter.js index fd3a90c5..f3d0889d 100644 --- a/src/server/es/filter.js +++ b/src/server/es/filter.js @@ -23,7 +23,7 @@ const mergeRangeOperations = (a, b) => { for (let key in merged) { if (typeof merged[key] === 'object' && merged[key] !== null) { - merged[key] = deepMerge(a[key], b[key]); + merged[key] = mergeRangeOperations(a[key], b[key]); } } From d96b4d4c26fc6290ef6e275585424286ce4bee35 Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 17:48:14 -0600 Subject: [PATCH 4/6] fix: test for "and" should result in different result + an extra test --- src/server/es/__tests__/filter.test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/server/es/__tests__/filter.test.js b/src/server/es/__tests__/filter.test.js index f240ba5f..f529049e 100644 --- a/src/server/es/__tests__/filter.test.js +++ b/src/server/es/__tests__/filter.test.js @@ -167,7 +167,22 @@ describe('Transfer GraphQL filter to ES filter, filter unit', () => { const resultESFilter1 = getFilterObj(esInstance, esIndex, gqlFilter1); const resultESFilter2 = getFilterObj(esInstance, esIndex, gqlFilter2); const resultESFilter3 = getFilterObj(esInstance, esIndex, gqlFilter3); - const expectedESFilter = { range: { file_count: { lte: 20, gte: 10 } } }; + const expectedESFilter = { bool: { must: [{ range: { file_count: { lte: 20, gte: 10 } } }] } }; + expect(resultESFilter1).toEqual(expectedESFilter); + expect(resultESFilter2).toEqual(expectedESFilter); + expect(resultESFilter3).toEqual(expectedESFilter); + }); + + test('could transfer graphql filter to ES filter object, range "<=" operator', async () => { + await esInstance.initialize(); + // <=, lte, LTE + const gqlFilter1 = { and: [{ '<=': { file_count: 20 } }] }; + const gqlFilter2 = { and: [{ lte: { file_count: 20 } }] }; + const gqlFilter3 = { and: [{ LTE: { file_count: 20 } }] }; + const resultESFilter1 = getFilterObj(esInstance, esIndex, gqlFilter1); + const resultESFilter2 = getFilterObj(esInstance, esIndex, gqlFilter2); + const resultESFilter3 = getFilterObj(esInstance, esIndex, gqlFilter3); + const expectedESFilter = { bool: { must: [{ range: { file_count: { lte: 20 } } }] } }; expect(resultESFilter1).toEqual(expectedESFilter); expect(resultESFilter2).toEqual(expectedESFilter); expect(resultESFilter3).toEqual(expectedESFilter); From fe5aab8f76f2b114e07e558dfb7d291c067898a2 Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 17:59:23 -0600 Subject: [PATCH 5/6] fix: eslint complaint about "for..in loops iterate over the entire prototype chain" --- src/server/es/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/es/filter.js b/src/server/es/filter.js index f3d0889d..61605a27 100644 --- a/src/server/es/filter.js +++ b/src/server/es/filter.js @@ -21,7 +21,7 @@ const fromPathToNode = (esInstance, esIndex, path) => { const mergeRangeOperations = (a, b) => { let merged = Object.assign({}, a, b); - for (let key in merged) { + for (let key in Object.keys(merged)) { if (typeof merged[key] === 'object' && merged[key] !== null) { merged[key] = mergeRangeOperations(a[key], b[key]); } From 7331f6e1af6ad21c97f1634810990c78fe868d2f Mon Sep 17 00:00:00 2001 From: Andrew Prokhorenkov Date: Mon, 12 Dec 2022 18:07:11 -0600 Subject: [PATCH 6/6] fix: use Object.keys() instead of for..in loop --- src/server/es/filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/es/filter.js b/src/server/es/filter.js index 61605a27..d02248de 100644 --- a/src/server/es/filter.js +++ b/src/server/es/filter.js @@ -21,11 +21,11 @@ const fromPathToNode = (esInstance, esIndex, path) => { const mergeRangeOperations = (a, b) => { let merged = Object.assign({}, a, b); - for (let key in Object.keys(merged)) { + Object.keys(merged).forEach(function(key) { if (typeof merged[key] === 'object' && merged[key] !== null) { merged[key] = mergeRangeOperations(a[key], b[key]); } - } + }) return merged; }