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>
This commit is contained in:
Aniket Katkar 2024-10-14 22:02:52 +05:30
parent 17d68ddd0c
commit d65c9ce1f9
23 changed files with 353 additions and 189 deletions

View File

@ -50,6 +50,7 @@
/playwright/e2e/.cache/ /playwright/e2e/.cache/
/playwright/.env /playwright/.env
/playwright/.auth /playwright/.auth
"/playwright/tsconfig.json"
# webpack # webpack
/webpack /webpack

View File

@ -143,7 +143,7 @@ test.describe('Activity feed', () => {
test('Assigned task should appear to task tab', async ({ page }) => { test('Assigned task should appear to task tab', async ({ page }) => {
const value: TaskDetails = { const value: TaskDetails = {
term: entity.entity.name, term: entity.entity.displayName,
assignee: user1.responseData.name, assignee: user1.responseData.name,
}; };
await redirectToHomePage(page); await redirectToHomePage(page);
@ -309,7 +309,7 @@ test.describe('Activity feed', () => {
test('Update Description Task on Columns', async ({ page }) => { test('Update Description Task on Columns', async ({ page }) => {
const firstTaskValue: TaskDetails = { const firstTaskValue: TaskDetails = {
term: entity4.entity.name, term: entity4.entity.displayName,
assignee: user1.responseData.name, assignee: user1.responseData.name,
description: 'Column Description 1', description: 'Column Description 1',
columnName: entity4.entity.columns[0].name, 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 }) => { test('Comment and Close Task should work in Task Flow', async ({ page }) => {
const value: TaskDetails = { const value: TaskDetails = {
term: entity2.entity.name, term: entity2.entity.displayName,
assignee: user1.responseData.name, assignee: user1.responseData.name,
}; };
await redirectToHomePage(page); await redirectToHomePage(page);
@ -436,7 +436,7 @@ test.describe('Activity feed', () => {
test('Open and Closed Task Tab', async ({ page }) => { test('Open and Closed Task Tab', async ({ page }) => {
const value: TaskDetails = { const value: TaskDetails = {
term: entity3.entity.name, term: entity3.entity.displayName,
assignee: user1.responseData.name, assignee: user1.responseData.name,
}; };
await redirectToHomePage(page); await redirectToHomePage(page);
@ -501,7 +501,7 @@ test.describe('Activity feed', () => {
page, page,
}) => { }) => {
const value: TaskDetails = { const value: TaskDetails = {
term: entity4.entity.name, term: entity4.entity.displayName,
assignee: user1.responseData.name, assignee: user1.responseData.name,
}; };
await redirectToHomePage(page); await redirectToHomePage(page);
@ -627,7 +627,7 @@ base.describe('Activity feed with Data Consumer User', () => {
await performUserLogin(browser, user2); await performUserLogin(browser, user2);
const value: TaskDetails = { const value: TaskDetails = {
term: entity.entity.name, term: entity.entity.displayName,
assignee: user2.responseData.name, assignee: user2.responseData.name,
}; };
@ -760,7 +760,7 @@ base.describe('Activity feed with Data Consumer User', () => {
await performUserLogin(browser, user2); await performUserLogin(browser, user2);
const value: TaskDetails = { const value: TaskDetails = {
term: entity2.entity.name, term: entity2.entity.displayName,
assignee: user2.responseData.name, assignee: user2.responseData.name,
}; };
@ -944,7 +944,7 @@ base.describe('Activity feed with Data Consumer User', () => {
await performUserLogin(browser, viewAllUser); await performUserLogin(browser, viewAllUser);
const value: TaskDetails = { const value: TaskDetails = {
term: entity3.entity.name, term: entity3.entity.displayName,
assignee: viewAllUser.responseData.name, assignee: viewAllUser.responseData.name,
}; };

View File

@ -99,6 +99,10 @@ test.describe('Advanced Search', { tag: '@advanced-search' }, () => {
'database.displayName': [table1.database.name, table2.database.name], 'database.displayName': [table1.database.name, table2.database.name],
'databaseSchema.displayName': [table1.schema.name, table2.schema.name], 'databaseSchema.displayName': [table1.schema.name, table2.schema.name],
'columns.name.keyword': ['email', 'shop_id'], 'columns.name.keyword': ['email', 'shop_id'],
'displayName.keyword': [
table1.entity.displayName,
table2.entity.displayName,
],
}; };
await afterAction(); await afterAction();

View File

@ -37,8 +37,8 @@ const queryData = {
tagFqn: 'PersonalData.Personal', tagFqn: 'PersonalData.Personal',
tagName: 'Personal', tagName: 'Personal',
queryUsedIn: { queryUsedIn: {
table1: table2.entity.name, table1: table2.entity.displayName,
table2: table3.entity.name, table2: table3.entity.displayName,
}, },
}; };

View File

@ -22,6 +22,7 @@ import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass'
import { TableClass } from '../../support/entity/TableClass'; import { TableClass } from '../../support/entity/TableClass';
import { TopicClass } from '../../support/entity/TopicClass'; import { TopicClass } from '../../support/entity/TopicClass';
import { createNewPage, redirectToHomePage } from '../../utils/common'; import { createNewPage, redirectToHomePage } from '../../utils/common';
import { getEntityDisplayName } from '../../utils/entity';
const entities = [ const entities = [
new ApiEndpointClass(), new ApiEndpointClass(),
@ -77,7 +78,9 @@ test.describe('Recently viewed data assets', () => {
await page.waitForSelector(`[data-testid="recently-viewed-widget"]`); 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(); await expect(page.locator(selector)).toBeVisible();
} }

View File

@ -98,6 +98,7 @@ export class TableClass extends EntityClass {
entity = { entity = {
name: `pw-table-${uuid()}`, name: `pw-table-${uuid()}`,
displayName: `pw table ${uuid()}`,
description: 'description', description: 'description',
columns: this.children, columns: this.children,
databaseSchema: `${this.service.name}.${this.database.name}.${this.schema.name}`, databaseSchema: `${this.service.name}.${this.database.name}.${this.schema.name}`,

View File

@ -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
}
}

View File

@ -12,11 +12,13 @@
*/ */
import { expect, Locator, Page } from '@playwright/test'; import { expect, Locator, Page } from '@playwright/test';
import { clickOutside } from './common'; import { clickOutside } from './common';
import { getEncodedFqn } from './entity';
type EntityFields = { type EntityFields = {
id: string; id: string;
name: string; name: string;
localSearch: boolean; localSearch: boolean;
skipConditions?: string[];
}; };
export const FIELDS: EntityFields[] = [ export const FIELDS: EntityFields[] = [
@ -55,6 +57,12 @@ export const FIELDS: EntityFields[] = [
name: 'columns.name.keyword', name: 'columns.name.keyword',
localSearch: false, 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 = { export const OPERATOR = {
@ -121,6 +129,9 @@ const selectOption = async (
optionTitle: string optionTitle: string
) => { ) => {
await dropdownLocator.click(); await dropdownLocator.click();
await page.waitForSelector(`.ant-select-dropdown:visible`, {
state: 'visible',
});
await page.click(`.ant-select-dropdown:visible [title="${optionTitle}"]`); await page.click(`.ant-select-dropdown:visible [title="${optionTitle}"]`);
}; };
@ -134,7 +145,7 @@ export const fillRule = async (
}: { }: {
condition: string; condition: string;
field: EntityFields; field: EntityFields;
searchCriteria: string; searchCriteria?: string;
index: number; index: number;
} }
) => { ) => {
@ -192,7 +203,17 @@ export const fillRule = async (
export const checkMustPaths = async ( export const checkMustPaths = async (
page: Page, page: Page,
{ condition, field, searchCriteria, index } {
condition,
field,
searchCriteria,
index,
}: {
condition: string;
field: EntityFields;
searchCriteria: string;
index: number;
}
) => { ) => {
const searchData = field.localSearch const searchData = field.localSearch
? searchCriteria ? searchCriteria
@ -209,13 +230,14 @@ export const checkMustPaths = async (
'/api/v1/search/query?*index=dataAsset&from=0&size=10*' '/api/v1/search/query?*index=dataAsset&from=0&size=10*'
); );
await page.getByTestId('apply-btn').click(); 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) => { const res = await searchRes;
await expect(JSON.stringify(json.hits.hits)).toContain(searchCriteria);
}); expect(res.request().url()).toContain(getEncodedFqn(searchData, true));
});
const json = await res.json();
expect(JSON.stringify(json.hits.hits)).toContain(searchCriteria);
await expect( await expect(
page.getByTestId('advance-search-filter-container') page.getByTestId('advance-search-filter-container')
@ -224,7 +246,17 @@ export const checkMustPaths = async (
export const checkMustNotPaths = async ( export const checkMustNotPaths = async (
page: Page, page: Page,
{ condition, field, searchCriteria, index } {
condition,
field,
searchCriteria,
index,
}: {
condition: string;
field: EntityFields;
searchCriteria: string;
index: number;
}
) => { ) => {
const searchData = field.localSearch const searchData = field.localSearch
? searchCriteria ? searchCriteria
@ -241,17 +273,15 @@ export const checkMustNotPaths = async (
'/api/v1/search/query?*index=dataAsset&from=0&size=10*' '/api/v1/search/query?*index=dataAsset&from=0&size=10*'
); );
await page.getByTestId('apply-btn').click(); await page.getByTestId('apply-btn').click();
await searchRes.then(async (res) => { const res = await searchRes;
await expect(res.request().url()).toContain(encodeURI(searchData));
expect(res.request().url()).toContain(getEncodedFqn(searchData, true));
if (!['columns.name.keyword'].includes(field.name)) { if (!['columns.name.keyword'].includes(field.name)) {
await res.json().then(async (json) => { const json = await res.json();
await expect(JSON.stringify(json.hits.hits)).not.toContain(
searchCriteria expect(JSON.stringify(json.hits.hits)).not.toContain(searchCriteria);
);
});
} }
});
await expect( await expect(
page.getByTestId('advance-search-filter-container') page.getByTestId('advance-search-filter-container')
@ -260,7 +290,17 @@ export const checkMustNotPaths = async (
export const checkNullPaths = async ( export const checkNullPaths = async (
page: Page, page: Page,
{ condition, field, searchCriteria, index } {
condition,
field,
searchCriteria,
index,
}: {
condition: string;
field: EntityFields;
searchCriteria?: string;
index: number;
}
) => { ) => {
await fillRule(page, { await fillRule(page, {
condition, condition,
@ -273,7 +313,7 @@ export const checkNullPaths = async (
'/api/v1/search/query?*index=dataAsset&from=0&size=10*' '/api/v1/search/query?*index=dataAsset&from=0&size=10*'
); );
await page.getByTestId('apply-btn').click(); await page.getByTestId('apply-btn').click();
await searchRes.then(async (res) => { const res = await searchRes;
const urlParams = new URLSearchParams(res.request().url()); const urlParams = new URLSearchParams(res.request().url());
const queryFilter = JSON.parse(urlParams.get('query_filter') ?? ''); const queryFilter = JSON.parse(urlParams.get('query_filter') ?? '');
@ -314,10 +354,7 @@ export const checkNullPaths = async (
}, },
}; };
await expect(JSON.stringify(queryFilter)).toContain( expect(JSON.stringify(queryFilter)).toContain(JSON.stringify(resultQuery));
JSON.stringify(resultQuery)
);
});
}; };
export const verifyAllConditions = async ( export const verifyAllConditions = async (
@ -349,6 +386,11 @@ export const verifyAllConditions = async (
await page.getByTestId('clear-filters').click(); 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 // Check for Null and Not Null conditions
for (const condition of Object.values(NULL_CONDITIONS)) { for (const condition of Object.values(NULL_CONDITIONS)) {
await showAdvancedSearchDialog(page); await showAdvancedSearchDialog(page);
@ -360,10 +402,11 @@ export const verifyAllConditions = async (
}); });
await page.getByTestId('clear-filters').click(); await page.getByTestId('clear-filters').click();
} }
}
}; };
export const checkAddRuleOrGroupWithOperator = async ( export const checkAddRuleOrGroupWithOperator = async (
page, page: Page,
{ {
field, field,
operator, operator,
@ -405,27 +448,25 @@ export const checkAddRuleOrGroupWithOperator = async (
if (operator === 'OR') { if (operator === 'OR') {
await page await page
.getByTestId('advanced-search-modal') .getByTestId('advanced-search-modal')
.getByRole('button', { name: 'Or' }); .getByRole('button', { name: 'Or' })
.click();
} }
const searchRes = page.waitForResponse( const searchRes = page.waitForResponse(
'/api/v1/search/query?*index=dataAsset&from=0&size=10*' '/api/v1/search/query?*index=dataAsset&from=0&size=10*'
); );
await page.getByTestId('apply-btn').click(); await page.getByTestId('apply-btn').click();
await searchRes;
await searchRes.then(async (res) => { // Since the OR operator with must not conditions will result in huge API response
await res.json().then(async (json) => { // with huge data, checking the required criteria might not be present on first page
if (field.id !== 'Column') { // Hence, checking the criteria only for AND operator
if (operator === 'Or') { if (field.id !== 'Column' && operator === 'AND') {
await expect(JSON.stringify(json)).toContain(searchCriteria1); const res = await searchRes;
await expect(JSON.stringify(json)).toContain(searchCriteria2); const json = await res.json();
} else {
await expect(JSON.stringify(json)).toContain(searchCriteria1); expect(JSON.stringify(json)).toContain(searchCriteria1);
await expect(JSON.stringify(json)).not.toContain(searchCriteria2); expect(JSON.stringify(json)).not.toContain(searchCriteria2);
} }
}
});
});
}; };
export const runRuleGroupTests = async ( export const runRuleGroupTests = async (

View File

@ -116,7 +116,7 @@ export const setValueForProperty = async (data: {
case 'enum': case 'enum':
await page.click('#enumValues'); await page.click('#enumValues');
await page.fill('#enumValues', value); await page.fill('#enumValues', value, { force: true });
await page.press('#enumValues', 'Enter'); await page.press('#enumValues', 'Enter');
await clickOutside(page); await clickOutside(page);
await container.locator('[data-testid="inline-save-btn"]').click(); 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' '/api/v1/metadata/types?category=field&limit=20'
); );
const properties = await propertiesResponse.json(); const properties = await propertiesResponse.json();
const propertyList = properties.data.filter((item) => const propertyList = properties.data.filter(
(item: { name: CustomPropertyTypeByName }) =>
Object.values(CustomPropertyTypeByName).includes(item.name) Object.values(CustomPropertyTypeByName).includes(item.name)
); );
const entitySchemaResponse = await apiContext.get( 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(); const entitySchema = await entitySchemaResponse.json();
@ -414,7 +417,7 @@ export const createCustomPropertyForEntity = async (
acc[`user${index + 1}`] = user.getUserName(); acc[`user${index + 1}`] = user.getUserName();
return acc; return acc;
}, {}); }, {} as Record<string, string>);
// Define an asynchronous function to clean up (delete) all users in the users array // Define an asynchronous function to clean up (delete) all users in the users array
const cleanupUser = async (apiContext: APIRequestContext) => { const cleanupUser = async (apiContext: APIRequestContext) => {
@ -501,7 +504,11 @@ export const createCustomPropertyForEntity = async (
const customProperty = await customPropertyResponse.json(); const customProperty = await customPropertyResponse.json();
// Process the custom properties // Process the custom properties
customProperties = customProperty.customProperties.reduce((prev, curr) => { customProperties = customProperty.customProperties.reduce(
(
prev: Record<string, string>,
curr: Record<string, Record<string, string>>
) => {
const propertyTypeName = curr.propertyType.name; const propertyTypeName = curr.propertyType.name;
return { return {
@ -511,7 +518,9 @@ export const createCustomPropertyForEntity = async (
property: curr, property: curr,
}, },
}; };
}, {}); },
{}
);
} }
return { customProperties, cleanupUser }; return { customProperties, cleanupUser };

View File

@ -77,7 +77,7 @@ export const removeDomain = async (page: Page) => {
await expect(page.getByTestId('no-domain-text')).toContainText('No Domain'); await expect(page.getByTestId('no-domain-text')).toContainText('No Domain');
}; };
export const validateDomainForm = async (page) => { export const validateDomainForm = async (page: Page) => {
// Error messages // Error messages
await expect(page.locator('#name_help')).toHaveText('Name is required'); await expect(page.locator('#name_help')).toHaveText('Name is required');
await expect(page.locator('#description_help')).toHaveText( await expect(page.locator('#description_help')).toHaveText(
@ -408,7 +408,7 @@ export const createDataProduct = async (
page: Page, page: Page,
dataProduct: DataProduct['data'] 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 page.getByRole('menuitem', { name: 'Data Products' }).click();
await expect(page.getByText('Add Data Product')).toBeVisible(); await expect(page.getByText('Add Data Product')).toBeVisible();

View File

@ -957,7 +957,15 @@ export const updateDisplayNameForEntity = async (
).toHaveText(displayName); ).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 { for (const {
containerSelector, containerSelector,
elementSelector, elementSelector,
@ -1320,3 +1328,20 @@ export const escapeESReservedCharacters = (text?: string) => {
? text.replace(reUnescapedHtml, getReplacedChar) ? text.replace(reUnescapedHtml, getReplacedChar)
: text ?? ''; : 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 || '';
};

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Neuen Registrierungstoken generieren", "regenerate-registration-token": "Neuen Registrierungstoken generieren",
"region-name": "Region Name", "region-name": "Region Name",
"registry": "Register", "registry": "Register",
"regular-expression": "Regular Expression",
"reject": "Ablehnen", "reject": "Ablehnen",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rejected", "rejected": "Rejected",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Regenerate registration token", "regenerate-registration-token": "Regenerate registration token",
"region-name": "Region Name", "region-name": "Region Name",
"registry": "Registry", "registry": "Registry",
"regular-expression": "Regular Expression",
"reject": "Reject", "reject": "Reject",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rejected", "rejected": "Rejected",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Regenerar token de registro", "regenerate-registration-token": "Regenerar token de registro",
"region-name": "Nombre de la región", "region-name": "Nombre de la región",
"registry": "Registro", "registry": "Registro",
"regular-expression": "Regular Expression",
"reject": "Rechazar", "reject": "Rechazar",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rechazado", "rejected": "Rechazado",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Regénérer le Jeton d'Inscription", "regenerate-registration-token": "Regénérer le Jeton d'Inscription",
"region-name": "Nom de Région", "region-name": "Nom de Région",
"registry": "Registre", "registry": "Registre",
"regular-expression": "Regular Expression",
"reject": "Rejeter", "reject": "Rejeter",
"reject-all": "Rejeter Tout", "reject-all": "Rejeter Tout",
"rejected": "Rejeté", "rejected": "Rejeté",

View File

@ -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", "reject-all": "Reject All",
"rejected": "נדחה", "rejected": "נדחה",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "登録するトークンを作り直す", "regenerate-registration-token": "登録するトークンを作り直す",
"region-name": "リージョン名", "region-name": "リージョン名",
"registry": "レジストリ", "registry": "レジストリ",
"regular-expression": "Regular Expression",
"reject": "Reject", "reject": "Reject",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rejected", "rejected": "Rejected",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Opnieuw genereren registratietoken", "regenerate-registration-token": "Opnieuw genereren registratietoken",
"region-name": "Regionaam", "region-name": "Regionaam",
"registry": "Register", "registry": "Register",
"regular-expression": "Regular Expression",
"reject": "Weigeren", "reject": "Weigeren",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Geweigerd", "rejected": "Geweigerd",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Regenerar token de registro", "regenerate-registration-token": "Regenerar token de registro",
"region-name": "Nome da Região", "region-name": "Nome da Região",
"registry": "Registro", "registry": "Registro",
"regular-expression": "Regular Expression",
"reject": "Rejeitar", "reject": "Rejeitar",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rejeitado", "rejected": "Rejeitado",

View File

@ -928,6 +928,7 @@
"regenerate-registration-token": "Восстановить регистрационный токен", "regenerate-registration-token": "Восстановить регистрационный токен",
"region-name": "Наименование региона", "region-name": "Наименование региона",
"registry": "Реестр", "registry": "Реестр",
"regular-expression": "Regular Expression",
"reject": "Reject", "reject": "Reject",
"reject-all": "Reject All", "reject-all": "Reject All",
"rejected": "Rejected", "rejected": "Rejected",

View File

@ -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": "已拒绝",

View File

@ -30,6 +30,74 @@ import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils';
class AdvancedSearchClassBase { class AdvancedSearchClassBase {
baseConfig = AntdConfig as BasicConfig; 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 = { mainWidgetProps = {
fullWidth: true, fullWidth: true,
@ -97,7 +165,7 @@ class AdvancedSearchClassBase {
}, },
}, },
tableType: { [EntityFields.TABLE_TYPE]: {
label: t('label.table-type'), label: t('label.table-type'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -115,7 +183,7 @@ class AdvancedSearchClassBase {
* Fields specific to pipelines * Fields specific to pipelines
*/ */
pipelineQueryBuilderFields: Fields = { pipelineQueryBuilderFields: Fields = {
'tasks.displayName.keyword': { [EntityFields.TASK]: {
label: t('label.task'), label: t('label.task'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -133,7 +201,7 @@ class AdvancedSearchClassBase {
* Fields specific to topics * Fields specific to topics
*/ */
topicQueryBuilderFields: Fields = { topicQueryBuilderFields: Fields = {
'messageSchema.schemaFields.name.keyword': { [EntityFields.SCHEMA_FIELD]: {
label: t('label.schema-field'), label: t('label.schema-field'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -151,7 +219,7 @@ class AdvancedSearchClassBase {
* Fields specific to API endpoints * Fields specific to API endpoints
*/ */
apiEndpointQueryBuilderFields: Fields = { apiEndpointQueryBuilderFields: Fields = {
'requestSchema.schemaFields.name.keyword': { [EntityFields.REQUEST_SCHEMA_FIELD]: {
label: t('label.request-schema-field'), label: t('label.request-schema-field'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -163,7 +231,7 @@ class AdvancedSearchClassBase {
useAsyncSearch: true, useAsyncSearch: true,
}, },
}, },
'responseSchema.schemaFields.name.keyword': { [EntityFields.RESPONSE_SCHEMA_FIELD]: {
label: t('label.response-schema-field'), label: t('label.response-schema-field'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -181,7 +249,7 @@ class AdvancedSearchClassBase {
* Fields specific to Glossary * Fields specific to Glossary
*/ */
glossaryQueryBuilderFields: Fields = { glossaryQueryBuilderFields: Fields = {
status: { [EntityFields.GLOSSARY_TERM_STATUS]: {
label: t('label.status'), label: t('label.status'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -199,7 +267,7 @@ class AdvancedSearchClassBase {
* Fields specific to dashboard * Fields specific to dashboard
*/ */
dashboardQueryBuilderFields: Fields = { dashboardQueryBuilderFields: Fields = {
'dataModels.displayName.keyword': { [EntityFields.DATA_MODEL]: {
label: t('label.data-model'), label: t('label.data-model'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -211,7 +279,7 @@ class AdvancedSearchClassBase {
useAsyncSearch: true, useAsyncSearch: true,
}, },
}, },
'charts.displayName.keyword': { [EntityFields.CHART]: {
label: t('label.chart'), label: t('label.chart'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -223,7 +291,7 @@ class AdvancedSearchClassBase {
useAsyncSearch: true, useAsyncSearch: true,
}, },
}, },
'project.keyword': { [EntityFields.PROJECT]: {
label: t('label.project'), label: t('label.project'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -241,7 +309,7 @@ class AdvancedSearchClassBase {
* Fields specific to ML models * Fields specific to ML models
*/ */
mlModelQueryBuilderFields: Fields = { mlModelQueryBuilderFields: Fields = {
'mlFeatures.name': { [EntityFields.FEATURE]: {
label: t('label.feature'), label: t('label.feature'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -259,7 +327,7 @@ class AdvancedSearchClassBase {
* Fields specific to containers * Fields specific to containers
*/ */
containerQueryBuilderFields: Fields = { containerQueryBuilderFields: Fields = {
'dataModel.columns.name.keyword': { [EntityFields.CONTAINER_COLUMN]: {
label: t('label.container-column'), label: t('label.container-column'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -277,7 +345,7 @@ class AdvancedSearchClassBase {
* Fields specific to search indexes * Fields specific to search indexes
*/ */
searchIndexQueryBuilderFields: Fields = { searchIndexQueryBuilderFields: Fields = {
'fields.name.keyword': { [EntityFields.FIELD]: {
label: t('label.field'), label: t('label.field'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -295,7 +363,7 @@ class AdvancedSearchClassBase {
* Fields specific to dashboard data models * Fields specific to dashboard data models
*/ */
dataModelQueryBuilderFields: Fields = { dataModelQueryBuilderFields: Fields = {
dataModelType: { [EntityFields.DATA_MODEL_TYPE]: {
label: t('label.data-model-type'), label: t('label.data-model-type'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -307,7 +375,7 @@ class AdvancedSearchClassBase {
useAsyncSearch: true, useAsyncSearch: true,
}, },
}, },
'project.keyword': { [EntityFields.PROJECT]: {
label: t('label.project'), label: t('label.project'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -328,62 +396,9 @@ class AdvancedSearchClassBase {
public getInitialConfigWithoutFields = (isExplorePage = true) => { public getInitialConfigWithoutFields = (isExplorePage = true) => {
const initialConfigWithoutFields: BasicConfig = { const initialConfigWithoutFields: BasicConfig = {
...this.baseConfig, ...this.baseConfig,
types: { types: this.configTypes,
...this.baseConfig.types, widgets: this.configWidgets,
multiselect: { operators: this.configOperators,
...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',
},
},
settings: { settings: {
...this.baseConfig.settings, ...this.baseConfig.settings,
showLabels: isExplorePage, showLabels: isExplorePage,
@ -427,13 +442,35 @@ class AdvancedSearchClassBase {
} = args; } = args;
return { 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: { deleted: {
label: t('label.deleted'), label: t('label.deleted'),
type: 'boolean', type: 'boolean',
defaultValue: true, defaultValue: true,
}, },
'owners.displayName.keyword': { [EntityFields.OWNERS]: {
label: t('label.owner'), label: t('label.owner'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -447,7 +484,7 @@ class AdvancedSearchClassBase {
}, },
}, },
'domain.displayName.keyword': { [EntityFields.DOMAIN]: {
label: t('label.domain'), label: t('label.domain'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -461,7 +498,7 @@ class AdvancedSearchClassBase {
}, },
}, },
serviceType: { [EntityFields.SERVICE_TYPE]: {
label: t('label.service-type'), label: t('label.service-type'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -475,7 +512,7 @@ class AdvancedSearchClassBase {
}, },
}, },
'tags.tagFQN': { [EntityFields.TAG]: {
label: t('label.tag-plural'), label: t('label.tag-plural'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -490,7 +527,7 @@ class AdvancedSearchClassBase {
}, },
}, },
'tier.tagFQN': { [EntityFields.TIER]: {
label: t('label.tier'), label: t('label.tier'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -540,7 +577,7 @@ class AdvancedSearchClassBase {
return !isEmpty(searchIndexWithColumns) return !isEmpty(searchIndexWithColumns)
? { ? {
'columns.name.keyword': { [EntityFields.COLUMN]: {
label: t('label.column'), label: t('label.column'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,
@ -618,7 +655,7 @@ class AdvancedSearchClassBase {
shouldAddServiceField?: boolean; shouldAddServiceField?: boolean;
}) => { }) => {
const serviceQueryBuilderFields: Fields = { const serviceQueryBuilderFields: Fields = {
'service.displayName.keyword': { [EntityFields.SERVICE]: {
label: t('label.service'), label: t('label.service'),
type: 'select', type: 'select',
mainWidgetProps: this.mainWidgetProps, mainWidgetProps: this.mainWidgetProps,

View File

@ -181,6 +181,7 @@ function determineQueryField(fieldDataType, fullFieldName, queryType) {
function buildRegexpParameters(value) { function buildRegexpParameters(value) {
return { return {
value: value, value: value,
case_insensitive: true,
}; };
} }