mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-05 15:48:03 +00:00
fix(ui): alphabetical sort order support for display name (#21745)
* add sort order support for display name * fix table sorting field for name * add tests * refactor code * add test for all entities * minor fix * fix sorting logic to match with ES behaviour * fix test * minor fix * fix flakyness
This commit is contained in:
parent
d36878409a
commit
d1b12650e3
@ -28,3 +28,22 @@ export const EXPECTED_BUCKETS = [
|
||||
'searchIndex',
|
||||
'mlmodel',
|
||||
];
|
||||
|
||||
export const DATA_ASSETS = [
|
||||
{ name: 'Table', filter: 'table' },
|
||||
{ name: 'Database', filter: 'database' },
|
||||
{ name: 'Database Schema', filter: 'databaseSchema' },
|
||||
{ name: 'Dashboard', filter: 'dashboard' },
|
||||
{ name: 'Dashboard Data Model', filter: 'dashboardDataModel' },
|
||||
{ name: 'Pipeline', filter: 'pipeline' },
|
||||
{ name: 'Topic', filter: 'topic' },
|
||||
{ name: 'ML Model', filter: 'mlmodel' },
|
||||
{ name: 'Container', filter: 'container' },
|
||||
{ name: 'Search Index', filter: 'searchIndex' },
|
||||
{ name: 'API Endpoint', filter: 'apiEndpoint' },
|
||||
{ name: 'API Collection', filter: 'apiCollection' },
|
||||
{ name: 'Stored Procedure', filter: 'storedProcedure' },
|
||||
{ name: 'Glossary Term', filter: 'glossaryTerm' },
|
||||
{ name: 'Tags', filter: 'tag' },
|
||||
{ name: 'Metrics', filter: 'metric' },
|
||||
];
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2025 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { test } from '@playwright/test';
|
||||
import { DATA_ASSETS } from '../../constant/explore';
|
||||
import { SidebarItem } from '../../constant/sidebar';
|
||||
import { EntityDataClass } from '../../support/entity/EntityDataClass';
|
||||
import { performAdminLogin } from '../../utils/admin';
|
||||
import { redirectToHomePage } from '../../utils/common';
|
||||
import { selectSortOrder, verifyEntitiesAreSorted } from '../../utils/explore';
|
||||
import { sidebarClick } from '../../utils/sidebar';
|
||||
|
||||
test.describe('Explore Sort Order Filter', () => {
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
test.slow(true);
|
||||
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll('Cleanup', async ({ browser }) => {
|
||||
test.slow(true);
|
||||
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await EntityDataClass.postRequisitesForTests(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
DATA_ASSETS.forEach(({ name, filter }) => {
|
||||
test(`${name}`, async ({ browser }) => {
|
||||
test.slow(true);
|
||||
|
||||
const { page, afterAction } = await performAdminLogin(browser);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await sidebarClick(page, SidebarItem.EXPLORE);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('button', { name: 'Data Assets' }).click();
|
||||
|
||||
await page.waitForSelector(
|
||||
'data-testid="drop-down-menu" data-testid="loader"',
|
||||
{
|
||||
state: 'detached',
|
||||
}
|
||||
);
|
||||
|
||||
await page.getByTestId(`${filter}-checkbox`).check();
|
||||
await page.getByTestId('update-btn').click();
|
||||
|
||||
await selectSortOrder(page, 'Name');
|
||||
await verifyEntitiesAreSorted(page);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
});
|
||||
});
|
@ -27,6 +27,7 @@ import { DashboardDataModelClass } from './DashboardDataModelClass';
|
||||
import { DatabaseClass } from './DatabaseClass';
|
||||
import { DatabaseSchemaClass } from './DatabaseSchemaClass';
|
||||
import { EntityDataClassCreationConfig } from './EntityDataClass.interface';
|
||||
import { MetricClass } from './MetricClass';
|
||||
import { MlModelClass } from './MlModelClass';
|
||||
import { PipelineClass } from './PipelineClass';
|
||||
import { SearchIndexClass } from './SearchIndexClass';
|
||||
@ -104,6 +105,7 @@ export class EntityDataClass {
|
||||
static readonly dataProduct1 = new DataProduct(this.domain1);
|
||||
static readonly dataProduct2 = new DataProduct(this.domain1);
|
||||
static readonly dataProduct3 = new DataProduct(this.domain2);
|
||||
static readonly metric1 = new MetricClass();
|
||||
|
||||
static async preRequisitesForTests(
|
||||
apiContext: APIRequestContext,
|
||||
@ -129,6 +131,7 @@ export class EntityDataClass {
|
||||
this.certificationTag1.create(apiContext),
|
||||
this.certificationTag2.create(apiContext),
|
||||
this.classification1.create(apiContext),
|
||||
this.metric1.create(apiContext),
|
||||
]
|
||||
: [];
|
||||
|
||||
@ -249,6 +252,7 @@ export class EntityDataClass {
|
||||
this.dataProduct1.delete(apiContext),
|
||||
this.dataProduct2.delete(apiContext),
|
||||
this.dataProduct3.delete(apiContext),
|
||||
this.metric1.delete(apiContext),
|
||||
]
|
||||
: [];
|
||||
|
||||
|
@ -154,3 +154,39 @@ export const validateBucketsForIndex = async (page: Page, index: string) => {
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
};
|
||||
|
||||
export const selectSortOrder = async (page: Page, sortOrder: string) => {
|
||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||
await page.getByTestId('sorting-dropdown-label').click();
|
||||
await page.waitForSelector(`role=menuitem[name="${sortOrder}"]`, {
|
||||
state: 'visible',
|
||||
});
|
||||
await page.getByRole('menuitem', { name: sortOrder }).click();
|
||||
|
||||
await expect(page.getByTestId('sorting-dropdown-label')).toHaveText(
|
||||
sortOrder
|
||||
);
|
||||
|
||||
await page.getByTestId('sort-order-button').click();
|
||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||
};
|
||||
|
||||
export const verifyEntitiesAreSorted = async (page: Page) => {
|
||||
const entityNames = await page.$$eval(
|
||||
'[data-testid="search-results"] .explore-search-card [data-testid="entity-link"]',
|
||||
(elements) => elements.map((el) => el.textContent?.trim() ?? '')
|
||||
);
|
||||
|
||||
// Normalize for case insensitivity, but retain punctuation
|
||||
const normalize = (str: string) => str.toLowerCase().trim();
|
||||
|
||||
// Sort using ASCII-based string comparison (ES behavior)
|
||||
const sortedEntityNames = [...entityNames].sort((a, b) => {
|
||||
const normA = normalize(a);
|
||||
const normB = normalize(b);
|
||||
|
||||
return normA < normB ? -1 : normA > normB ? 1 : 0;
|
||||
});
|
||||
|
||||
expect(entityNames).toEqual(sortedEntityNames);
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ export const tableSortingFields: SortingField[] = [
|
||||
},
|
||||
{
|
||||
name: i18n.t('label.name'),
|
||||
value: 'name.keyword',
|
||||
value: 'displayName.keyword',
|
||||
},
|
||||
{
|
||||
name: i18n.t('label.weekly-usage'),
|
||||
@ -65,7 +65,7 @@ export const entitySortingFields = [
|
||||
},
|
||||
{
|
||||
name: i18n.t('label.name'),
|
||||
value: 'name.keyword',
|
||||
value: 'displayName.keyword',
|
||||
},
|
||||
{ name: i18n.t('label.relevance'), value: '_score' },
|
||||
{
|
||||
@ -77,7 +77,7 @@ export const entitySortingFields = [
|
||||
export const tagSortingFields = [
|
||||
{
|
||||
name: i18n.t('label.name'),
|
||||
value: 'name.keyword',
|
||||
value: 'displayName.keyword',
|
||||
},
|
||||
{ name: i18n.t('label.relevance'), value: '_score' },
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user