Skip to content

Commit f759c10

Browse files
authored
fix(ui): backport Suspense loader fallbacks to 1.13 (#31827)
* Fix UI Suspense loader fallbacks (#29746) * fix(ui): reduce suspense loader flashes * fix(ui): scope suspense loading states * fix(ui): avoid lazy entity header title * fix(ui): keep entity header title stable * fix(ui): align entity page loading states * fix multiple loaders * address comments * address comments * nit * address comments * address comments * address comments * fix failing tests * revert * fix failing tests * address comments and fix checkstyle * fix(ui): add tab content suspense fallbacks * address comments * test(ui): preserve 1.13 landing page test setup * test(ui): align backport tests with 1.13 * fix(ui): stabilize suspense fallback backport * test(ui): wait for glossary term description
1 parent 616e1cd commit f759c10

78 files changed

Lines changed: 680 additions & 372 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/GlobalPageSize.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,18 @@ test.describe('Table & Data Model columns table pagination', () => {
2828
await waitForAllLoadersToDisappear(page);
2929

3030
// Change page size to 25
31-
await page.getByTestId('page-size-selection-dropdown').click();
32-
await page.getByRole('menuitem', { name: '25 / Page' }).click();
31+
const tablePageSizeDropdown = page.getByTestId(
32+
'page-size-selection-dropdown'
33+
);
34+
await tablePageSizeDropdown.scrollIntoViewIfNeeded();
35+
await expect(tablePageSizeDropdown).toBeVisible();
36+
await tablePageSizeDropdown.hover();
37+
38+
const tablePageSizeOption = page
39+
.locator('.ant-dropdown:not(.ant-dropdown-hidden)')
40+
.getByRole('menuitem', { name: '25 / Page' });
41+
await expect(tablePageSizeOption).toBeVisible();
42+
await tablePageSizeOption.click();
3343

3444
await waitForAllLoadersToDisappear(page);
3545

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -342,59 +342,34 @@ test.describe.serial('Domain and Data Product Asset Counts', () => {
342342
await waitForAllLoadersToDisappear(page);
343343
await sidebarClick(page, SidebarItem.DATA_PRODUCT);
344344
await selectDataProduct(page, dataProduct.data);
345+
await waitForAllLoadersToDisappear(page);
345346

347+
const dataProductAssetsResponse = page.waitForResponse(
348+
(response) =>
349+
response.url().includes('/api/v1/dataProducts/name/') &&
350+
response.url().includes('fields=domains%2Cassets') &&
351+
response.request().method() === 'GET'
352+
);
346353
await page.getByTestId('assets').click();
354+
await dataProductAssetsResponse;
347355

348-
await page
349-
.getByTestId('loader')
350-
.waitFor({
351-
state: 'detached',
352-
timeout: 10000,
353-
})
354-
.catch(() => {
355-
/* ignore if loader not found */
356-
});
357-
358-
let hasAssets = true;
359-
while (hasAssets) {
360-
const checkboxes = page.locator(
361-
'[data-testid^="table-data-card_"] input[type="checkbox"]'
362-
);
363-
const count = await checkboxes.count();
364-
365-
if (count === 0) {
366-
hasAssets = false;
367-
break;
368-
}
369-
370-
const selectAll = page.getByRole('checkbox', { name: 'Select All' });
371-
if (await selectAll.isVisible()) {
372-
await selectAll.check();
373-
} else {
374-
for (let i = 0; i < count; i++) {
375-
await checkboxes.nth(i).check();
376-
}
377-
}
378-
379-
const previousCount = count;
380-
const removeRes = page.waitForResponse('**/assets/remove');
381-
await page.getByTestId('delete-all-button').click();
382-
await removeRes;
383-
384-
await expect
385-
.poll(
386-
async () =>
387-
page
388-
.locator(
389-
'[data-testid^="table-data-card_"] input[type="checkbox"]'
390-
)
391-
.count(),
392-
{ timeout: 10_000 }
393-
)
394-
.toBeLessThan(previousCount);
356+
// The card list paints after the assets response resolves, and count()
357+
// does not auto-wait. Wait for a card before selecting every attached asset.
358+
await waitForAllLoadersToDisappear(page);
359+
const assetCard = page.locator('[data-testid^="table-data-card_"]');
360+
await assetCard.first().waitFor({ state: 'visible' });
361+
362+
const attachedCount = await assetCard.count();
363+
for (let i = 0; i < attachedCount; i++) {
364+
await assetCard.nth(i).locator('input[type="checkbox"]').check();
395365
}
396366

367+
const removeRes = page.waitForResponse('**/assets/remove');
368+
await page.getByTestId('delete-all-button').click();
369+
await removeRes;
370+
397371
await page.reload();
372+
await waitForAllLoadersToDisappear(page);
398373
await checkAssetsCount(page, 0);
399374

400375
await redirectToHomePage(page);

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,11 +1258,11 @@ test.describe('Glossary tests', () => {
12581258
await selectActiveGlossary(page, glossary1.data.displayName);
12591259
await selectActiveGlossaryTerm(page, glossaryTerm1.data.displayName);
12601260

1261-
const viewerContainerText = await page.textContent(
1262-
'[data-testid="viewer-container"]'
1263-
);
1264-
1265-
expect(viewerContainerText).toContain('Updated description');
1261+
// The description renders after the term page finishes loading, so assert
1262+
// on the locator rather than reading textContent once.
1263+
await expect(
1264+
page.locator('[data-testid="viewer-container"]')
1265+
).toContainText('Updated description');
12661266
} finally {
12671267
await glossaryTerm1.delete(apiContext);
12681268
await glossary1.delete(apiContext);

openmetadata-ui/src/main/resources/ui/playwright/utils/odcsImportExport.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* See the License for the specific language governing permissions and
1111
* limitations under the License.
1212
*/
13-
import { Page, Response } from '@playwright/test';
13+
import { expect, Page, Response } from '@playwright/test';
1414
import { TableClass } from '../support/entity/TableClass';
1515
import { toastNotification } from './common';
1616
import { waitForAllLoadersToDisappear } from './entity';
@@ -25,17 +25,22 @@ export const openODCSImportDropdown = async (page: Page) => {
2525
const addButton = page.getByTestId('add-contract-button');
2626
const manageButton = page.getByTestId('manage-contract-actions');
2727

28-
const addButtonVisible = await addButton.isVisible().catch(() => false);
29-
const manageButtonVisible = await manageButton.isVisible().catch(() => false);
28+
// Contract actions can render after the page loader disappears, so wait for
29+
// either valid entry point instead of making a one-shot visibility decision.
30+
await expect(addButton.or(manageButton)).toBeVisible({ timeout: 15000 });
3031

31-
if (addButtonVisible) {
32+
if (await addButton.isVisible()) {
3233
await addButton.click();
3334
await page.getByTestId('add-contract-menu').waitFor({
3435
state: 'visible',
3536
timeout: 10000,
3637
});
37-
} else if (manageButtonVisible) {
38+
} else {
3839
await manageButton.click();
40+
await page.locator('.contract-action-dropdown').waitFor({
41+
state: 'visible',
42+
timeout: 10000,
43+
});
3944
}
4045
};
4146

openmetadata-ui/src/main/resources/ui/playwright/utils/permission.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,12 @@ export const validateViewPermissions = async (
137137
await expect(page.locator('[data-testid="add-domain"]')).not.toBeVisible();
138138

139139
if (permission?.editDisplayName) {
140-
expect(
141-
await page.locator('[data-testid="edit-displayName-button"]').count()
142-
).toBeGreaterThan(0);
140+
const editDisplayNameButton = page.locator(
141+
'[data-testid="edit-displayName-button"]'
142+
);
143+
await expect(editDisplayNameButton.first()).toBeVisible({
144+
timeout: 30_000,
145+
});
143146
} else {
144147
await expect(
145148
page.locator('[data-testid="edit-displayName-button"]')

openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import { APP_ROUTER_ROUTES } from '../../constants/router.constants';
1919
import { useApplicationStore } from '../../hooks/useApplicationStore';
2020
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
2121
import Loader from '../common/Loader/Loader';
22-
import withSuspenseFallback from './withSuspenseFallback';
22+
import { withPageSuspenseFallback } from './withSuspenseFallback';
2323

24-
const AuthenticatedApp = withSuspenseFallback(
24+
const AuthenticatedApp = withPageSuspenseFallback(
2525
lazy(() => import('./AuthenticatedApp'))
2626
);
2727

28-
const AuthenticatedRoutes = withSuspenseFallback(
28+
const AuthenticatedRoutes = withPageSuspenseFallback(
2929
lazy(() =>
3030
import('./AuthenticatedRoutes').then((m) => ({
3131
default: m.AuthenticatedRoutes,
@@ -34,27 +34,27 @@ const AuthenticatedRoutes = withSuspenseFallback(
3434
);
3535

3636
// Lazy-load infrequently-visited unauthenticated pages
37-
const AccessNotAllowedPage = withSuspenseFallback(
37+
const AccessNotAllowedPage = withPageSuspenseFallback(
3838
lazy(() => import('../../pages/AccessNotAllowedPage/AccessNotAllowedPage'))
3939
);
4040

41-
const LogoutPage = withSuspenseFallback(
41+
const LogoutPage = withPageSuspenseFallback(
4242
lazy(() =>
4343
import('../../pages/LogoutPage/LogoutPage').then((m) => ({
4444
default: m.LogoutPage,
4545
}))
4646
)
4747
);
4848

49-
const PageNotFound = withSuspenseFallback(
49+
const PageNotFound = withPageSuspenseFallback(
5050
lazy(() => import('../../pages/PageNotFound/PageNotFound'))
5151
);
5252

53-
const SamlCallback = withSuspenseFallback(
53+
const SamlCallback = withPageSuspenseFallback(
5454
lazy(() => import('../../pages/SamlCallback'))
5555
);
5656

57-
const SignUpPage = withSuspenseFallback(
57+
const SignUpPage = withPageSuspenseFallback(
5858
lazy(() => import('../../pages/SignUp/SignUpPage'))
5959
);
6060

0 commit comments

Comments
 (0)