From 597881657ee969bfa7b5c21e144aae2127f17157 Mon Sep 17 00:00:00 2001 From: himanshu748 Date: Sat, 18 Apr 2026 00:22:35 +0530 Subject: [PATCH 1/2] Fixes #27482: fix number custom property between filter Prevent range operators (between/not_between) from being treated like multiselect values and ensure both bounds are passed through when building customPropertiesTyped queries. Made-with: Cursor --- .../QueryBuilderElasticsearchFormatUtils.js | 16 +++- ...eryBuilderElasticsearchFormatUtils.test.ts | 95 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js index 5be8ed853a09..a9d956f58d50 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js @@ -736,10 +736,15 @@ function buildEsRule(fieldName, value, operator, config, valueSrc) { const isUnaryOperator = op === 'is_null' || op === 'is_not_null'; const hasValue = Array.isArray(value) && value.length > 0; if (isNestedExtensionField && entityType && (hasValue || isUnaryOperator)) { + // Query Builder represents some operators (e.g. between) as nested arrays: [[from, to]]. + // For custom properties we want to pass the operator value through unchanged so the + // underlying range builder can see both bounds. + const extensionValue = hasValue ? value[0] : null; + return buildExtensionQuery( extensionPropertyName, entityType, - hasValue ? value[0] : null, + extensionValue, op, not ); @@ -882,7 +887,14 @@ export function elasticSearchFormat(tree, config, syntax = ES_6_SYNTAX) { return; } - if (value && Array.isArray(value[0])) { + // Query Builder uses nested arrays for a few cases: + // - multiselect values: [[v1, v2, ...]] + // - range operators (between): [[from, to]] + // Only expand the nested array into multiple rules for multiselect operators. + const isMultiSelectOperator = + operator?.startsWith('multiselect_') || operator?.startsWith('select_'); + + if (isMultiSelectOperator && value && Array.isArray(value[0])) { // Check if this is a multiselect equals operator that should use AND logic const useAndLogic = operator === 'multiselect_equals' || diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts new file mode 100644 index 000000000000..5548a9dec1f0 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2026 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { QbUtils } from '@react-awesome-query-builder/antd'; +import { SearchIndex } from '../enums/search.enum'; +import { SearchOutputType } from '../generated/type/searchOutputType'; +import { elasticSearchFormat } from './QueryBuilderElasticsearchFormatUtils'; +import { getTreeConfig } from './AdvancedSearchUtils'; + +describe('QueryBuilderElasticsearchFormatUtils', () => { + it('builds a bounded range query for number custom properties with between operator', () => { + const config = getTreeConfig({ + searchOutputType: SearchOutputType.ElasticSearch, + searchIndex: SearchIndex.TABLE, + isExplorePage: true, + }); + + const jsonTree = { + id: 'root', + type: 'group', + properties: { conjunction: 'AND', not: false }, + children1: { + rules: { + type: 'rule', + id: 'rules', + properties: { + field: 'extension.table.myNumberProperty', + operator: 'between', + value: [[1, 5]], + valueSrc: ['value'], + }, + }, + }, + }; + + const tree = QbUtils.checkTree(QbUtils.loadTree(jsonTree), config); + const query = elasticSearchFormat(tree, config); + + // Should apply both gte and lte bounds (and not degrade to "exists"/unbounded query) + expect(query).toEqual( + expect.objectContaining({ + bool: expect.objectContaining({ + must: expect.arrayContaining([ + expect.objectContaining({ + bool: expect.objectContaining({ + should: expect.arrayContaining([ + expect.objectContaining({ + nested: expect.objectContaining({ + path: 'customPropertiesTyped', + query: expect.objectContaining({ + bool: expect.objectContaining({ + must: expect.arrayContaining([ + { + term: { + 'customPropertiesTyped.name': 'myNumberProperty', + }, + }, + expect.objectContaining({ + range: expect.objectContaining({ + 'customPropertiesTyped.longValue': { + gte: 1, + lte: 5, + }, + }), + }), + ]), + }), + }), + }), + }), + ]), + }), + }), + { + term: { + entityType: 'table', + }, + }, + ]), + }), + }) + ); + }); +}); + From a1b05b9425dce992330c6dfa64396c7f8d56681a Mon Sep 17 00:00:00 2001 From: himanshu748 Date: Sat, 18 Apr 2026 00:41:12 +0530 Subject: [PATCH 2/2] test: use app SearchOutputType and inject custom property field Made-with: Cursor --- ...eryBuilderElasticsearchFormatUtils.test.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts index 5548a9dec1f0..79824292ec03 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.test.ts @@ -12,8 +12,9 @@ */ import { QbUtils } from '@react-awesome-query-builder/antd'; +import { NUMBER_FIELD_OPERATORS } from '../constants/AdvancedSearch.constants'; +import { SearchOutputType } from '../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; import { SearchIndex } from '../enums/search.enum'; -import { SearchOutputType } from '../generated/type/searchOutputType'; import { elasticSearchFormat } from './QueryBuilderElasticsearchFormatUtils'; import { getTreeConfig } from './AdvancedSearchUtils'; @@ -25,6 +26,24 @@ describe('QueryBuilderElasticsearchFormatUtils', () => { isExplorePage: true, }); + // Make the test deterministic: Advanced Search injects custom property subfields at runtime. + // Provide a minimal extension field so QbUtils.checkTree keeps the rule. + config.fields.extension = { + ...(config.fields.extension ?? {}), + subfields: { + ...(config.fields.extension?.subfields ?? {}), + table: { + type: '!group', + subfields: { + myNumberProperty: { + type: 'number', + operators: NUMBER_FIELD_OPERATORS, + }, + }, + }, + }, + }; + const jsonTree = { id: 'root', type: 'group',