Migrate advanced search tests (#17666)

* migrate advanced search tests

* verify advanced search tests

* fix minor config

* push fixes

* add before all timeout

* increase test timeout

* revert playwright config

* add rule based search

* migrate group tests

(cherry picked from commit a63ad691000c5d49b251602f96a519d846a178da)
This commit is contained in:
Karan Hotchandani 2024-09-03 18:59:58 +05:30 committed by karanh37
parent 6fa662bfda
commit 32a85c0c96
7 changed files with 627 additions and 599 deletions

View File

@ -1,256 +0,0 @@
/*
* Copyright 2023 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 {
searchAndClickOnOption,
selectNullOption,
} from '../../common/advancedSearchQuickFilters';
import { interceptURL, verifyResponseStatusCode } from '../../common/common';
import { goToAdvanceSearch } from '../../common/Utils/AdvancedSearch';
import { addDomainToEntity } from '../../common/Utils/Domain';
import {
createEntityViaREST,
deleteEntityViaREST,
visitEntityDetailsPage,
} from '../../common/Utils/Entity';
import { getToken } from '../../common/Utils/LocalStorage';
import { addOwner, removeOwner } from '../../common/Utils/Owner';
import { assignTags, removeTags } from '../../common/Utils/Tags';
import { addTier, removeTier } from '../../common/Utils/Tier';
import {
FilterItem,
QUICK_FILTERS_BY_ASSETS,
SUPPORTED_EMPTY_FILTER_FIELDS,
} from '../../constants/advancedSearchQuickFilters.constants';
import { SEARCH_ENTITY_TABLE } from '../../constants/constants';
import { EntityType, SidebarItem } from '../../constants/Entity.interface';
import { DOMAIN_QUICK_FILTERS_DETAILS } from '../../constants/EntityConstant';
const ownerName = 'Aaron Johnson';
const preRequisitesForTests = () => {
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
createEntityViaREST({
body: DOMAIN_QUICK_FILTERS_DETAILS,
endPoint: EntityType.Domain,
token,
});
visitEntityDetailsPage({
term: SEARCH_ENTITY_TABLE.table_1.term,
entity: SEARCH_ENTITY_TABLE.table_1.entity,
serviceName: SEARCH_ENTITY_TABLE.table_1.serviceName,
});
addDomainToEntity(DOMAIN_QUICK_FILTERS_DETAILS.displayName);
addOwner(ownerName);
assignTags('PersonalData.Personal', EntityType.Table);
addTier('Tier1');
});
};
const postRequisitesForTests = () => {
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
// Domain 1 to test
deleteEntityViaREST({
entityName: DOMAIN_QUICK_FILTERS_DETAILS.name,
endPoint: EntityType.Domain,
token,
});
visitEntityDetailsPage({
term: SEARCH_ENTITY_TABLE.table_1.term,
entity: SEARCH_ENTITY_TABLE.table_1.entity,
serviceName: SEARCH_ENTITY_TABLE.table_1.serviceName,
});
removeOwner(ownerName);
removeTags('PersonalData.Personal', EntityType.Table);
removeTier();
});
};
// migrated to playwright
describe.skip(
`Advanced search quick filters should work properly for assets`,
{ tags: 'DataAssets' },
() => {
before(() => {
cy.login();
preRequisitesForTests();
});
after(() => {
cy.login();
postRequisitesForTests();
});
beforeEach(() => {
cy.login();
});
it(`should show the quick filters for respective assets`, () => {
// Navigate to explore page
cy.sidebarClick(SidebarItem.EXPLORE);
QUICK_FILTERS_BY_ASSETS.map((asset) => {
cy.get(`[data-testid="${asset.tab}"]`).scrollIntoView().click();
asset.filters.map((filter) => {
cy.get(`[data-testid="search-dropdown-${filter.label}"]`)
.scrollIntoView()
.should('be.visible');
});
});
});
it('search dropdown should work properly for tables', () => {
// Table
const asset = QUICK_FILTERS_BY_ASSETS[0];
// Navigate to explore page
cy.sidebarClick(SidebarItem.EXPLORE);
cy.get(`[data-testid="${asset.tab}"]`).scrollIntoView().click();
asset.filters
.filter((item: FilterItem) => item.select)
.map((filter: FilterItem) => {
cy.get(`[data-testid="search-dropdown-${filter.label}"]`).click();
searchAndClickOnOption(asset, filter, true);
const querySearchURL = `/api/v1/search/query?*index=${
asset.searchIndex
}*query_filter=*should*${filter.key}*${encodeURI(
Cypress._.toLower(filter.selectOption1).replace(' ', '+')
)}*`;
interceptURL('GET', querySearchURL, 'querySearchAPI');
cy.get('[data-testid="update-btn"]').click();
verifyResponseStatusCode('@querySearchAPI', 200);
});
});
it('should search for empty or null filters', () => {
const initialQuery = encodeURI(JSON.stringify({ query: { bool: {} } }));
// Table
interceptURL(
'GET',
`/api/v1/search/query?*index=table_search_index&*query_filter=${initialQuery}&*`,
'initialQueryAPI'
);
const asset = QUICK_FILTERS_BY_ASSETS[0];
cy.sidebarClick(SidebarItem.EXPLORE);
verifyResponseStatusCode('@initialQueryAPI', 200);
cy.get(`[data-testid="${asset.tab}"]`).scrollIntoView().click();
asset.filters
.filter((item) => SUPPORTED_EMPTY_FILTER_FIELDS.includes(item.key))
.map((filter) => {
selectNullOption(asset, filter);
});
});
it('should search for multiple values alongwith null filters', () => {
const initialQuery = encodeURI(JSON.stringify({ query: { bool: {} } }));
// Table
interceptURL(
'GET',
`/api/v1/search/query?*index=table_search_index&*query_filter=${initialQuery}&*`,
'initialQueryAPI'
);
const asset = QUICK_FILTERS_BY_ASSETS[0];
cy.sidebarClick(SidebarItem.EXPLORE);
verifyResponseStatusCode('@initialQueryAPI', 200);
cy.get(`[data-testid="${asset.tab}"]`).scrollIntoView().click();
// Checking Owner with multiple values
asset.filters
.filter((item) => SUPPORTED_EMPTY_FILTER_FIELDS.includes(item.key))
.map((filter: FilterItem) => {
selectNullOption(asset, filter, filter?.selectOptionTestId1);
});
});
}
);
const testIsNullAndIsNotNullFilters = (operatorTitle, queryFilter, alias) => {
goToAdvanceSearch();
// Check Is Null or Is Not Null
cy.get('.rule--operator > .ant-select > .ant-select-selector').eq(0).click();
cy.get(`[title="${operatorTitle}"]`).click();
cy.intercept('GET', '/api/v1/search/query?*', (req) => {
req.alias = alias;
}).as(alias);
cy.get('[data-testid="apply-btn"]').click();
cy.wait(`@${alias}`).then((xhr) => {
const actualQueryFilter = JSON.parse(xhr.request.query['query_filter']);
expect(actualQueryFilter).to.deep.equal(queryFilter);
});
};
describe(`Advanced Search Modal`, () => {
beforeEach(() => {
cy.login();
});
it('should check isNull and isNotNull filters', () => {
// Check Is Null
const isNullQuery = {
query: {
bool: {
must: [
{
bool: {
must: [
{
bool: {
must_not: {
exists: { field: 'owners.displayName.keyword' },
},
},
},
],
},
},
],
},
},
};
testIsNullAndIsNotNullFilters('Is null', isNullQuery, 'searchAPI');
// Check Is Not Null
const isNotNullQuery = {
query: {
bool: {
must: [
{
bool: {
must: [{ exists: { field: 'owners.displayName.keyword' } }],
},
},
],
},
},
};
testIsNullAndIsNotNullFilters(
'Is not null',
isNotNullQuery,
'newSearchAPI'
);
});
});

View File

@ -1,135 +0,0 @@
/*
* Copyright 2023 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.
*/
// The spec is related to advance search feature
import {
advancedSearchFlowCleanup,
advanceSearchPreRequests,
checkAddRuleWithOperator,
CONDITIONS_MUST,
CONDITIONS_MUST_NOT,
FIELDS,
OPERATOR,
} from '../../../common/Utils/AdvancedSearch';
import { getToken } from '../../../common/Utils/LocalStorage';
describe('Search with additional rule', () => {
const testData = {
user_1: {
id: '',
},
user_2: {
id: '',
},
};
before(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advanceSearchPreRequests(testData, token);
});
});
after(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advancedSearchFlowCleanup(token);
});
Cypress.session.clearAllSavedSessions();
});
beforeEach(() => {
cy.login();
});
Object.values(OPERATOR).forEach((operator) => {
it(`Verify Add Rule functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.equalTo.name} and ${CONDITIONS_MUST_NOT.notEqualTo.name} `, () => {
Object.values(FIELDS).forEach((field) => {
let val = field.searchCriteriaSecondGroup;
if (field.owner) {
val = field.responseValueSecondGroup;
}
checkAddRuleWithOperator({
condition_1: CONDITIONS_MUST.equalTo.name,
condition_2: CONDITIONS_MUST_NOT.notEqualTo.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
filter_1: CONDITIONS_MUST.equalTo.filter,
filter_2: CONDITIONS_MUST_NOT.notEqualTo.filter,
response: field.isLocalSearch ? val : Cypress._.toLower(val),
});
});
});
it(`Verify Add Rule functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.anyIn.name} and ${CONDITIONS_MUST_NOT.notIn.name} `, () => {
Object.values(FIELDS).forEach((field) => {
let val = field.searchCriteriaSecondGroup;
if (field.owner) {
val = field.responseValueSecondGroup;
}
checkAddRuleWithOperator({
condition_1: CONDITIONS_MUST.anyIn.name,
condition_2: CONDITIONS_MUST_NOT.notIn.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
filter_1: CONDITIONS_MUST.anyIn.filter,
filter_2: CONDITIONS_MUST_NOT.notIn.filter,
response: field.isLocalSearch ? val : Cypress._.toLower(val),
});
});
});
it(`Verify Add Rule functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.contains.name} and ${CONDITIONS_MUST_NOT.notContains.name} `, () => {
Object.values(FIELDS).forEach((field) => {
const val = field.searchCriteriaSecondGroup;
checkAddRuleWithOperator({
condition_1: CONDITIONS_MUST.contains.name,
condition_2: CONDITIONS_MUST_NOT.notContains.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
filter_1: CONDITIONS_MUST.contains.filter,
filter_2: CONDITIONS_MUST_NOT.notContains.filter,
response: field.isLocalSearch ? val : Cypress._.toLower(val),
});
});
});
});
});

View File

@ -1,120 +0,0 @@
/*
* Copyright 2023 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.
*/
// The spec is related to advance search feature
import {
advancedSearchFlowCleanup,
advanceSearchPreRequests,
checkAddGroupWithOperator,
CONDITIONS_MUST,
CONDITIONS_MUST_NOT,
FIELDS,
OPERATOR,
} from '../../../common/Utils/AdvancedSearch';
import { getToken } from '../../../common/Utils/LocalStorage';
describe('Group search', () => {
const testData = {
user_1: {
id: '',
},
user_2: {
id: '',
},
};
before(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advanceSearchPreRequests(testData, token);
});
});
after(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advancedSearchFlowCleanup(token);
});
Cypress.session.clearAllSavedSessions();
});
beforeEach(() => {
cy.login();
});
Object.values(OPERATOR).forEach((operator) => {
it(`Verify Add group functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.equalTo.name} and ${CONDITIONS_MUST_NOT.notEqualTo.name} `, () => {
Object.values(FIELDS).forEach((field) => {
checkAddGroupWithOperator({
condition_1: CONDITIONS_MUST.equalTo.name,
condition_2: CONDITIONS_MUST_NOT.notEqualTo.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
isLocalSearch: field.isLocalSearch,
});
});
});
it(`Verify Add group functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.anyIn.name} and ${CONDITIONS_MUST_NOT.notIn.name} `, () => {
Object.values(FIELDS).forEach((field) => {
checkAddGroupWithOperator({
condition_1: CONDITIONS_MUST.anyIn.name,
condition_2: CONDITIONS_MUST_NOT.notIn.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
isLocalSearch: field.isLocalSearch,
});
});
});
it(`Verify Add group functionality for All with ${operator.name} operator & condition ${CONDITIONS_MUST.contains.name} and ${CONDITIONS_MUST_NOT.notContains.name} `, () => {
Object.values(FIELDS).forEach((field) => {
checkAddGroupWithOperator({
condition_1: CONDITIONS_MUST.contains.name,
condition_2: CONDITIONS_MUST_NOT.notContains.name,
fieldId: field.testId,
searchCriteria_1: field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
searchCriteria_2: field.isLocalSearch
? field.searchCriteriaSecondGroup
: Cypress._.toLower(field.searchCriteriaSecondGroup),
index_1: 0,
index_2: 1,
operatorIndex: operator.index,
isLocalSearch: field.isLocalSearch,
});
});
});
});
});

View File

@ -1,88 +0,0 @@
/*
* Copyright 2023 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.
*/
// The spec is related to advance search feature
import {
advancedSearchFlowCleanup,
advanceSearchPreRequests,
checkMustPaths,
checkMust_notPaths,
CONDITIONS_MUST,
CONDITIONS_MUST_NOT,
FIELDS,
} from '../../../common/Utils/AdvancedSearch';
import { getToken } from '../../../common/Utils/LocalStorage';
describe('Single filed search', () => {
const testData = {
user_1: {
id: '',
},
user_2: {
id: '',
},
};
before(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advanceSearchPreRequests(testData, token);
});
});
after(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
advancedSearchFlowCleanup(token);
});
Cypress.session.clearAllSavedSessions();
});
beforeEach(() => {
cy.login();
});
Object.values(FIELDS).forEach((field) => {
it(`Verify advance search results for ${field.name} field and all conditions`, () => {
Object.values(CONDITIONS_MUST).forEach((condition) => {
checkMustPaths(
condition.name,
field.testId,
field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
0,
field.responseValueFirstGroup,
field.isLocalSearch
);
});
Object.values(CONDITIONS_MUST_NOT).forEach((condition) => {
checkMust_notPaths(
condition.name,
field.testId,
field.isLocalSearch
? field.searchCriteriaFirstGroup
: Cypress._.toLower(field.searchCriteriaFirstGroup),
0,
field.responseValueFirstGroup,
field.isLocalSearch
);
});
});
});
});

View File

@ -0,0 +1,152 @@
/*
* Copyright 2024 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 from '@playwright/test';
import { SidebarItem } from '../../constant/sidebar';
import { TableClass } from '../../support/entity/TableClass';
import { TopicClass } from '../../support/entity/TopicClass';
import { UserClass } from '../../support/user/UserClass';
import {
FIELDS,
OPERATOR,
runRuleGroupTests,
verifyAllConditions,
} from '../../utils/advancedSearch';
import { createNewPage, redirectToHomePage } from '../../utils/common';
import { addMultiOwner, assignTag, assignTier } from '../../utils/entity';
import { sidebarClick } from '../../utils/sidebar';
test.describe('Advanced Search', { tag: '@advanced-search' }, () => {
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
const user1 = new UserClass();
const user2 = new UserClass();
const table1 = new TableClass();
const table2 = new TableClass();
const topic1 = new TopicClass();
const topic2 = new TopicClass();
let searchCriteria = {};
test.beforeAll('Setup pre-requests', async ({ browser }) => {
test.setTimeout(150000);
const { page, apiContext, afterAction } = await createNewPage(browser);
await Promise.all([
user1.create(apiContext),
user2.create(apiContext),
table1.create(apiContext),
table2.create(apiContext),
topic1.create(apiContext),
topic2.create(apiContext),
]);
// Add Owner & Tag to the table
await table1.visitEntityPage(page);
await addMultiOwner({
page,
ownerNames: [user1.getUserName()],
activatorBtnDataTestId: 'edit-owner',
resultTestId: 'data-assets-header',
endpoint: table1.endpoint,
type: 'Users',
});
await assignTag(page, 'PersonalData.Personal');
await table2.visitEntityPage(page);
await addMultiOwner({
page,
ownerNames: [user2.getUserName()],
activatorBtnDataTestId: 'edit-owner',
resultTestId: 'data-assets-header',
endpoint: table1.endpoint,
type: 'Users',
});
await assignTag(page, 'PII.None');
// Add Tier To the topic 1
await topic1.visitEntityPage(page);
await assignTier(page, 'Tier1', topic1.endpoint);
// Add Tier To the topic 2
await topic2.visitEntityPage(page);
await assignTier(page, 'Tier2', topic2.endpoint);
// Update Search Criteria here
searchCriteria = {
'owners.displayName.keyword': [user1.getUserName(), user2.getUserName()],
'tags.tagFQN': ['PersonalData.Personal', 'PII.None'],
'tier.tagFQN': ['Tier.Tier1', 'Tier.Tier2'],
'service.displayName.keyword': [table1.service.name, table2.service.name],
'database.displayName.keyword': [
table1.database.name,
table2.database.name,
],
'databaseSchema.displayName.keyword': [
table1.schema.name,
table2.schema.name,
],
'columns.name.keyword': ['email', 'shop_id'],
};
await afterAction();
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
await Promise.all([
user1.delete(apiContext),
user2.delete(apiContext),
table1.delete(apiContext),
table2.delete(apiContext),
topic1.delete(apiContext),
topic2.delete(apiContext),
]);
await afterAction();
});
test.beforeEach(async ({ page }) => {
await redirectToHomePage(page);
await sidebarClick(page, SidebarItem.EXPLORE);
});
FIELDS.forEach((field) => {
test(`Verify All conditions for ${field.id} field`, async ({ page }) => {
test.slow(true);
await verifyAllConditions(page, field, searchCriteria[field.name][0]);
});
});
Object.values(OPERATOR).forEach(({ name: operator }) => {
FIELDS.forEach((field) => {
// Rule based search
test(`Verify Rule functionality for field ${field.id} with ${operator} operator`, async ({
page,
}) => {
test.slow(true);
await runRuleGroupTests(page, field, operator, false, searchCriteria);
});
// Group based search
test(`Verify Group functionality for field ${field.id} with ${operator} operator`, async ({
page,
}) => {
test.slow(true);
await runRuleGroupTests(page, field, operator, true, searchCriteria);
});
});
});
});

View File

@ -0,0 +1,471 @@
/*
* Copyright 2024 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, Locator, Page } from '@playwright/test';
import { clickOutside } from './common';
type EntityFields = {
id: string;
name: string;
localSearch: boolean;
};
export const FIELDS: EntityFields[] = [
{
id: 'Owner',
name: 'owners.displayName.keyword',
localSearch: false,
},
{
id: 'Tags',
name: 'tags.tagFQN',
localSearch: false,
},
{
id: 'Tier',
name: 'tier.tagFQN',
localSearch: true,
},
{
id: 'Service',
name: 'service.displayName.keyword',
localSearch: false,
},
{
id: 'Database',
name: 'database.displayName.keyword',
localSearch: false,
},
{
id: 'Database Schema',
name: 'databaseSchema.displayName.keyword',
localSearch: false,
},
{
id: 'Column',
name: 'columns.name.keyword',
localSearch: false,
},
];
export const OPERATOR = {
AND: {
name: 'AND',
index: 1,
},
OR: {
name: 'OR',
index: 2,
},
};
export const CONDITIONS_MUST = {
equalTo: {
name: '==',
filter: 'must',
},
contains: {
name: 'Contains',
filter: 'must',
},
anyIn: {
name: 'Any in',
filter: 'must',
},
};
export const CONDITIONS_MUST_NOT = {
notEqualTo: {
name: '!=',
filter: 'must_not',
},
notIn: {
name: 'Not in',
filter: 'must_not',
},
notContains: {
name: 'Not contains',
filter: 'must_not',
},
};
export const NULL_CONDITIONS = {
isNull: {
name: 'Is null',
filter: 'empty',
},
isNotNull: {
name: 'Is not null',
filter: 'empty',
},
};
export const showAdvancedSearchDialog = async (page: Page) => {
await page.getByTestId('advance-search-button').click();
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
};
const selectOption = async (
page: Page,
dropdownLocator: Locator,
optionTitle: string
) => {
await dropdownLocator.click();
await page.click(`.ant-select-dropdown:visible [title="${optionTitle}"]`);
};
export const fillRule = async (
page: Page,
{
condition,
field,
searchCriteria,
index,
}: {
condition: string;
field: EntityFields;
searchCriteria: string;
index: number;
}
) => {
const ruleLocator = page.locator('.rule').nth(index - 1);
// Perform click on rule field
await selectOption(
page,
ruleLocator.locator('.rule--field .ant-select'),
field.id
);
// Perform click on operator
await selectOption(
page,
ruleLocator.locator('.rule--operator .ant-select'),
condition
);
if (searchCriteria) {
const inputElement = ruleLocator.locator(
'.rule--widget--TEXT input[type="text"]'
);
const searchData = field.localSearch
? searchCriteria
: searchCriteria.toLowerCase();
if (await inputElement.isVisible()) {
await inputElement.fill(searchData);
} else {
const dropdownInput = ruleLocator.locator(
'.widget--widget > .ant-select > .ant-select-selector input'
);
let aggregateRes;
if (!field.localSearch) {
aggregateRes = page.waitForResponse('/api/v1/search/aggregate?*');
}
await dropdownInput.click();
await dropdownInput.fill(searchData);
if (aggregateRes) {
await aggregateRes;
}
await page
.locator(`.ant-select-dropdown [title="${searchData}"]`)
.click();
}
await clickOutside(page);
}
};
export const checkMustPaths = async (
page: Page,
{ condition, field, searchCriteria, index }
) => {
const searchData = field.localSearch
? searchCriteria
: searchCriteria.toLowerCase();
await fillRule(page, {
condition,
field,
searchCriteria,
index,
});
const searchRes = page.waitForResponse(
'/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);
});
});
await expect(
page.getByTestId('advance-search-filter-container')
).toContainText(searchData);
};
export const checkMustNotPaths = async (
page: Page,
{ condition, field, searchCriteria, index }
) => {
const searchData = field.localSearch
? searchCriteria
: searchCriteria.toLowerCase();
await fillRule(page, {
condition,
field,
searchCriteria,
index,
});
const searchRes = page.waitForResponse(
'/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));
if (!['columns.name.keyword'].includes(field.name)) {
await res.json().then(async (json) => {
await expect(JSON.stringify(json.hits.hits)).not.toContain(
searchCriteria
);
});
}
});
await expect(
page.getByTestId('advance-search-filter-container')
).toContainText(searchData);
};
export const checkNullPaths = async (
page: Page,
{ condition, field, searchCriteria, index }
) => {
await fillRule(page, {
condition,
field,
searchCriteria,
index,
});
const searchRes = page.waitForResponse(
'/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 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 } }],
},
},
],
},
},
};
await expect(JSON.stringify(queryFilter)).toContain(
JSON.stringify(resultQuery)
);
});
};
export const verifyAllConditions = async (
page: Page,
field: EntityFields,
searchCriteria: string
) => {
// Check for Must conditions
for (const condition of Object.values(CONDITIONS_MUST)) {
await showAdvancedSearchDialog(page);
await checkMustPaths(page, {
condition: condition.name,
field,
searchCriteria: searchCriteria,
index: 1,
});
await page.getByTestId('clear-filters').click();
}
// Check for Must Not conditions
for (const condition of Object.values(CONDITIONS_MUST_NOT)) {
await showAdvancedSearchDialog(page);
await checkMustNotPaths(page, {
condition: condition.name,
field,
searchCriteria: searchCriteria,
index: 1,
});
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();
}
};
export const checkAddRuleOrGroupWithOperator = async (
page,
{
field,
operator,
condition1,
condition2,
searchCriteria1,
searchCriteria2,
}: {
field: EntityFields;
operator: string;
condition1: string;
condition2: string;
searchCriteria1: string;
searchCriteria2: string;
},
isGroupTest = false
) => {
await showAdvancedSearchDialog(page);
await fillRule(page, {
condition: condition1,
field,
searchCriteria: searchCriteria1,
index: 1,
});
if (!isGroupTest) {
await page.getByTestId('advanced-search-add-rule').nth(1).click();
} else {
await page.getByTestId('advanced-search-add-group').first().click();
}
await fillRule(page, {
condition: condition2,
field,
searchCriteria: searchCriteria2,
index: 2,
});
if (operator === 'OR') {
await page
.getByTestId('advanced-search-modal')
.getByRole('button', { name: 'Or' });
}
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);
}
}
});
});
};
export const runRuleGroupTests = async (
page: Page,
field: EntityFields,
operator: string,
isGroupTest: boolean,
searchCriteria: Record<string, string[]>
) => {
const searchCriteria1 = searchCriteria[field.name][0];
const searchCriteria2 = searchCriteria[field.name][1];
const testCases = [
{
condition1: CONDITIONS_MUST.equalTo.name,
condition2: CONDITIONS_MUST_NOT.notEqualTo.name,
},
{
condition1: CONDITIONS_MUST.contains.name,
condition2: CONDITIONS_MUST_NOT.notContains.name,
},
{
condition1: CONDITIONS_MUST.anyIn.name,
condition2: CONDITIONS_MUST_NOT.notIn.name,
},
];
for (const { condition1, condition2 } of testCases) {
await checkAddRuleOrGroupWithOperator(
page,
{
field,
operator,
condition1,
condition2,
searchCriteria1,
searchCriteria2,
},
isGroupTest
);
await page.getByTestId('clear-filters').click();
}
};

View File

@ -115,6 +115,7 @@ export const renderAdvanceSearchButtons: RenderSettings['renderButton'] = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CloseCircleOutlined as React.ForwardRefExoticComponent<any>
}
data-testid="advanced-search-delete-rule"
onClick={props?.onClick}
/>
);
@ -123,6 +124,7 @@ export const renderAdvanceSearchButtons: RenderSettings['renderButton'] = (
<Button
ghost
className="action action--ADD-RULE"
data-testid="advanced-search-add-rule"
icon={<PlusOutlined />}
type="primary"
onClick={props?.onClick}>
@ -133,6 +135,7 @@ export const renderAdvanceSearchButtons: RenderSettings['renderButton'] = (
return (
<Button
className="action action--ADD-GROUP"
data-testid="advanced-search-add-group"
icon={<PlusOutlined />}
type="primary"
onClick={props?.onClick}>
@ -147,6 +150,7 @@ export const renderAdvanceSearchButtons: RenderSettings['renderButton'] = (
})}
className="action action--DELETE cursor-pointer align-middle"
component={IconDeleteColored}
data-testid="advanced-search-delete-group"
style={{ fontSize: '16px' }}
onClick={props?.onClick as () => void}
/>