From d65c9ce1f9c67f52e1b6ad7d49541c9332ccf41c Mon Sep 17 00:00:00 2001 From: Aniket Katkar Date: Mon, 14 Oct 2024 22:02:52 +0530 Subject: [PATCH] GEN-1226: Add display name field in the advanced search filter (#17829) * Add display name filter field for advanced search * Localization changes * Update advanced search tests to include display name filter checks * Fix the failing playwright tests * localization changes * Fix flaky tests --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> --- .../main/resources/ui/.licenseheaderignore | 1 + .../e2e/Features/ActivityFeed.spec.ts | 16 +- .../e2e/Features/AdvancedSearch.spec.ts | 4 + .../e2e/Features/QueryEntity.spec.ts | 4 +- .../e2e/Features/RecentlyViewed.spec.ts | 5 +- .../playwright/support/entity/TableClass.ts | 1 + .../resources/ui/playwright/tsconfig.json | 32 +++ .../ui/playwright/utils/advancedSearch.ts | 207 +++++++++++------- .../ui/playwright/utils/customProperty.ts | 39 ++-- .../resources/ui/playwright/utils/domain.ts | 4 +- .../resources/ui/playwright/utils/entity.ts | 27 ++- .../ui/src/locale/languages/de-de.json | 1 + .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/he-he.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/nl-nl.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/ru-ru.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../ui/src/utils/AdvancedSearchClassBase.ts | 191 +++++++++------- .../QueryBuilderElasticsearchFormatUtils.js | 1 + 23 files changed, 353 insertions(+), 189 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/tsconfig.json diff --git a/openmetadata-ui/src/main/resources/ui/.licenseheaderignore b/openmetadata-ui/src/main/resources/ui/.licenseheaderignore index 25b99935e2b..6678b7b4a9c 100644 --- a/openmetadata-ui/src/main/resources/ui/.licenseheaderignore +++ b/openmetadata-ui/src/main/resources/ui/.licenseheaderignore @@ -50,6 +50,7 @@ /playwright/e2e/.cache/ /playwright/.env /playwright/.auth +"/playwright/tsconfig.json" # webpack /webpack diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts index 3f395b4d061..cdb60bb2679 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts @@ -143,7 +143,7 @@ test.describe('Activity feed', () => { test('Assigned task should appear to task tab', async ({ page }) => { const value: TaskDetails = { - term: entity.entity.name, + term: entity.entity.displayName, assignee: user1.responseData.name, }; await redirectToHomePage(page); @@ -309,7 +309,7 @@ test.describe('Activity feed', () => { test('Update Description Task on Columns', async ({ page }) => { const firstTaskValue: TaskDetails = { - term: entity4.entity.name, + term: entity4.entity.displayName, assignee: user1.responseData.name, description: 'Column Description 1', columnName: entity4.entity.columns[0].name, @@ -376,7 +376,7 @@ test.describe('Activity feed', () => { test('Comment and Close Task should work in Task Flow', async ({ page }) => { const value: TaskDetails = { - term: entity2.entity.name, + term: entity2.entity.displayName, assignee: user1.responseData.name, }; await redirectToHomePage(page); @@ -436,7 +436,7 @@ test.describe('Activity feed', () => { test('Open and Closed Task Tab', async ({ page }) => { const value: TaskDetails = { - term: entity3.entity.name, + term: entity3.entity.displayName, assignee: user1.responseData.name, }; await redirectToHomePage(page); @@ -501,7 +501,7 @@ test.describe('Activity feed', () => { page, }) => { const value: TaskDetails = { - term: entity4.entity.name, + term: entity4.entity.displayName, assignee: user1.responseData.name, }; await redirectToHomePage(page); @@ -627,7 +627,7 @@ base.describe('Activity feed with Data Consumer User', () => { await performUserLogin(browser, user2); const value: TaskDetails = { - term: entity.entity.name, + term: entity.entity.displayName, assignee: user2.responseData.name, }; @@ -760,7 +760,7 @@ base.describe('Activity feed with Data Consumer User', () => { await performUserLogin(browser, user2); const value: TaskDetails = { - term: entity2.entity.name, + term: entity2.entity.displayName, assignee: user2.responseData.name, }; @@ -944,7 +944,7 @@ base.describe('Activity feed with Data Consumer User', () => { await performUserLogin(browser, viewAllUser); const value: TaskDetails = { - term: entity3.entity.name, + term: entity3.entity.displayName, assignee: viewAllUser.responseData.name, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearch.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearch.spec.ts index c33fc834f49..dc8ab42bd0a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearch.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearch.spec.ts @@ -99,6 +99,10 @@ test.describe('Advanced Search', { tag: '@advanced-search' }, () => { 'database.displayName': [table1.database.name, table2.database.name], 'databaseSchema.displayName': [table1.schema.name, table2.schema.name], 'columns.name.keyword': ['email', 'shop_id'], + 'displayName.keyword': [ + table1.entity.displayName, + table2.entity.displayName, + ], }; await afterAction(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts index a4a34cd1fd4..bb4ce12bf13 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts @@ -37,8 +37,8 @@ const queryData = { tagFqn: 'PersonalData.Personal', tagName: 'Personal', queryUsedIn: { - table1: table2.entity.name, - table2: table3.entity.name, + table1: table2.entity.displayName, + table2: table3.entity.displayName, }, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts index 3d91dc884b1..c553b0a2d84 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts @@ -22,6 +22,7 @@ import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass' import { TableClass } from '../../support/entity/TableClass'; import { TopicClass } from '../../support/entity/TopicClass'; import { createNewPage, redirectToHomePage } from '../../utils/common'; +import { getEntityDisplayName } from '../../utils/entity'; const entities = [ new ApiEndpointClass(), @@ -77,7 +78,9 @@ test.describe('Recently viewed data assets', () => { await page.waitForSelector(`[data-testid="recently-viewed-widget"]`); - const selector = `[data-testid="recently-viewed-widget"] [title="${entity.entity.name}"]`; + const selector = `[data-testid="recently-viewed-widget"] [title="${getEntityDisplayName( + entity.entity + )}"]`; await expect(page.locator(selector)).toBeVisible(); } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts index 07ca6a12426..bce1ca3dcaf 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts @@ -98,6 +98,7 @@ export class TableClass extends EntityClass { entity = { name: `pw-table-${uuid()}`, + displayName: `pw table ${uuid()}`, description: 'description', columns: this.children, databaseSchema: `${this.service.name}.${this.database.name}.${this.schema.name}`, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/tsconfig.json b/openmetadata-ui/src/main/resources/ui/playwright/tsconfig.json new file mode 100644 index 00000000000..df20e0b0713 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "ES5", + "module": "esnext", + "lib": ["dom", "dom.iterable", "ES2020.Promise", "es2021"], + "allowJs": true, + "jsx": "react", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": false, + "removeComments": false, + "noEmit": true, + "importHelpers": true, + "downlevelIteration": true, + "isolatedModules": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "allowUnreachableCode": false, + "skipLibCheck": true, + "noImplicitAny": true + } +} diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts index f6fd7384463..f2b4e53d41c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts @@ -12,11 +12,13 @@ */ import { expect, Locator, Page } from '@playwright/test'; import { clickOutside } from './common'; +import { getEncodedFqn } from './entity'; type EntityFields = { id: string; name: string; localSearch: boolean; + skipConditions?: string[]; }; export const FIELDS: EntityFields[] = [ @@ -55,6 +57,12 @@ export const FIELDS: EntityFields[] = [ name: 'columns.name.keyword', localSearch: false, }, + { + id: 'Display Name', + name: 'displayName.keyword', + localSearch: false, + skipConditions: ['isNull', 'isNotNull'], // Null and isNotNull conditions are not present for display name + }, ]; export const OPERATOR = { @@ -121,6 +129,9 @@ const selectOption = async ( optionTitle: string ) => { await dropdownLocator.click(); + await page.waitForSelector(`.ant-select-dropdown:visible`, { + state: 'visible', + }); await page.click(`.ant-select-dropdown:visible [title="${optionTitle}"]`); }; @@ -134,7 +145,7 @@ export const fillRule = async ( }: { condition: string; field: EntityFields; - searchCriteria: string; + searchCriteria?: string; index: number; } ) => { @@ -192,7 +203,17 @@ export const fillRule = async ( export const checkMustPaths = async ( page: Page, - { condition, field, searchCriteria, index } + { + condition, + field, + searchCriteria, + index, + }: { + condition: string; + field: EntityFields; + searchCriteria: string; + index: number; + } ) => { const searchData = field.localSearch ? searchCriteria @@ -209,13 +230,14 @@ export const checkMustPaths = async ( '/api/v1/search/query?*index=dataAsset&from=0&size=10*' ); await page.getByTestId('apply-btn').click(); - await searchRes.then(async (res) => { - await expect(res.request().url()).toContain(encodeURI(searchData)); - await res.json().then(async (json) => { - await expect(JSON.stringify(json.hits.hits)).toContain(searchCriteria); - }); - }); + const res = await searchRes; + + expect(res.request().url()).toContain(getEncodedFqn(searchData, true)); + + const json = await res.json(); + + expect(JSON.stringify(json.hits.hits)).toContain(searchCriteria); await expect( page.getByTestId('advance-search-filter-container') @@ -224,7 +246,17 @@ export const checkMustPaths = async ( export const checkMustNotPaths = async ( page: Page, - { condition, field, searchCriteria, index } + { + condition, + field, + searchCriteria, + index, + }: { + condition: string; + field: EntityFields; + searchCriteria: string; + index: number; + } ) => { const searchData = field.localSearch ? searchCriteria @@ -241,17 +273,15 @@ export const checkMustNotPaths = async ( '/api/v1/search/query?*index=dataAsset&from=0&size=10*' ); await page.getByTestId('apply-btn').click(); - await searchRes.then(async (res) => { - await expect(res.request().url()).toContain(encodeURI(searchData)); + const res = await searchRes; - if (!['columns.name.keyword'].includes(field.name)) { - await res.json().then(async (json) => { - await expect(JSON.stringify(json.hits.hits)).not.toContain( - searchCriteria - ); - }); - } - }); + expect(res.request().url()).toContain(getEncodedFqn(searchData, true)); + + if (!['columns.name.keyword'].includes(field.name)) { + const json = await res.json(); + + expect(JSON.stringify(json.hits.hits)).not.toContain(searchCriteria); + } await expect( page.getByTestId('advance-search-filter-container') @@ -260,7 +290,17 @@ export const checkMustNotPaths = async ( export const checkNullPaths = async ( page: Page, - { condition, field, searchCriteria, index } + { + condition, + field, + searchCriteria, + index, + }: { + condition: string; + field: EntityFields; + searchCriteria?: string; + index: number; + } ) => { await fillRule(page, { condition, @@ -273,51 +313,48 @@ export const checkNullPaths = async ( '/api/v1/search/query?*index=dataAsset&from=0&size=10*' ); await page.getByTestId('apply-btn').click(); - await searchRes.then(async (res) => { - const urlParams = new URLSearchParams(res.request().url()); - const queryFilter = JSON.parse(urlParams.get('query_filter') ?? ''); + const res = await searchRes; + const urlParams = new URLSearchParams(res.request().url()); + const queryFilter = JSON.parse(urlParams.get('query_filter') ?? ''); - const resultQuery = - condition === 'Is null' - ? { - query: { - bool: { - must: [ - { - bool: { - must: [ - { - bool: { - must_not: { - exists: { field: field.name }, - }, + const resultQuery = + condition === 'Is null' + ? { + query: { + bool: { + must: [ + { + bool: { + must: [ + { + bool: { + must_not: { + exists: { field: field.name }, }, }, - ], - }, + }, + ], }, - ], - }, + }, + ], }, - } - : { - query: { - bool: { - must: [ - { - bool: { - must: [{ exists: { field: field.name } }], - }, + }, + } + : { + query: { + bool: { + must: [ + { + bool: { + must: [{ exists: { field: field.name } }], }, - ], - }, + }, + ], }, - }; + }, + }; - await expect(JSON.stringify(queryFilter)).toContain( - JSON.stringify(resultQuery) - ); - }); + expect(JSON.stringify(queryFilter)).toContain(JSON.stringify(resultQuery)); }; export const verifyAllConditions = async ( @@ -349,21 +386,27 @@ export const verifyAllConditions = async ( await page.getByTestId('clear-filters').click(); } - // Check for Null and Not Null conditions - for (const condition of Object.values(NULL_CONDITIONS)) { - await showAdvancedSearchDialog(page); - await checkNullPaths(page, { - condition: condition.name, - field, - searchCriteria: undefined, - index: 1, - }); - await page.getByTestId('clear-filters').click(); + // Don't run null path if it's present in skipConditions + if ( + !field.skipConditions?.includes('isNull') || + !field.skipConditions?.includes('isNotNull') + ) { + // Check for Null and Not Null conditions + for (const condition of Object.values(NULL_CONDITIONS)) { + await showAdvancedSearchDialog(page); + await checkNullPaths(page, { + condition: condition.name, + field, + searchCriteria: undefined, + index: 1, + }); + await page.getByTestId('clear-filters').click(); + } } }; export const checkAddRuleOrGroupWithOperator = async ( - page, + page: Page, { field, operator, @@ -405,27 +448,25 @@ export const checkAddRuleOrGroupWithOperator = async ( if (operator === 'OR') { await page .getByTestId('advanced-search-modal') - .getByRole('button', { name: 'Or' }); + .getByRole('button', { name: 'Or' }) + .click(); } const searchRes = page.waitForResponse( '/api/v1/search/query?*index=dataAsset&from=0&size=10*' ); await page.getByTestId('apply-btn').click(); - await searchRes; - await searchRes.then(async (res) => { - await res.json().then(async (json) => { - if (field.id !== 'Column') { - if (operator === 'Or') { - await expect(JSON.stringify(json)).toContain(searchCriteria1); - await expect(JSON.stringify(json)).toContain(searchCriteria2); - } else { - await expect(JSON.stringify(json)).toContain(searchCriteria1); - await expect(JSON.stringify(json)).not.toContain(searchCriteria2); - } - } - }); - }); + + // Since the OR operator with must not conditions will result in huge API response + // with huge data, checking the required criteria might not be present on first page + // Hence, checking the criteria only for AND operator + if (field.id !== 'Column' && operator === 'AND') { + const res = await searchRes; + const json = await res.json(); + + expect(JSON.stringify(json)).toContain(searchCriteria1); + expect(JSON.stringify(json)).not.toContain(searchCriteria2); + } }; export const runRuleGroupTests = async ( diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts index ddc63ee9a8a..0202119b438 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts @@ -116,7 +116,7 @@ export const setValueForProperty = async (data: { case 'enum': await page.click('#enumValues'); - await page.fill('#enumValues', value); + await page.fill('#enumValues', value, { force: true }); await page.press('#enumValues', 'Enter'); await clickOutside(page); await container.locator('[data-testid="inline-save-btn"]').click(); @@ -384,12 +384,15 @@ export const createCustomPropertyForEntity = async ( '/api/v1/metadata/types?category=field&limit=20' ); const properties = await propertiesResponse.json(); - const propertyList = properties.data.filter((item) => - Object.values(CustomPropertyTypeByName).includes(item.name) + const propertyList = properties.data.filter( + (item: { name: CustomPropertyTypeByName }) => + Object.values(CustomPropertyTypeByName).includes(item.name) ); const entitySchemaResponse = await apiContext.get( - `/api/v1/metadata/types/name/${ENTITY_PATH[endpoint]}` + `/api/v1/metadata/types/name/${ + ENTITY_PATH[endpoint as keyof typeof ENTITY_PATH] + }` ); const entitySchema = await entitySchemaResponse.json(); @@ -414,7 +417,7 @@ export const createCustomPropertyForEntity = async ( acc[`user${index + 1}`] = user.getUserName(); return acc; - }, {}); + }, {} as Record); // Define an asynchronous function to clean up (delete) all users in the users array const cleanupUser = async (apiContext: APIRequestContext) => { @@ -501,17 +504,23 @@ export const createCustomPropertyForEntity = async ( const customProperty = await customPropertyResponse.json(); // Process the custom properties - customProperties = customProperty.customProperties.reduce((prev, curr) => { - const propertyTypeName = curr.propertyType.name; + customProperties = customProperty.customProperties.reduce( + ( + prev: Record, + curr: Record> + ) => { + const propertyTypeName = curr.propertyType.name; - return { - ...prev, - [propertyTypeName]: { - ...getPropertyValues(propertyTypeName, userNames), - property: curr, - }, - }; - }, {}); + return { + ...prev, + [propertyTypeName]: { + ...getPropertyValues(propertyTypeName, userNames), + property: curr, + }, + }; + }, + {} + ); } return { customProperties, cleanupUser }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts index 27b46c8bbf3..cb108858a4f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts @@ -77,7 +77,7 @@ export const removeDomain = async (page: Page) => { await expect(page.getByTestId('no-domain-text')).toContainText('No Domain'); }; -export const validateDomainForm = async (page) => { +export const validateDomainForm = async (page: Page) => { // Error messages await expect(page.locator('#name_help')).toHaveText('Name is required'); await expect(page.locator('#description_help')).toHaveText( @@ -408,7 +408,7 @@ export const createDataProduct = async ( page: Page, dataProduct: DataProduct['data'] ) => { - await page.getByTestId('domain-details-add-button').click(); + await page.getByTestId('domain-details-add-button').click({ force: true }); await page.getByRole('menuitem', { name: 'Data Products' }).click(); await expect(page.getByText('Add Data Product')).toBeVisible(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts index 33ed3a4bf30..cf838b359c0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -957,7 +957,15 @@ export const updateDisplayNameForEntity = async ( ).toHaveText(displayName); }; -export const checkForEditActions = async ({ entityType, deleted, page }) => { +export const checkForEditActions = async ({ + page, + entityType, + deleted = false, +}: { + page: Page; + entityType: string; + deleted?: boolean; +}) => { for (const { containerSelector, elementSelector, @@ -1320,3 +1328,20 @@ export const escapeESReservedCharacters = (text?: string) => { ? text.replace(reUnescapedHtml, getReplacedChar) : text ?? ''; }; + +export const getEncodedFqn = (fqn: string, spaceAsPlus = false) => { + let uri = encodeURIComponent(fqn); + + if (spaceAsPlus) { + uri = uri.replaceAll('%20', '+'); + } + + return uri; +}; + +export const getEntityDisplayName = (entity?: { + name?: string; + displayName?: string; +}) => { + return entity?.displayName || entity?.name || ''; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index c9978b067f0..6bded0c1870 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Neuen Registrierungstoken generieren", "region-name": "Region Name", "registry": "Register", + "regular-expression": "Regular Expression", "reject": "Ablehnen", "reject-all": "Reject All", "rejected": "Rejected", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 4d2ff226b7c..5382b6d34fc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Regenerate registration token", "region-name": "Region Name", "registry": "Registry", + "regular-expression": "Regular Expression", "reject": "Reject", "reject-all": "Reject All", "rejected": "Rejected", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 885dd80fac4..1e2d09aa62e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Regenerar token de registro", "region-name": "Nombre de la región", "registry": "Registro", + "regular-expression": "Regular Expression", "reject": "Rechazar", "reject-all": "Reject All", "rejected": "Rechazado", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index abb24e274d7..f66551ec463 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Regénérer le Jeton d'Inscription", "region-name": "Nom de Région", "registry": "Registre", + "regular-expression": "Regular Expression", "reject": "Rejeter", "reject-all": "Rejeter Tout", "rejected": "Rejeté", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index e02dff9d6bc..1d9eef69922 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "הפק מחדש את אסימון הרישום", "region-name": "שם האזור", "registry": "רשומון", + "regular-expression": "Regular Expression", "reject": "דחה", "reject-all": "Reject All", "rejected": "נדחה", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index d1ead9821e6..9431880aca3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "登録するトークンを作り直す", "region-name": "リージョン名", "registry": "レジストリ", + "regular-expression": "Regular Expression", "reject": "Reject", "reject-all": "Reject All", "rejected": "Rejected", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 82ef8cad96e..4592b6e1cab 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Opnieuw genereren registratietoken", "region-name": "Regionaam", "registry": "Register", + "regular-expression": "Regular Expression", "reject": "Weigeren", "reject-all": "Reject All", "rejected": "Geweigerd", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 33258aa4a32..88a59c9b409 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Regenerar token de registro", "region-name": "Nome da Região", "registry": "Registro", + "regular-expression": "Regular Expression", "reject": "Rejeitar", "reject-all": "Reject All", "rejected": "Rejeitado", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 4f8eed0b514..a0e7f876b38 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "Восстановить регистрационный токен", "region-name": "Наименование региона", "registry": "Реестр", + "regular-expression": "Regular Expression", "reject": "Reject", "reject-all": "Reject All", "rejected": "Rejected", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 369d5efecf8..8adc434d5e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -928,6 +928,7 @@ "regenerate-registration-token": "重新产生注册令牌", "region-name": "区域名称", "registry": "仓库", + "regular-expression": "Regular Expression", "reject": "拒绝", "reject-all": "拒绝全部", "rejected": "已拒绝", diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts index dc49e2a3f96..c7c57bc8e47 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts @@ -30,6 +30,74 @@ import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils'; class AdvancedSearchClassBase { baseConfig = AntdConfig as BasicConfig; + configTypes: BasicConfig['types'] = { + ...this.baseConfig.types, + multiselect: { + ...this.baseConfig.types.multiselect, + widgets: { + ...this.baseConfig.types.multiselect.widgets, + // Adds the "Contains" and "Not contains" options for fields with type multiselect + text: { + operators: ['like', 'not_like', 'regexp'], + }, + }, + // Limits source to user input values, not other fields + valueSources: ['value'], + }, + select: { + ...this.baseConfig.types.select, + widgets: { + ...this.baseConfig.types.select.widgets, + text: { + operators: ['like', 'not_like', 'regexp'], + }, + }, + valueSources: ['value'], + }, + text: { + ...this.baseConfig.types.text, + valueSources: ['value'], + }, + }; + configWidgets: BasicConfig['widgets'] = { + ...this.baseConfig.widgets, + multiselect: { + ...this.baseConfig.widgets.multiselect, + showSearch: true, + showCheckboxes: true, + useAsyncSearch: true, + useLoadMore: false, + customProps: { + popupClassName: 'w-max-600', + }, + }, + select: { + ...this.baseConfig.widgets.select, + showSearch: true, + showCheckboxes: true, + useAsyncSearch: true, + useLoadMore: false, + customProps: { + popupClassName: 'w-max-600', + }, + }, + text: { + ...this.baseConfig.widgets.text, + }, + }; + configOperators = { + ...this.baseConfig.operators, + like: { + ...this.baseConfig.operators.like, + elasticSearchQueryType: 'wildcard', + }, + regexp: { + label: t('label.regular-expression'), + labelForFormat: t('label.regular-expression'), + elasticSearchQueryType: 'regexp', + valueSources: ['value'], + }, + }; mainWidgetProps = { fullWidth: true, @@ -97,7 +165,7 @@ class AdvancedSearchClassBase { }, }, - tableType: { + [EntityFields.TABLE_TYPE]: { label: t('label.table-type'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -115,7 +183,7 @@ class AdvancedSearchClassBase { * Fields specific to pipelines */ pipelineQueryBuilderFields: Fields = { - 'tasks.displayName.keyword': { + [EntityFields.TASK]: { label: t('label.task'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -133,7 +201,7 @@ class AdvancedSearchClassBase { * Fields specific to topics */ topicQueryBuilderFields: Fields = { - 'messageSchema.schemaFields.name.keyword': { + [EntityFields.SCHEMA_FIELD]: { label: t('label.schema-field'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -151,7 +219,7 @@ class AdvancedSearchClassBase { * Fields specific to API endpoints */ apiEndpointQueryBuilderFields: Fields = { - 'requestSchema.schemaFields.name.keyword': { + [EntityFields.REQUEST_SCHEMA_FIELD]: { label: t('label.request-schema-field'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -163,7 +231,7 @@ class AdvancedSearchClassBase { useAsyncSearch: true, }, }, - 'responseSchema.schemaFields.name.keyword': { + [EntityFields.RESPONSE_SCHEMA_FIELD]: { label: t('label.response-schema-field'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -181,7 +249,7 @@ class AdvancedSearchClassBase { * Fields specific to Glossary */ glossaryQueryBuilderFields: Fields = { - status: { + [EntityFields.GLOSSARY_TERM_STATUS]: { label: t('label.status'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -199,7 +267,7 @@ class AdvancedSearchClassBase { * Fields specific to dashboard */ dashboardQueryBuilderFields: Fields = { - 'dataModels.displayName.keyword': { + [EntityFields.DATA_MODEL]: { label: t('label.data-model'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -211,7 +279,7 @@ class AdvancedSearchClassBase { useAsyncSearch: true, }, }, - 'charts.displayName.keyword': { + [EntityFields.CHART]: { label: t('label.chart'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -223,7 +291,7 @@ class AdvancedSearchClassBase { useAsyncSearch: true, }, }, - 'project.keyword': { + [EntityFields.PROJECT]: { label: t('label.project'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -241,7 +309,7 @@ class AdvancedSearchClassBase { * Fields specific to ML models */ mlModelQueryBuilderFields: Fields = { - 'mlFeatures.name': { + [EntityFields.FEATURE]: { label: t('label.feature'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -259,7 +327,7 @@ class AdvancedSearchClassBase { * Fields specific to containers */ containerQueryBuilderFields: Fields = { - 'dataModel.columns.name.keyword': { + [EntityFields.CONTAINER_COLUMN]: { label: t('label.container-column'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -277,7 +345,7 @@ class AdvancedSearchClassBase { * Fields specific to search indexes */ searchIndexQueryBuilderFields: Fields = { - 'fields.name.keyword': { + [EntityFields.FIELD]: { label: t('label.field'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -295,7 +363,7 @@ class AdvancedSearchClassBase { * Fields specific to dashboard data models */ dataModelQueryBuilderFields: Fields = { - dataModelType: { + [EntityFields.DATA_MODEL_TYPE]: { label: t('label.data-model-type'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -307,7 +375,7 @@ class AdvancedSearchClassBase { useAsyncSearch: true, }, }, - 'project.keyword': { + [EntityFields.PROJECT]: { label: t('label.project'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -328,62 +396,9 @@ class AdvancedSearchClassBase { public getInitialConfigWithoutFields = (isExplorePage = true) => { const initialConfigWithoutFields: BasicConfig = { ...this.baseConfig, - types: { - ...this.baseConfig.types, - multiselect: { - ...this.baseConfig.types.multiselect, - widgets: { - ...this.baseConfig.types.multiselect.widgets, - // Adds the "Contains" and "Not contains" options for fields with type multiselect - text: { - operators: ['like', 'not_like'], - }, - }, - // Limits source to user input values, not other fields - valueSources: ['value'], - }, - select: { - ...this.baseConfig.types.select, - widgets: { - ...this.baseConfig.types.select.widgets, - text: { - operators: ['like', 'not_like'], - }, - }, - valueSources: ['value'], - }, - text: { - ...this.baseConfig.types.text, - valueSources: ['value'], - }, - }, - widgets: { - ...this.baseConfig.widgets, - multiselect: { - ...this.baseConfig.widgets.multiselect, - showSearch: true, - showCheckboxes: true, - useAsyncSearch: true, - useLoadMore: false, - }, - select: { - ...this.baseConfig.widgets.select, - showSearch: true, - showCheckboxes: true, - useAsyncSearch: true, - useLoadMore: false, - }, - text: { - ...this.baseConfig.widgets.text, - }, - }, - operators: { - ...this.baseConfig.operators, - like: { - ...this.baseConfig.operators.like, - elasticSearchQueryType: 'wildcard', - }, - }, + types: this.configTypes, + widgets: this.configWidgets, + operators: this.configOperators, settings: { ...this.baseConfig.settings, showLabels: isExplorePage, @@ -427,13 +442,35 @@ class AdvancedSearchClassBase { } = args; return { + [EntityFields.DISPLAY_NAME_KEYWORD]: { + label: t('label.display-name'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + fieldSettings: { + asyncFetch: this.autocomplete({ + searchIndex: entitySearchIndex ?? [SearchIndex.DATA_ASSET], + entityField: EntityFields.DISPLAY_NAME_KEYWORD, + }), + useAsyncSearch: true, + }, + operators: [ + 'select_equals', + 'select_not_equals', + 'select_any_in', + 'select_not_any_in', + 'like', + 'not_like', + 'regexp', + ], + }, + deleted: { label: t('label.deleted'), type: 'boolean', defaultValue: true, }, - 'owners.displayName.keyword': { + [EntityFields.OWNERS]: { label: t('label.owner'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -447,7 +484,7 @@ class AdvancedSearchClassBase { }, }, - 'domain.displayName.keyword': { + [EntityFields.DOMAIN]: { label: t('label.domain'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -461,7 +498,7 @@ class AdvancedSearchClassBase { }, }, - serviceType: { + [EntityFields.SERVICE_TYPE]: { label: t('label.service-type'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -475,7 +512,7 @@ class AdvancedSearchClassBase { }, }, - 'tags.tagFQN': { + [EntityFields.TAG]: { label: t('label.tag-plural'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -490,7 +527,7 @@ class AdvancedSearchClassBase { }, }, - 'tier.tagFQN': { + [EntityFields.TIER]: { label: t('label.tier'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -540,7 +577,7 @@ class AdvancedSearchClassBase { return !isEmpty(searchIndexWithColumns) ? { - 'columns.name.keyword': { + [EntityFields.COLUMN]: { label: t('label.column'), type: 'select', mainWidgetProps: this.mainWidgetProps, @@ -618,7 +655,7 @@ class AdvancedSearchClassBase { shouldAddServiceField?: boolean; }) => { const serviceQueryBuilderFields: Fields = { - 'service.displayName.keyword': { + [EntityFields.SERVICE]: { label: t('label.service'), type: 'select', mainWidgetProps: this.mainWidgetProps, 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 72817652f27..0c8e83f8588 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js @@ -181,6 +181,7 @@ function determineQueryField(fieldDataType, fullFieldName, queryType) { function buildRegexpParameters(value) { return { value: value, + case_insensitive: true, }; }