fix the search for custom properties in advance search for some types (#19113)

* fix the search in advance search custom properties

* modify v1/metadata/types/customProperties api to get customPropertyConfig along with name and type

* added config list in the entity ref list

* fix type of duration, timestamp and other types

* minor ui changes

* remove customProperty type which are not supported for now

* added playwright test around the duration type property

* fix flaky test

---------

Co-authored-by: sonikashah <sonikashah94@gmail.com>
(cherry picked from commit 2179b43232803a9ebb7465fb1d545b887d68252c)
This commit is contained in:
Ashish Gupta 2025-01-08 11:08:34 +05:30
parent 9a63d776b5
commit cb733a2ad8
13 changed files with 376 additions and 54 deletions

View File

@ -32,7 +32,7 @@ import org.openmetadata.service.jdbi3.TypeRepository;
@Slf4j @Slf4j
public class SchemaFieldExtractor { public class SchemaFieldExtractor {
private static final Map<String, Map<String, String>> entityFieldsCache = private static final Map<String, Map<String, FieldDefinition>> entityFieldsCache =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
public SchemaFieldExtractor() { public SchemaFieldExtractor() {
@ -55,7 +55,7 @@ public class SchemaFieldExtractor {
Schema mainSchema = loadMainSchema(schemaPath, entityType, schemaUri, schemaClient); Schema mainSchema = loadMainSchema(schemaPath, entityType, schemaUri, schemaClient);
// Extract fields from the schema // Extract fields from the schema
Map<String, String> fieldTypesMap = new LinkedHashMap<>(); Map<String, FieldDefinition> fieldTypesMap = new LinkedHashMap<>();
Deque<Schema> processingStack = new ArrayDeque<>(); Deque<Schema> processingStack = new ArrayDeque<>();
Set<String> processedFields = new HashSet<>(); Set<String> processedFields = new HashSet<>();
extractFieldsFromSchema(mainSchema, "", fieldTypesMap, processingStack, processedFields); extractFieldsFromSchema(mainSchema, "", fieldTypesMap, processingStack, processedFields);
@ -75,7 +75,7 @@ public class SchemaFieldExtractor {
SchemaClient schemaClient = new CustomSchemaClient(schemaUri); SchemaClient schemaClient = new CustomSchemaClient(schemaUri);
Deque<Schema> processingStack = new ArrayDeque<>(); Deque<Schema> processingStack = new ArrayDeque<>();
Set<String> processedFields = new HashSet<>(); Set<String> processedFields = new HashSet<>();
Map<String, String> fieldTypesMap = entityFieldsCache.get(entityType); Map<String, FieldDefinition> fieldTypesMap = entityFieldsCache.get(entityType);
addCustomProperties( addCustomProperties(
typeEntity, schemaUri, schemaClient, fieldTypesMap, processingStack, processedFields); typeEntity, schemaUri, schemaClient, fieldTypesMap, processingStack, processedFields);
return convertMapToFieldList(fieldTypesMap); return convertMapToFieldList(fieldTypesMap);
@ -90,7 +90,7 @@ public class SchemaFieldExtractor {
SchemaClient schemaClient = new CustomSchemaClient(schemaUri); SchemaClient schemaClient = new CustomSchemaClient(schemaUri);
EntityUtil.Fields fieldsParam = new EntityUtil.Fields(Set.of("customProperties")); EntityUtil.Fields fieldsParam = new EntityUtil.Fields(Set.of("customProperties"));
Type typeEntity = repository.getByName(uriInfo, entityType, fieldsParam, Include.ALL, false); Type typeEntity = repository.getByName(uriInfo, entityType, fieldsParam, Include.ALL, false);
Map<String, String> fieldTypesMap = new LinkedHashMap<>(); Map<String, FieldDefinition> fieldTypesMap = new LinkedHashMap<>();
Set<String> processedFields = new HashSet<>(); Set<String> processedFields = new HashSet<>();
Deque<Schema> processingStack = new ArrayDeque<>(); Deque<Schema> processingStack = new ArrayDeque<>();
addCustomProperties( addCustomProperties(
@ -170,7 +170,7 @@ public class SchemaFieldExtractor {
private static void extractFieldsFromSchema( private static void extractFieldsFromSchema(
Schema schema, Schema schema,
String parentPath, String parentPath,
Map<String, String> fieldTypesMap, Map<String, FieldDefinition> fieldTypesMap,
Deque<Schema> processingStack, Deque<Schema> processingStack,
Set<String> processedFields) { Set<String> processedFields) {
if (processingStack.contains(schema)) { if (processingStack.contains(schema)) {
@ -206,7 +206,8 @@ public class SchemaFieldExtractor {
arraySchema, fullFieldName, fieldTypesMap, processingStack, processedFields); arraySchema, fullFieldName, fieldTypesMap, processingStack, processedFields);
} else { } else {
String fieldType = mapSchemaTypeToSimpleType(fieldSchema); String fieldType = mapSchemaTypeToSimpleType(fieldSchema);
fieldTypesMap.putIfAbsent(fullFieldName, fieldType); fieldTypesMap.putIfAbsent(
fullFieldName, new FieldDefinition(fullFieldName, fieldType, null));
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added field '{}', Type: '{}'", fullFieldName, fieldType); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, fieldType);
// Recursively process nested objects or arrays // Recursively process nested objects or arrays
@ -220,7 +221,7 @@ public class SchemaFieldExtractor {
handleArraySchema(arraySchema, parentPath, fieldTypesMap, processingStack, processedFields); handleArraySchema(arraySchema, parentPath, fieldTypesMap, processingStack, processedFields);
} else { } else {
String fieldType = mapSchemaTypeToSimpleType(schema); String fieldType = mapSchemaTypeToSimpleType(schema);
fieldTypesMap.putIfAbsent(parentPath, fieldType); fieldTypesMap.putIfAbsent(parentPath, new FieldDefinition(parentPath, fieldType, null));
LOG.debug("Added field '{}', Type: '{}'", parentPath, fieldType); LOG.debug("Added field '{}', Type: '{}'", parentPath, fieldType);
} }
} finally { } finally {
@ -231,7 +232,7 @@ public class SchemaFieldExtractor {
private static void handleReferenceSchema( private static void handleReferenceSchema(
ReferenceSchema referenceSchema, ReferenceSchema referenceSchema,
String fullFieldName, String fullFieldName,
Map<String, String> fieldTypesMap, Map<String, FieldDefinition> fieldTypesMap,
Deque<Schema> processingStack, Deque<Schema> processingStack,
Set<String> processedFields) { Set<String> processedFields) {
@ -239,7 +240,8 @@ public class SchemaFieldExtractor {
String referenceType = determineReferenceType(refUri); String referenceType = determineReferenceType(refUri);
if (referenceType != null) { if (referenceType != null) {
fieldTypesMap.putIfAbsent(fullFieldName, referenceType); fieldTypesMap.putIfAbsent(
fullFieldName, new FieldDefinition(fullFieldName, referenceType, null));
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added field '{}', Type: '{}'", fullFieldName, referenceType); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, referenceType);
if (referenceType.startsWith("array<") && referenceType.endsWith(">")) { if (referenceType.startsWith("array<") && referenceType.endsWith(">")) {
@ -255,7 +257,7 @@ public class SchemaFieldExtractor {
referredSchema, fullFieldName, fieldTypesMap, processingStack, processedFields); referredSchema, fullFieldName, fieldTypesMap, processingStack, processedFields);
} }
} else { } else {
fieldTypesMap.putIfAbsent(fullFieldName, "object"); fieldTypesMap.putIfAbsent(fullFieldName, new FieldDefinition(fullFieldName, "object", null));
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added field '{}', Type: 'object'", fullFieldName); LOG.debug("Added field '{}', Type: 'object'", fullFieldName);
extractFieldsFromSchema( extractFieldsFromSchema(
@ -270,7 +272,7 @@ public class SchemaFieldExtractor {
private static void handleArraySchema( private static void handleArraySchema(
ArraySchema arraySchema, ArraySchema arraySchema,
String fullFieldName, String fullFieldName,
Map<String, String> fieldTypesMap, Map<String, FieldDefinition> fieldTypesMap,
Deque<Schema> processingStack, Deque<Schema> processingStack,
Set<String> processedFields) { Set<String> processedFields) {
@ -282,7 +284,8 @@ public class SchemaFieldExtractor {
if (itemsReferenceType != null) { if (itemsReferenceType != null) {
String arrayFieldType = "array<" + itemsReferenceType + ">"; String arrayFieldType = "array<" + itemsReferenceType + ">";
fieldTypesMap.putIfAbsent(fullFieldName, arrayFieldType); fieldTypesMap.putIfAbsent(
fullFieldName, new FieldDefinition(fullFieldName, arrayFieldType, null));
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added field '{}', Type: '{}'", fullFieldName, arrayFieldType); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, arrayFieldType);
Schema referredItemsSchema = itemsReferenceSchema.getReferredSchema(); Schema referredItemsSchema = itemsReferenceSchema.getReferredSchema();
@ -292,7 +295,8 @@ public class SchemaFieldExtractor {
} }
} }
String arrayType = mapSchemaTypeToSimpleType(itemsSchema); String arrayType = mapSchemaTypeToSimpleType(itemsSchema);
fieldTypesMap.putIfAbsent(fullFieldName, "array<" + arrayType + ">"); fieldTypesMap.putIfAbsent(
fullFieldName, new FieldDefinition(fullFieldName, "array<" + arrayType + ">", null));
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added field '{}', Type: 'array<{}>'", fullFieldName, arrayType); LOG.debug("Added field '{}', Type: 'array<{}>'", fullFieldName, arrayType);
@ -306,7 +310,7 @@ public class SchemaFieldExtractor {
Type typeEntity, Type typeEntity,
String schemaUri, String schemaUri,
SchemaClient schemaClient, SchemaClient schemaClient,
Map<String, String> fieldTypesMap, Map<String, FieldDefinition> fieldTypesMap,
Deque<Schema> processingStack, Deque<Schema> processingStack,
Set<String> processedFields) { Set<String> processedFields) {
if (typeEntity == null || typeEntity.getCustomProperties() == null) { if (typeEntity == null || typeEntity.getCustomProperties() == null) {
@ -320,9 +324,13 @@ public class SchemaFieldExtractor {
LOG.debug("Processing custom property '{}'", fullFieldName); LOG.debug("Processing custom property '{}'", fullFieldName);
Object customPropertyConfigObj = customProperty.getCustomPropertyConfig();
if (isEntityReferenceList(propertyType)) { if (isEntityReferenceList(propertyType)) {
String referenceType = "array<entityReference>"; String referenceType = "array<entityReference>";
fieldTypesMap.putIfAbsent(fullFieldName, referenceType); FieldDefinition referenceFieldDefinition =
new FieldDefinition(fullFieldName, referenceType, customPropertyConfigObj);
fieldTypesMap.putIfAbsent(fullFieldName, referenceFieldDefinition);
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType);
@ -337,7 +345,9 @@ public class SchemaFieldExtractor {
} }
} else if (isEntityReference(propertyType)) { } else if (isEntityReference(propertyType)) {
String referenceType = "entityReference"; String referenceType = "entityReference";
fieldTypesMap.putIfAbsent(fullFieldName, referenceType); FieldDefinition referenceFieldDefinition =
new FieldDefinition(fullFieldName, referenceType, customPropertyConfigObj);
fieldTypesMap.putIfAbsent(fullFieldName, referenceFieldDefinition);
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType);
@ -351,17 +361,22 @@ public class SchemaFieldExtractor {
fullFieldName); fullFieldName);
} }
} else { } else {
fieldTypesMap.putIfAbsent(fullFieldName, propertyType); FieldDefinition entityFieldDefinition =
new FieldDefinition(fullFieldName, propertyType, customPropertyConfigObj);
fieldTypesMap.putIfAbsent(fullFieldName, entityFieldDefinition);
processedFields.add(fullFieldName); processedFields.add(fullFieldName);
LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, propertyType); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, propertyType);
} }
} }
} }
private List<FieldDefinition> convertMapToFieldList(Map<String, String> fieldTypesMap) { private List<FieldDefinition> convertMapToFieldList(Map<String, FieldDefinition> fieldTypesMap) {
List<FieldDefinition> fieldsList = new ArrayList<>(); List<FieldDefinition> fieldsList = new ArrayList<>();
for (Map.Entry<String, String> entry : fieldTypesMap.entrySet()) { for (Map.Entry<String, FieldDefinition> entry : fieldTypesMap.entrySet()) {
fieldsList.add(new FieldDefinition(entry.getKey(), entry.getValue())); FieldDefinition fieldDef = entry.getValue();
fieldsList.add(
new FieldDefinition(
fieldDef.getName(), fieldDef.getType(), fieldDef.getCustomPropertyConfig()));
} }
return fieldsList; return fieldsList;
} }
@ -622,10 +637,12 @@ public class SchemaFieldExtractor {
public static class FieldDefinition { public static class FieldDefinition {
private String name; private String name;
private String type; private String type;
private Object customPropertyConfig;
public FieldDefinition(String name, String type) { public FieldDefinition(String name, String type, Object customPropertyConfig) {
this.name = name; this.name = name;
this.type = type; this.type = type;
this.customPropertyConfig = customPropertyConfig;
} }
} }
} }

View File

@ -0,0 +1,140 @@
/*
* Copyright 2025 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 test, { expect } from '@playwright/test';
import { CUSTOM_PROPERTIES_ENTITIES } from '../../constant/customProperty';
import { GlobalSettingOptions } from '../../constant/settings';
import { SidebarItem } from '../../constant/sidebar';
import { TableClass } from '../../support/entity/TableClass';
import {
selectOption,
showAdvancedSearchDialog,
} from '../../utils/advancedSearch';
import { advanceSearchSaveFilter } from '../../utils/advancedSearchCustomProperty';
import { createNewPage, redirectToHomePage, uuid } from '../../utils/common';
import { addCustomPropertiesForEntity } from '../../utils/customProperty';
import { settingClick, sidebarClick } from '../../utils/sidebar';
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
test.describe('Advanced Search Custom Property', () => {
const table = new TableClass();
const durationPropertyName = `pwCustomPropertyDurationTest${uuid()}`;
const durationPropertyValue = 'PT1H30M';
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
await table.create(apiContext);
await afterAction();
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
await table.delete(apiContext);
await afterAction();
});
test('Create, Assign and Test Advance Search for Duration', async ({
page,
}) => {
test.slow(true);
await redirectToHomePage(page);
await test.step('Create and Assign Custom Property Value', async () => {
await settingClick(page, GlobalSettingOptions.TABLES, true);
await addCustomPropertiesForEntity({
page,
propertyName: durationPropertyName,
customPropertyData: CUSTOM_PROPERTIES_ENTITIES['entity_table'],
customType: 'Duration',
});
await table.visitEntityPage(page);
await page.getByTestId('custom_properties').click(); // Tab Click
await page
.getByTestId(`custom-property-${durationPropertyName}-card`)
.locator('svg')
.click(); // Add Custom Property Value
await page.getByTestId('duration-input').fill(durationPropertyValue);
const saveResponse = page.waitForResponse('/api/v1/tables/*');
await page.getByTestId('inline-save-btn').click();
await saveResponse;
});
await test.step('Verify Duration Type in Advance Search ', async () => {
await sidebarClick(page, SidebarItem.EXPLORE);
await showAdvancedSearchDialog(page);
const ruleLocator = page.locator('.rule').nth(0);
// Perform click on rule field
await selectOption(
page,
ruleLocator.locator('.rule--field .ant-select'),
'Custom Properties'
);
// Perform click on custom property type to filter
await selectOption(
page,
ruleLocator.locator('.rule--field .ant-select'),
durationPropertyName
);
const inputElement = ruleLocator.locator(
'.rule--widget--TEXT input[type="text"]'
);
await inputElement.fill(durationPropertyValue);
await advanceSearchSaveFilter(page, durationPropertyValue);
await expect(
page.getByTestId(
`table-data-card_${table.entityResponseData.fullyQualifiedName}`
)
).toBeVisible();
// Check around the Partial Search Value
const partialSearchValue = durationPropertyValue.slice(0, 3);
await page.getByTestId('advance-search-filter-btn').click();
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
// Perform click on operator
await selectOption(
page,
ruleLocator.locator('.rule--operator .ant-select'),
'Contains'
);
await inputElement.fill(partialSearchValue);
await advanceSearchSaveFilter(page, partialSearchValue);
await expect(
page.getByTestId(
`table-data-card_${table.entityResponseData.fullyQualifiedName}`
)
).toBeVisible();
});
});
});

View File

@ -0,0 +1,28 @@
/*
* Copyright 2025 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 { expect, Page } from '@playwright/test';
export const advanceSearchSaveFilter = async (
page: Page,
propertyValue: string
) => {
const searchResponse = page.waitForResponse(
'/api/v1/search/query?*index=dataAsset&from=0&size=10*'
);
await page.getByTestId('apply-btn').click();
const res = await searchResponse;
const json = await res.json();
expect(JSON.stringify(json)).toContain(propertyValue);
};

View File

@ -27,16 +27,15 @@ import {
JsonTree, JsonTree,
Utils as QbUtils, Utils as QbUtils,
ValueField, ValueField,
ValueSource,
} from 'react-awesome-query-builder'; } from 'react-awesome-query-builder';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { import { emptyJsonTree } from '../../../constants/AdvancedSearch.constants';
emptyJsonTree,
TEXT_FIELD_OPERATORS,
} from '../../../constants/AdvancedSearch.constants';
import { SearchIndex } from '../../../enums/search.enum'; import { SearchIndex } from '../../../enums/search.enum';
import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation';
import { TabsInfoData } from '../../../pages/ExplorePage/ExplorePage.interface'; import { TabsInfoData } from '../../../pages/ExplorePage/ExplorePage.interface';
import { getAllCustomProperties } from '../../../rest/metadataTypeAPI'; import { getAllCustomProperties } from '../../../rest/metadataTypeAPI';
import advancedSearchClassBase from '../../../utils/AdvancedSearchClassBase';
import { import {
getTierOptions, getTierOptions,
getTreeConfig, getTreeConfig,
@ -225,15 +224,24 @@ export const AdvanceSearchProvider = ({
Object.entries(res).forEach(([_, fields]) => { Object.entries(res).forEach(([_, fields]) => {
if (Array.isArray(fields) && fields.length > 0) { if (Array.isArray(fields) && fields.length > 0) {
fields.forEach((field: { name: string; type: string }) => { fields.forEach(
if (field.name && field.type) { (field: {
subfields[field.name] = { name: string;
type: 'text', type: string;
valueSources: ['value'], customPropertyConfig: {
operators: TEXT_FIELD_OPERATORS, config: string | string[];
}; };
}) => {
if (field.name && field.type) {
const { subfieldsKey, dataObject } =
advancedSearchClassBase.getCustomPropertiesSubFields(field);
subfields[subfieldsKey] = {
...dataObject,
valueSources: dataObject.valueSources as ValueSource[],
};
}
} }
}); );
} }
}); });
} catch (error) { } catch (error) {

View File

@ -51,3 +51,7 @@ export interface AdvanceSearchContext {
} }
export type FilterObject = Record<string, string[]>; export type FilterObject = Record<string, string[]>;
export interface CustomPropertyEnumConfig {
multiSelect: boolean;
values: string[];
}

View File

@ -905,7 +905,7 @@ export const PropertyValue: FC<PropertyValueProps> = ({
return ( return (
<div <div
className="d-flex justify-center flex-wrap gap-3" className="d-flex justify-center flex-wrap gap-2 py-2"
data-testid="time-interval-value"> data-testid="time-interval-value">
<div className="d-flex flex-column gap-2 items-center"> <div className="d-flex flex-column gap-2 items-center">
<StartTimeIcon height={30} width={30} /> <StartTimeIcon height={30} width={30} />

View File

@ -299,6 +299,16 @@ export const TEXT_FIELD_OPERATORS = [
'is_null', 'is_null',
'is_not_null', 'is_not_null',
]; ];
export const RANGE_FIELD_OPERATORS = ['between', 'not_between'];
export const LIST_VALUE_OPERATORS = [
'select_equals',
'select_not_equals',
'is_null',
'is_not_null',
];
/** /**
* Generates a query builder tree with a group containing an empty rule * Generates a query builder tree with a group containing an empty rule
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2024 Collate. * Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
@ -10,9 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/**
/**
* Airbyte Metadata Database Connection Config * Airbyte Metadata Database Connection Config
*/ */
export interface AirbyteConnection { export interface AirbyteConnection {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2024 Collate. * Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at

View File

@ -255,6 +255,15 @@
} }
} }
.rule--widget--NUMBER {
.widget--valuesrc {
display: none;
}
.ant-input-number {
width: 100%;
}
}
.rule--widget { .rule--widget {
min-width: 180px; min-width: 180px;
.ant-select, .ant-select,

View File

@ -22,11 +22,20 @@ import {
SelectFieldSettings, SelectFieldSettings,
} from 'react-awesome-query-builder'; } from 'react-awesome-query-builder';
import AntdConfig from 'react-awesome-query-builder/lib/config/antd'; import AntdConfig from 'react-awesome-query-builder/lib/config/antd';
import { SEARCH_INDICES_WITH_COLUMNS_FIELD } from '../constants/AdvancedSearch.constants'; import { CustomPropertyEnumConfig } from '../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface';
import {
LIST_VALUE_OPERATORS,
RANGE_FIELD_OPERATORS,
SEARCH_INDICES_WITH_COLUMNS_FIELD,
TEXT_FIELD_OPERATORS,
} from '../constants/AdvancedSearch.constants';
import { EntityFields, SuggestionField } from '../enums/AdvancedSearch.enum'; import { EntityFields, SuggestionField } from '../enums/AdvancedSearch.enum';
import { SearchIndex } from '../enums/search.enum'; import { SearchIndex } from '../enums/search.enum';
import { getAggregateFieldOptions } from '../rest/miscAPI'; import { getAggregateFieldOptions } from '../rest/miscAPI';
import { renderAdvanceSearchButtons } from './AdvancedSearchUtils'; import {
getCustomPropertyAdvanceSearchEnumOptions,
renderAdvanceSearchButtons,
} from './AdvancedSearchUtils';
import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils'; import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils';
import { renderQueryBuilderFilterButtons } from './QueryBuilderUtils'; import { renderQueryBuilderFilterButtons } from './QueryBuilderUtils';
@ -208,12 +217,7 @@ class AdvancedSearchClassBase {
[EntityFields.COLUMN_DESCRIPTION_STATUS]: { [EntityFields.COLUMN_DESCRIPTION_STATUS]: {
label: t('label.column-description'), label: t('label.column-description'),
type: 'select', type: 'select',
operators: [ operators: LIST_VALUE_OPERATORS,
'select_equals',
'select_not_equals',
'is_null',
'is_not_null',
],
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
valueSources: ['value'], valueSources: ['value'],
fieldSettings: { fieldSettings: {
@ -680,12 +684,7 @@ class AdvancedSearchClassBase {
descriptionStatus: { descriptionStatus: {
label: t('label.description'), label: t('label.description'),
type: 'select', type: 'select',
operators: [ operators: LIST_VALUE_OPERATORS,
'select_equals',
'select_not_equals',
'is_null',
'is_not_null',
],
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
valueSources: ['value'], valueSources: ['value'],
fieldSettings: { fieldSettings: {
@ -899,6 +898,86 @@ class AdvancedSearchClassBase {
}, },
}; };
}; };
public getCustomPropertiesSubFields(field: {
name: string;
type: string;
customPropertyConfig: {
config: string | string[] | CustomPropertyEnumConfig;
};
}) {
{
switch (field.type) {
case 'array<entityReference>':
case 'entityReference':
return {
subfieldsKey: field.name + `.displayName`,
dataObject: {
type: 'select',
label: field.name,
fieldSettings: {
asyncFetch: this.autocomplete({
searchIndex: (
(field.customPropertyConfig.config ?? []) as string[]
).join(',') as SearchIndex,
entityField: EntityFields.DISPLAY_NAME_KEYWORD,
}),
useAsyncSearch: true,
},
},
};
case 'enum':
return {
subfieldsKey: field.name,
dataObject: {
type: 'select',
operators: LIST_VALUE_OPERATORS,
fieldSettings: {
listValues: getCustomPropertyAdvanceSearchEnumOptions(
(
field.customPropertyConfig
.config as CustomPropertyEnumConfig
).values
),
},
},
};
case 'date-cp': {
return {
subfieldsKey: field.name,
dataObject: {
type: 'date',
operators: RANGE_FIELD_OPERATORS,
},
};
}
case 'timestamp':
case 'integer':
case 'number': {
return {
subfieldsKey: field.name,
dataObject: {
type: 'number',
operators: RANGE_FIELD_OPERATORS,
},
};
}
default:
return {
subfieldsKey: field.name,
dataObject: {
type: 'text',
valueSources: ['value'],
operators: TEXT_FIELD_OPERATORS,
},
};
}
}
}
} }
const advancedSearchClassBase = new AdvancedSearchClassBase(); const advancedSearchClassBase = new AdvancedSearchClassBase();

View File

@ -14,7 +14,7 @@
import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Checkbox, MenuProps, Space, Typography } from 'antd'; import { Button, Checkbox, MenuProps, Space, Typography } from 'antd';
import i18next from 'i18next'; import i18next from 'i18next';
import { isArray, isEmpty } from 'lodash'; import { isArray, isEmpty, toLower } from 'lodash';
import React from 'react'; import React from 'react';
import { import {
AsyncFetchListValues, AsyncFetchListValues,
@ -452,3 +452,25 @@ export const getTreeConfig = ({
? advancedSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage) ? advancedSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage)
: jsonLogicSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage); : jsonLogicSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage);
}; };
export const formatQueryValueBasedOnType = (
value: string[],
field: string,
type: string
) => {
if (field.includes('extension') && type === 'text') {
return value.map((item) => toLower(item));
}
return value;
};
export const getCustomPropertyAdvanceSearchEnumOptions = (
enumValues: string[]
) => {
return enumValues.reduce((acc: Record<string, string>, value) => {
acc[value] = value;
return acc;
}, {});
};

View File

@ -15,6 +15,7 @@
import { defaultConjunction } from 'react-awesome-query-builder/lib/utils/defaultUtils'; import { defaultConjunction } from 'react-awesome-query-builder/lib/utils/defaultUtils';
import { getWidgetForFieldOp } from 'react-awesome-query-builder/lib/utils/ruleUtils'; import { getWidgetForFieldOp } from 'react-awesome-query-builder/lib/utils/ruleUtils';
import { formatQueryValueBasedOnType } from './AdvancedSearchUtils';
/* /*
* This script is a modified version of https://github.com/ukrbublik/react-awesome-query-builder/blob/5.1.2/modules/export/elasticSearch.js * This script is a modified version of https://github.com/ukrbublik/react-awesome-query-builder/blob/5.1.2/modules/export/elasticSearch.js
@ -372,7 +373,13 @@ export function elasticSearchFormat(tree, config) {
}, },
}; };
} else { } else {
return buildEsRule(field, value, operator, config, valueSrc); return buildEsRule(
field,
formatQueryValueBasedOnType(value, field, _valueType),
operator,
config,
valueSrc
);
} }
} }