From dbebaff32b230acb8481d7b78d4f1a06001a3eec Mon Sep 17 00:00:00 2001 From: Karan Hotchandani <33024356+karanh37@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:24:04 +0530 Subject: [PATCH] Appconfig fixes (#20960) * fix initial config for json logic query builder * show alert for application * hide hidden fields from doc --- .../resources/ui/generateApplicationDocs.js | 12 ++ .../Applications/AutoPilotApplication.md | 14 -- .../Applications/DataInsightsApplication.md | 7 - .../AdvanceSearchProvider.component.tsx | 8 +- .../QueryBuilderWidget/QueryBuilderWidget.tsx | 96 +++++----- .../src/constants/AdvancedSearch.constants.ts | 34 ---- .../ui/src/utils/AdvancedSearchUtils.test.tsx | 60 ++++++ .../ui/src/utils/AdvancedSearchUtils.tsx | 36 ++++ .../ui/src/utils/QueryBuilderUtils.test.ts | 180 +++++++++++++++++- .../ui/src/utils/QueryBuilderUtils.tsx | 52 +++++ 10 files changed, 395 insertions(+), 104 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/generateApplicationDocs.js b/openmetadata-ui/src/main/resources/ui/generateApplicationDocs.js index 0b7d02089e2..e6d7d165a1c 100644 --- a/openmetadata-ui/src/main/resources/ui/generateApplicationDocs.js +++ b/openmetadata-ui/src/main/resources/ui/generateApplicationDocs.js @@ -17,6 +17,12 @@ const path = require('path'); const SCHEMA_DIR = path.join(__dirname, './src/utils/ApplicationSchemas'); const DOCS_DIR = path.join(__dirname, './public/locales/en-US/Applications'); +const IGNORE_FIELDS = [ + 'moduleConfiguration.dataAssets.serviceFilter', + 'entityLink', + 'type', +]; + const resolveRef = (schema, ref) => { const path = ref.split('/').slice(1); let current = schema; @@ -27,6 +33,12 @@ const resolveRef = (schema, ref) => { }; const processProperty = (key, prop, schema) => { + // Skip if the key is in IGNORE_FIELDS + const currentKey = key.split('.').pop(); + if (IGNORE_FIELDS.includes(currentKey)) { + return ''; + } + let markdown = `$$section\n`; markdown += `### ${prop.title || key} $(id="${key}")\n\n`; diff --git a/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/AutoPilotApplication.md b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/AutoPilotApplication.md index e4c191419a9..5aba9493fa2 100644 --- a/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/AutoPilotApplication.md +++ b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/AutoPilotApplication.md @@ -2,23 +2,9 @@ Configuration for the AutoPilot Application. -$$section -### Application Type $(id="type") - -Application Type - -$$ - $$section ### Active $(id="active") Whether the AutoPilot Workflow should be active or not. -$$ - -$$section -### Service Entity Link $(id="entityLink") - -Service Entity Link for which to trigger the application. - $$ \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/DataInsightsApplication.md b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/DataInsightsApplication.md index 5c6f1e4c278..1c4e9701432 100644 --- a/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/DataInsightsApplication.md +++ b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/DataInsightsApplication.md @@ -2,13 +2,6 @@ This schema defines configuration for the Data Insights Application. -$$section -### Application Type $(id="type") - -Application Type - -$$ - $$section ### batchSize $(id="batchSize") diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx index d3960a5ea82..bf03040a2c0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx @@ -30,13 +30,13 @@ import { ValueSource, } from 'react-awesome-query-builder'; import { useHistory, useParams } from 'react-router-dom'; -import { emptyJsonTree } from '../../../constants/AdvancedSearch.constants'; import { SearchIndex } from '../../../enums/search.enum'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import { TabsInfoData } from '../../../pages/ExplorePage/ExplorePage.interface'; import { getAllCustomProperties } from '../../../rest/metadataTypeAPI'; import advancedSearchClassBase from '../../../utils/AdvancedSearchClassBase'; import { + getEmptyJsonTree, getTierOptions, getTreeConfig, } from '../../../utils/AdvancedSearchUtils'; @@ -114,7 +114,7 @@ export const AdvanceSearchProvider = ({ const [initialised, setInitialised] = useState(false); const defaultTree = useMemo( - () => QbUtils.checkTree(QbUtils.loadTree(emptyJsonTree), config), + () => QbUtils.checkTree(QbUtils.loadTree(getEmptyJsonTree()), config), [] ); @@ -197,7 +197,9 @@ export const AdvanceSearchProvider = ({ }; const handleReset = useCallback(() => { - setTreeInternal(QbUtils.checkTree(QbUtils.loadTree(emptyJsonTree), config)); + setTreeInternal( + QbUtils.checkTree(QbUtils.loadTree(getEmptyJsonTree()), config) + ); setQueryFilter(undefined); setSQLQuery(''); }, [config]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index 60ffd9263f1..84815902c5f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -35,19 +35,23 @@ import { Query, Utils as QbUtils, } from 'react-awesome-query-builder'; +import { + EntityFields, + EntityReferenceFields, +} from '../../../../../../enums/AdvancedSearch.enum'; import { EntityType } from '../../../../../../enums/entity.enum'; import { SearchIndex } from '../../../../../../enums/search.enum'; -import { - EsBoolQuery, - QueryFieldInterface, -} from '../../../../../../pages/ExplorePage/ExplorePage.interface'; +import { QueryFilterInterface } from '../../../../../../pages/ExplorePage/ExplorePage.interface'; import { searchQuery } from '../../../../../../rest/searchAPI'; +import { getEmptyJsonTree } from '../../../../../../utils/AdvancedSearchUtils'; import { elasticSearchFormat, elasticSearchFormatForJSONLogic, } from '../../../../../../utils/QueryBuilderElasticsearchFormatUtils'; import { + addEntityTypeFilter, elasticsearchToJsonLogic, + getEntityTypeAggregationFilter, getJsonTreeFromQueryFilter, jsonLogicToElasticsearch, READONLY_SETTINGS, @@ -82,29 +86,46 @@ const QueryBuilderWidget: FC = ({ const outputType = schema?.outputType ?? SearchOutputType.ElasticSearch; const isSearchIndexUpdatedInContext = searchIndexFromContext === searchIndex; const [initDone, setInitDone] = useState(false); + const [queryURL, setQueryURL] = useState(''); const fetchEntityCount = useCallback( async (queryFilter: Record) => { + const qFilter = getEntityTypeAggregationFilter( + queryFilter as unknown as QueryFilterInterface, + entityType + ); + + const tree = QbUtils.checkTree( + QbUtils.loadTree(getJsonTreeFromQueryFilter(qFilter) as JsonTree), + config + ); + + const queryFilterString = !isEmpty(tree) + ? Qs.stringify({ queryFilter: JSON.stringify(tree) }) + : ''; + + setQueryURL(`${getExplorePath({})}${queryFilterString}`); + try { setIsCountLoading(true); const res = await searchQuery({ query: '', pageNumber: 0, pageSize: 0, - queryFilter, + queryFilter: qFilter as unknown as Record, searchIndex: SearchIndex.ALL, includeDeleted: false, trackTotalHits: true, fetchSource: false, }); setSearchResults(res.hits.total.value ?? 0); - } catch (_) { + } catch { // silent fail } finally { setIsCountLoading(false); } }, - [] + [entityType] ); const debouncedFetchEntityCount = useMemo( @@ -112,14 +133,6 @@ const QueryBuilderWidget: FC = ({ [fetchEntityCount] ); - const queryURL = useMemo(() => { - const queryFilterString = !isEmpty(treeInternal) - ? Qs.stringify({ queryFilter: JSON.stringify(treeInternal) }) - : ''; - - return `${getExplorePath({})}${queryFilterString}`; - }, [treeInternal]); - const showFilteredResourceCount = useMemo( () => outputType === SearchOutputType.ElasticSearch && @@ -138,31 +151,14 @@ const QueryBuilderWidget: FC = ({ query: data, }; if (data) { - if (entityType !== EntityType.ALL) { - // Scope the search to the passed entity type - if ( - Array.isArray( - ((qFilter.query as QueryFieldInterface)?.bool as EsBoolQuery) - ?.must - ) - ) { - ( - (qFilter.query as QueryFieldInterface)?.bool - ?.must as QueryFieldInterface[] - )?.push({ - bool: { - must: [ - { - term: { - entityType: entityType, - }, - }, - ], - }, - }); - } - } - debouncedFetchEntityCount(qFilter); + const qFilterWithEntityType = addEntityTypeFilter( + qFilter as unknown as QueryFilterInterface, + entityType + ); + + debouncedFetchEntityCount( + qFilterWithEntityType as unknown as Record + ); } onChange(!isEmpty(data) ? JSON.stringify(qFilter) : ''); @@ -187,22 +183,22 @@ const QueryBuilderWidget: FC = ({ const loadDefaultValueInTree = useCallback(() => { if (!isEmpty(value)) { + const parsedValue = JSON.parse(value ?? '{}'); if (outputType === SearchOutputType.ElasticSearch) { const parsedTree = getJsonTreeFromQueryFilter( - JSON.parse(value || ''), + parsedValue, config.fields ) as JsonTree; if (Object.keys(parsedTree).length > 0) { const tree = QbUtils.checkTree(QbUtils.loadTree(parsedTree), config); onTreeUpdate(tree, config); + // Fetch count for default value + debouncedFetchEntityCount(parsedValue); } } else { try { - const query = jsonLogicToElasticsearch( - JSON.parse(value || ''), - config.fields - ); + const query = jsonLogicToElasticsearch(parsedValue, config.fields); const updatedQ = { query: query, }; @@ -225,6 +221,16 @@ const QueryBuilderWidget: FC = ({ console.log(e); } } + } else { + const emptyJsonTree = getEmptyJsonTree( + outputType === SearchOutputType.JSONLogic + ? EntityReferenceFields.OWNERS + : EntityFields.OWNERS + ); + onTreeUpdate( + QbUtils.checkTree(QbUtils.loadTree(emptyJsonTree), config), + config + ); } setInitDone(true); }, [config, value, outputType]); diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts index 9a50b3aca50..e65b13669a6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts @@ -12,7 +12,6 @@ */ import { t } from 'i18next'; -import { JsonTree, Utils as QbUtils } from 'react-awesome-query-builder'; import { EntityFields } from '../enums/AdvancedSearch.enum'; import { SearchIndex } from '../enums/search.enum'; @@ -304,39 +303,6 @@ export const RANGE_FIELD_OPERATORS = ['between', 'not_between']; export const LIST_VALUE_OPERATORS = ['select_equals', 'select_not_equals']; -/** - * Generates a query builder tree with a group containing an empty rule - */ -export const emptyJsonTree: JsonTree = { - id: QbUtils.uuid(), - type: 'group', - properties: { - conjunction: 'AND', - not: false, - }, - children1: { - [QbUtils.uuid()]: { - type: 'group', - properties: { - conjunction: 'AND', - not: false, - }, - children1: { - [QbUtils.uuid()]: { - type: 'rule', - properties: { - // owner is common field , so setting owner as default field here - field: EntityFields.OWNERS, - operator: null, - value: [], - valueSrc: ['value'], - }, - }, - }, - }, - }, -}; - export const MISC_FIELDS = ['owner.displayName', 'tags.tagFQN']; export const OWNER_QUICK_FILTER_DEFAULT_OPTIONS_KEY = 'displayName.keyword'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx index 1f8551fd8f9..1e2ee4a22c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx @@ -12,10 +12,12 @@ */ import { SearchDropdownOption } from '../components/SearchDropdown/SearchDropdown.interface'; +import { EntityFields } from '../enums/AdvancedSearch.enum'; import { SearchIndex } from '../enums/search.enum'; import { getChartsOptions, getColumnsOptions, + getEmptyJsonTree, getOptionsFromAggregationBucket, getSchemaFieldOptions, getSearchDropdownLabels, @@ -45,6 +47,13 @@ import { mockShortOptionsArray, } from './mocks/AdvancedSearchUtils.mock'; +// Mock QbUtils +jest.mock('react-awesome-query-builder', () => ({ + Utils: { + uuid: jest.fn().mockReturnValue('test-uuid'), + }, +})); + describe('AdvancedSearchUtils tests', () => { it('Function getSearchDropdownLabels should return menuItems for passed options', () => { const resultMenuItems = getSearchDropdownLabels(mockOptionsArray, true); @@ -215,4 +224,55 @@ describe('AdvancedSearchUtils tests', () => { { count: 3, key: 'chart', label: 'chart' }, ]); }); + + describe('getEmptyJsonTree', () => { + it('should return a default JsonTree structure with OWNERS as the default field', () => { + const result = getEmptyJsonTree(); + + const expected = { + id: 'test-uuid', + type: 'group', + properties: { + conjunction: 'AND', + not: false, + }, + children1: { + 'test-uuid': { + type: 'group', + properties: { + conjunction: 'AND', + not: false, + }, + children1: { + 'test-uuid': { + type: 'rule', + properties: { + field: EntityFields.OWNERS, + operator: null, + value: [], + valueSrc: ['value'], + }, + }, + }, + }, + }, + }; + + expect(result).toEqual(expected); + }); + + it('should use the provided field when passed as parameter', () => { + const customField = EntityFields.TAG; + const result = getEmptyJsonTree(customField); + + const children1 = result.children1 as Record< + string, + { children1: Record } + >; + + expect( + children1['test-uuid'].children1['test-uuid'].properties.field + ).toEqual(customField); + }); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx index 569b4a74b7d..d58d3da6ae2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -18,8 +18,10 @@ import { isArray, isEmpty, toLower } from 'lodash'; import React from 'react'; import { AsyncFetchListValues, + JsonTree, ListValues, RenderSettings, + Utils as QbUtils, } from 'react-awesome-query-builder'; import { ReactComponent as IconDeleteColored } from '../assets/svg/ic-delete-colored.svg'; import ProfilePicture from '../components/common/ProfilePicture/ProfilePicture'; @@ -33,6 +35,7 @@ import { LINEAGE_DROPDOWN_ITEMS, } from '../constants/AdvancedSearch.constants'; import { NOT_INCLUDE_AGGREGATION_QUICK_FILTER } from '../constants/explore.constants'; +import { EntityFields } from '../enums/AdvancedSearch.enum'; import { EntityType } from '../enums/entity.enum'; import { SearchIndex } from '../enums/search.enum'; import { @@ -410,3 +413,36 @@ export const getCustomPropertyAdvanceSearchEnumOptions = ( return acc; }, {}); }; + +export const getEmptyJsonTree = ( + defaultField: string = EntityFields.OWNERS +): JsonTree => { + return { + id: QbUtils.uuid(), + type: 'group', + properties: { + conjunction: 'AND', + not: false, + }, + children1: { + [QbUtils.uuid()]: { + type: 'group', + properties: { + conjunction: 'AND', + not: false, + }, + children1: { + [QbUtils.uuid()]: { + type: 'rule', + properties: { + field: defaultField, + operator: null, + value: [], + valueSrc: ['value'], + }, + }, + }, + }, + }, + }; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts index d63e4f3fb11..6df0622210c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts @@ -11,8 +11,14 @@ * limitations under the License. */ import { Fields } from 'react-awesome-query-builder'; -import { QueryFilterInterface } from '../pages/ExplorePage/ExplorePage.interface'; +import { EntityType } from '../enums/entity.enum'; import { + QueryFieldInterface, + QueryFilterInterface, +} from '../pages/ExplorePage/ExplorePage.interface'; +import { + addEntityTypeFilter, + getEntityTypeAggregationFilter, getJsonTreeFromQueryFilter, resolveFieldType, } from './QueryBuilderUtils'; @@ -152,3 +158,175 @@ describe('resolveFieldType', () => { expect(resolveFieldType(undefined, 'name')).toBe(''); }); }); + +describe('addEntityTypeFilter', () => { + const baseQueryFilter: QueryFilterInterface = { + query: { + bool: { + must: [ + { + bool: { + must: [ + { + term: { + field1: 'value1', + }, + }, + ], + }, + }, + ], + }, + }, + }; + + it('should return the original filter when entityType is ALL', () => { + const result = addEntityTypeFilter({ ...baseQueryFilter }, EntityType.ALL); + + expect(result).toEqual(baseQueryFilter); + }); + + it('should add entity type filter for non-ALL entity types', () => { + const result = addEntityTypeFilter( + { ...baseQueryFilter }, + EntityType.TABLE + ); + + // Assert the must array exists and has correct length + expect(result.query?.bool?.must).toBeDefined(); + + const mustArray = result.query?.bool?.must as QueryFieldInterface[]; + + expect(Array.isArray(mustArray)).toBe(true); + expect(mustArray).toHaveLength(2); + + // Assert the entity type filter is added correctly + expect(mustArray[1]).toEqual({ + bool: { + must: [ + { + term: { + entityType: EntityType.TABLE, + }, + }, + ], + }, + }); + }); + + it('should handle undefined must array gracefully', () => { + const queryFilter: QueryFilterInterface = { + query: { + bool: {}, + }, + }; + const result = addEntityTypeFilter(queryFilter, EntityType.TABLE); + + expect(result).toEqual(queryFilter); + }); + + it('should handle empty query gracefully', () => { + const queryFilter = {} as QueryFilterInterface; + const result = addEntityTypeFilter(queryFilter, EntityType.TABLE); + + expect(result).toEqual(queryFilter); + }); +}); + +describe('getEntityTypeAggregationFilter', () => { + const baseQueryFilter: QueryFilterInterface = { + query: { + bool: { + must: [ + { + bool: { + must: [ + { + term: { + field1: 'value1', + }, + }, + ], + }, + }, + ], + }, + }, + }; + + it('should add entity type to the first must block', () => { + const result = getEntityTypeAggregationFilter( + { ...baseQueryFilter }, + EntityType.TABLE + ); + + // Assert the must array exists + expect(result.query?.bool?.must).toBeDefined(); + + const mustArray = result.query?.bool?.must as QueryFieldInterface[]; + + expect(Array.isArray(mustArray)).toBe(true); + expect(mustArray.length).toBeGreaterThan(0); + + // Get the first must block and assert its structure + const firstMustBlock = mustArray[0]; + const mustBlockArray = firstMustBlock.bool?.must as QueryFieldInterface[]; + + expect(mustBlockArray).toBeDefined(); + expect(Array.isArray(mustBlockArray)).toBe(true); + expect(mustBlockArray).toHaveLength(2); + + // Assert the entity type filter is added correctly + expect(mustBlockArray[1]).toEqual({ + term: { + entityType: EntityType.TABLE, + }, + }); + }); + + it('should handle undefined must array in first block gracefully', () => { + const queryFilter: QueryFilterInterface = { + query: { + bool: { + must: [ + { + bool: {}, + }, + ], + }, + }, + }; + const result = getEntityTypeAggregationFilter( + queryFilter, + EntityType.TABLE + ); + + expect(result).toEqual(queryFilter); + }); + + it('should handle empty must array gracefully', () => { + const queryFilter: QueryFilterInterface = { + query: { + bool: { + must: [], + }, + }, + }; + const result = getEntityTypeAggregationFilter( + queryFilter, + EntityType.TABLE + ); + + expect(result).toEqual(queryFilter); + }); + + it('should handle empty query gracefully', () => { + const queryFilter = {} as QueryFilterInterface; + const result = getEntityTypeAggregationFilter( + queryFilter, + EntityType.TABLE + ); + + expect(result).toEqual(queryFilter); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx index e7f65dc4be1..cb925cec156 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx @@ -21,6 +21,7 @@ import { RenderSettings, } from 'react-awesome-query-builder'; import { EntityReferenceFields } from '../enums/AdvancedSearch.enum'; +import { EntityType } from '../enums/entity.enum'; import { EsBoolQuery, EsExistsQuery, @@ -801,3 +802,54 @@ export const jsonLogicToElasticsearch = ( throw new Error('Unsupported JSON Logic format'); }; + +/** + * Adds entity type filter to the query filter if entity type is specified + * @param qFilter Query filter to add entity type to + * @param entityType Entity type to filter by + * @returns Updated query filter with entity type + */ +export const addEntityTypeFilter = ( + qFilter: QueryFilterInterface, + entityType: string +): QueryFilterInterface => { + if (entityType === EntityType.ALL) { + return qFilter; + } + + if (Array.isArray((qFilter.query?.bool as EsBoolQuery)?.must)) { + (qFilter.query?.bool?.must as QueryFieldInterface[])?.push({ + bool: { + must: [ + { + term: { + entityType: entityType, + }, + }, + ], + }, + }); + } + + return qFilter; +}; + +export const getEntityTypeAggregationFilter = ( + qFilter: QueryFilterInterface, + entityType: string +): QueryFilterInterface => { + if (Array.isArray((qFilter.query?.bool as EsBoolQuery)?.must)) { + const firstMustBlock = ( + qFilter.query?.bool?.must as QueryFieldInterface[] + )[0]; + if (firstMustBlock?.bool?.must) { + (firstMustBlock.bool.must as QueryFieldInterface[]).push({ + term: { + entityType: entityType, + }, + }); + } + } + + return qFilter; +};