Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
* limitations under the License.
*/

import { expect } from '@playwright/test';
import { EntityStatus } from '../../../src/generated/entity/data/searchIndex';
import { COMMON_TIER_TAG } from '../../constant/common';
import { DOMAIN_TAGS } from '../../constant/config';
import { SidebarItem } from '../../constant/sidebar';
import { DataProduct } from '../../support/domain/DataProduct';
import { EntityDataClass } from '../../support/entity/EntityDataClass';
import { MlModelClass } from '../../support/entity/MlModelClass';
import { TableClass } from '../../support/entity/TableClass';
import { TopicClass } from '../../support/entity/TopicClass';
import { Glossary } from '../../support/glossary/Glossary';
Expand All @@ -22,12 +27,17 @@ import { UserClass } from '../../support/user/UserClass';
import { performAdminLogin } from '../../utils/admin';
import {
FIELDS,
fillRule,
fillStaticListRule,
OPERATOR,
runRuleGroupTests,
runRuleGroupTestsWithNonExistingValue,
selectOption,
showAdvancedSearchDialog,
verifyAllConditions,
} from '../../utils/advancedSearch';
import { redirectToHomePage } from '../../utils/common';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import { sidebarClick } from '../../utils/sidebar';
import { test } from '../fixtures/pages';

Expand Down Expand Up @@ -285,8 +295,8 @@ test.describe('Advanced Search', { tag: ['@advanced-search'] }, () => {
table2.entityResponseData.name,
],
'project.keyword': [
EntityDataClass.dashboardDataModel1.entityResponseData.project,
EntityDataClass.dashboardDataModel2.entityResponseData.project,
EntityDataClass.dashboardDataModel1.entityResponseData.project || '',
EntityDataClass.dashboardDataModel2.entityResponseData.project || '',
],
'charts.displayName.keyword': [
EntityDataClass.dashboard1.chartsResponseData.displayName,
Expand Down Expand Up @@ -344,3 +354,279 @@ test.describe('Advanced Search', { tag: ['@advanced-search'] }, () => {
await runRuleGroupTestsWithNonExistingValue(page);
});
});

const ENTITY_STATUSES = Object.values(EntityStatus);

test.describe(
'Advanced Search - Entity Status Filter',
{ tag: [DOMAIN_TAGS.DISCOVERY] },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Bug: Dropped '@advanced-search' tag from Entity Status test describe

The original code tagged this test.describe with both ['@advanced-search', '@Discovery']. The refactor replaced it with [DOMAIN_TAGS.DISCOVERY] which only resolves to '@Discovery', dropping the @advanced-search tag.

The main test.describe('Advanced Search', ...) block at line 52 uses { tag: ['@advanced-search'] }. If CI or local test runs use --grep @advanced-search to run all advanced-search tests, this nested describe block will no longer be selected.

This may be intentional (aligning with a tag convention migration), but if not, the Entity Status filter tests will silently stop running in @advanced-search-filtered test suites.

Suggested fix:

{ tag: [DOMAIN_TAGS.DISCOVERY, '@advanced-search'] },

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

() => {
type StatusEntry = {
status: EntityStatus;
endpoint: string;
id: () => string;
displayName: () => string;
fqn: () => string;
};

let glossaryForStatus: Glossary;
let glossaryTermApproved: GlossaryTerm;
let mlModelDraft: MlModelClass;
let dataProductInReview: DataProduct;
let statusEntries: StatusEntry[];

test.beforeAll(
'Create mixed entity types with distinct entity statuses',
async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);

glossaryForStatus = new Glossary();
glossaryTermApproved = new GlossaryTerm(glossaryForStatus);
mlModelDraft = new MlModelClass();
dataProductInReview = new DataProduct();

await glossaryForStatus.create(apiContext);
await Promise.all([
glossaryTermApproved.create(apiContext),
mlModelDraft.create(apiContext),
dataProductInReview.create(apiContext),
]);

statusEntries = [
{
status: EntityStatus.Approved,
endpoint: 'glossaryTerms',
id: () => glossaryTermApproved.responseData.id,
displayName: () => glossaryTermApproved.data.displayName,
fqn: () => glossaryTermApproved.responseData.fullyQualifiedName,
},
{
status: EntityStatus.Draft,
endpoint: 'mlmodels',
id: () => mlModelDraft.entityResponseData.id,
displayName: () => mlModelDraft.entity.displayName,
fqn: () => mlModelDraft.entityResponseData.fullyQualifiedName,
},
{
status: EntityStatus.InReview,
endpoint: 'dataProducts',
id: () => dataProductInReview.responseData.id ?? '',
displayName: () => dataProductInReview.data.displayName,
fqn: () =>
dataProductInReview.responseData.fullyQualifiedName ?? '',
},
];

// Patch entityStatus on each entity
await Promise.all(
statusEntries.map(({ id, status, endpoint }) =>
apiContext.patch(`/api/v1/${endpoint}/${id()}`, {
data: [{ op: 'add', path: '/entityStatus', value: status }],
headers: { 'Content-Type': 'application/json-patch+json' },
})
)
);

await afterAction();
}
);

test.beforeEach(async ({ page }) => {
await redirectToHomePage(page);
await sidebarClick(page, SidebarItem.EXPLORE);
});

test('All entity status options are visible in the Status dropdown', async ({
page,
}) => {
await test.step('Open advanced search dialog', async () => {
await showAdvancedSearchDialog(page);
});

await test.step('Select Status field and == operator', async () => {
const ruleLocator = page.locator('.rule').nth(0);
await selectOption(
page,
ruleLocator.locator('.rule--field .ant-select'),
'Status',
true
);
await selectOption(
page,
ruleLocator.locator('.rule--operator .ant-select'),
'=='
);
});

await test.step('Open Status value dropdown and verify all hard-coded options appear', async () => {
const ruleLocator = page.locator('.rule').nth(0);
await ruleLocator.locator('.widget--widget > .ant-select').click();

const dropdown = page
.locator('.ant-select-dropdown')
.filter({ hasText: EntityStatus.Approved })
.last();

await expect(dropdown).toBeVisible();

for (const status of ENTITY_STATUSES) {
await expect(
dropdown
.locator('.ant-select-item-option')
.filter({ hasText: new RegExp(`^${status}$`, 'i') })
.first()
).toBeVisible();
}
});
});

test('Filtering by status "==" shows matching entity and hides others across entity types', async ({
page,
}) => {
test.slow();

for (const entry of statusEntries) {
const { status } = entry;
const matchedName = entry.displayName();

await test.step(`Apply Status == "${status}" AND Name == "${matchedName}"`, async () => {
await showAdvancedSearchDialog(page);

await fillStaticListRule(page, {
fieldLabel: 'Status',
condition: '==',
value: status,
ruleIndex: 1,
});

await page.getByTestId('advanced-search-add-rule').nth(1).click();

await fillRule(page, {
condition: '==',
field: { id: 'Display Name', name: 'displayName.keyword' },
searchCriteria: matchedName,
index: 2,
});

const searchRes = page.waitForResponse(
'/api/v1/search/query?*index=dataAsset*'
);
await page.getByTestId('apply-btn').click();
await searchRes;
await waitForAllLoadersToDisappear(page);
});

await test.step('Filter chip shows the applied status', async () => {
await expect(
page.getByTestId('advance-search-filter-container')
).toContainText(`'${status}'`);
});

await test.step(`"${matchedName}" (${entry.endpoint}) is visible`, async () => {
await expect(
page.getByTestId(`table-data-card_${entry.fqn()}`)
).toBeVisible();
});

await test.step('Entities with other statuses are not in results', async () => {
const otherEntries = statusEntries.filter((e) => e.status !== status);

for (const other of otherEntries) {
await expect(
page.getByTestId(`table-data-card_${other.fqn()}`)
).not.toBeVisible();
}
});

await page.getByTestId('clear-filters').click();
}
});

test('Filtering by status "!=" excludes matched entity but shows all other entity types', async ({
page,
}) => {
const targetEntry = statusEntries.find(
(e) => e.status === EntityStatus.Approved
)!;
const approvedName = targetEntry.displayName();
const otherEntries = statusEntries.filter(
(e) => e.status !== EntityStatus.Approved
);

await test.step('Apply Status != "Approved" AND Name == approved entity name', async () => {
await showAdvancedSearchDialog(page);

await fillStaticListRule(page, {
fieldLabel: 'Status',
condition: '!=',
value: EntityStatus.Approved,
ruleIndex: 1,
});

await page.getByTestId('advanced-search-add-rule').nth(1).click();

await fillRule(page, {
condition: '==',
field: { id: 'Display Name', name: 'displayName.keyword' },
searchCriteria: approvedName,
index: 2,
});

const searchRes = page.waitForResponse(
'/api/v1/search/query?*index=dataAsset*'
);
await page.getByTestId('apply-btn').click();
await searchRes;
await waitForAllLoadersToDisappear(page);
});

await test.step('Filter chip reflects the != condition', async () => {
await expect(
page.getByTestId('advance-search-filter-container')
).toContainText(`'${EntityStatus.Approved}'`);
});

await test.step('GlossaryTerm with Approved status is not visible', async () => {
await expect(
page.getByTestId(`table-data-card_${targetEntry.fqn()}`)
).not.toBeVisible();
});

await test.step('Draft and In Review entities appear when searched by their name and non-Approved status', async () => {
for (const entry of otherEntries) {
await page.getByTestId('clear-filters').click();
await showAdvancedSearchDialog(page);

await fillStaticListRule(page, {
fieldLabel: 'Status',
condition: '!=',
value: EntityStatus.Approved,
ruleIndex: 1,
});

await page.getByTestId('advanced-search-add-rule').nth(1).click();

await fillRule(page, {
condition: '==',
field: { id: 'Display Name', name: 'displayName.keyword' },
searchCriteria: entry.displayName(),
index: 2,
});

const searchRes = page.waitForResponse(
'/api/v1/search/query?*index=dataAsset*'
);
await page.getByTestId('apply-btn').click();
await searchRes;
await waitForAllLoadersToDisappear(page);

await expect(
page.getByTestId(`table-data-card_${entry.fqn()}`)
).toBeVisible();
}
});

await page.getByTestId('clear-filters').click();
});
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,43 @@ export const runRuleGroupTestsWithNonExistingValue = async (page: Page) => {
await expect(dropdownText).not.toContainText('Loading...');
};

// For fields backed by hard-coded listValues (no aggregate API call), options are
// rendered immediately — use selectOption directly instead of fillRule which waits
// for a network response that never comes.
export const fillStaticListRule = async (
page: Page,
{
fieldLabel,
condition,
value,
ruleIndex,
}: {
fieldLabel: string;
condition: string;
value: string;
ruleIndex: number;
}
) => {
const ruleLocator = page.locator('.rule').nth(ruleIndex - 1);

await selectOption(
page,
ruleLocator.locator('.rule--field .ant-select'),
fieldLabel,
true
);
await selectOption(
page,
ruleLocator.locator('.rule--operator .ant-select'),
condition
);
await selectOption(
page,
ruleLocator.locator('.widget--widget > .ant-select'),
value
);
};

export const getFieldsSuggestionSearchText = (
fieldLabel: string,
data: Record<string, string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export const GLOSSARY_DROPDOWN_ITEMS = [
},
{
label: 'label.status',
key: EntityFields.GLOSSARY_TERM_STATUS,
key: EntityFields.ENTITY_STATUS,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export enum EntityFields {
API_COLLECTION = 'apiCollection.displayName.keyword',
CHART = 'charts.displayName.keyword',
TASK = 'tasks.displayName.keyword',
GLOSSARY_TERM_STATUS = 'entityStatus',
ENTITY_STATUS = 'entityStatus',
REQUEST_SCHEMA_FIELD = 'requestSchema.schemaFields.name.keyword',
RESPONSE_SCHEMA_FIELD = 'responseSchema.schemaFields.name.keyword',
SERVICE_NAME = 'service.name.keyword',
Expand Down
Loading
Loading