Skip to content

Commit cb12710

Browse files
authored
refactor(ui): migrate toast notifications to core-components toast (open-metadata#29545)
* refactor(ui): migrate toast notifications to core-components toast Replace useAlertStore + inline AlertBar pattern with toast from @openmetadata/ui-core-components across the application. - ToastUtils: showErrorToast/showSuccessToast/showWarningToast/showInfoToast now delegate to toast.error/success/warning/info - AlertBar: decoupled from useAlertStore, now a pure display component - Remove store-driven AlertBar renders from PageLayoutV1, OntologyEntityPanel, auth pages (ForgotPassword, SignInPage, BasicSignup, ResetPassword), and ContextCenter pages (Documents, Memories, Articles, Dashboard) - Delete useAlertStore (addAlert had zero callers after ToastUtils migration) AlertBar component retained for standalone in-page use (forms/modals). * fix(ui): add alert-message testid to Toast for Playwright compatibility * fix(ui): fix toast dismiss timings for warnings and errors Rule-violation warnings were dismissing after 2.2s (core default) instead of 5s, diverging from showWarningToast and the old useAlertStore behavior. Non-rule errors now correctly pass empty options so toast.error keeps its timeout:0 (stays until dismissed). Also bumps the global default timeout from 2200ms to 3500ms. * style(ui): reformat toast.error call for readability * lint fix * fix(e2e): scope alert-icon check to matched toast in toastNotification Strict mode violation when multiple toasts appear simultaneously — getByTestId('alert-icon') resolved to 2 elements. Scope both the wait and the icon assertion to the toast matching the expected message. * fix(e2e): handle duplicate toasts in toastNotification; reduce success timeout .filter({ hasText }) resolves to 2 elements when the same success message fires twice in quick succession. Add .first() to avoid strict mode violation. Also reduce showSuccessToast default from 5000ms to 3000ms to shrink the overlap window. * fix(e2e): handle duplicate toasts in toastNotification; reduce success timeout - Replace raw getByText toast assertions in TestLibrary.spec.ts with toastNotification() to avoid strict mode violations when duplicate toasts appear simultaneously. - Fix announcementForm: toast.success has no close button, so replace alert-icon-close click with toastNotification() verification. * fix(e2e): replace raw alert-bar assertions with toastNotification() Strict mode violations when multiple toasts appear simultaneously — unscoped getByTestId('alert-bar') / toHaveText resolves to 2+ elements. Replace all such patterns with toastNotification() which scopes to the matching toast via .filter({ hasText }).first(). Files updated: Login.spec.ts, user.ts, entity.ts, DataProductRename, MutuallyExclusiveColumnTags, NavigationBlocker, TestSuitePipelineRedeploy, Domains, Entity.
1 parent d077465 commit cb12710

37 files changed

Lines changed: 92 additions & 460 deletions

File tree

openmetadata-ui-core-components/src/main/resources/ui/src/components/application/toast/toast-store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const toastQueue = new ToastQueue<ToastContent>({
2828
export interface ShowToastOptions {
2929
/** Whether the toast should dismiss automatically. Defaults to true. */
3030
autoDismiss?: boolean;
31-
/** Auto-dismiss delay in ms. Defaults to 2200. Pass 0 to keep the toast until manually dismissed. */
31+
/** Auto-dismiss delay in ms. Defaults to 3500. Pass 0 to keep the toast until manually dismissed. */
3232
timeout?: number;
3333
}
3434

@@ -37,7 +37,7 @@ function add(
3737
variant: ToastVariant,
3838
options?: ShowToastOptions
3939
): string {
40-
const { autoDismiss = true, timeout = 2200 } = options ?? {};
40+
const { autoDismiss = true, timeout = 3500 } = options ?? {};
4141

4242
return toastQueue.add(
4343
{ message, variant },

openmetadata-ui-core-components/src/main/resources/ui/src/components/application/toast/toast.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@ export const Toast = ({ toast }: ToastProps) => {
8888
className={cx('tw:size-4', config.iconClass)}
8989
/>
9090
</span>
91-
{typeof messageOrNode === 'string' ? (
92-
<span>{messageOrNode}</span>
93-
) : (
94-
(messageOrNode as ReactNode)
95-
)}
91+
<span data-testid="alert-message">
92+
{typeof messageOrNode === 'string'
93+
? messageOrNode
94+
: (messageOrNode as ReactNode)}
95+
</span>
9696
{showClose && state && (
9797
<Button
9898
aria-label="Close"

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
createNewPage,
2323
getApiContext,
2424
redirectToHomePage,
25+
toastNotification,
2526
uuid,
2627
} from '../../utils/common';
2728
import {
@@ -396,13 +397,7 @@ test.describe('Data Product Rename', () => {
396397
// Verify the response status is 400 (Bad Request)
397398
expect(response.status()).toBe(400);
398399

399-
// Verify an error alert is shown
400-
await expect(page.getByTestId('alert-bar')).toBeVisible();
401-
402-
// Verify the error message contains information about the duplicate name
403-
await expect(page.getByTestId('alert-message')).toContainText(
404-
'already exists'
405-
);
400+
await toastNotification(page, /already exists/i);
406401
} finally {
407402
await dataProduct1.delete(apiContext);
408403
await dataProduct2.delete(apiContext);

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
*/
1313
import test, { expect } from '@playwright/test';
1414
import { DOMAIN_TAGS } from '../../../constant/config';
15-
import { getApiContext, redirectToHomePage, uuid } from '../../../utils/common';
15+
import {
16+
getApiContext,
17+
redirectToHomePage,
18+
toastNotification,
19+
uuid,
20+
} from '../../../utils/common';
1621
import { findSystemTestDefinition } from '../../../utils/testCases';
1722

1823
const TEST_DEFINITION_NAME = `AaroCustomTestDefinition${uuid()}`;
@@ -155,7 +160,7 @@ test.describe(
155160
expect(responseData.status()).toBe(201);
156161

157162
// Wait for success toast
158-
await expect(page.getByText(/created successfully/i)).toBeVisible();
163+
await toastNotification(page, /created successfully/i);
159164

160165
// Verify test definition appears in table
161166
await expect(page.getByTestId(TEST_DEFINITION_NAME)).toBeVisible();
@@ -206,7 +211,7 @@ test.describe(
206211
expect(responseData.status()).toBe(200);
207212

208213
// Wait for success toast
209-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
214+
await toastNotification(page, /updated successfully/i);
210215
});
211216

212217
await test.step('should enable/disable test definition', async () => {
@@ -235,7 +240,7 @@ test.describe(
235240
expect(responseData.status()).toBe(200);
236241

237242
// Wait for success toast
238-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
243+
await toastNotification(page, /updated successfully/i);
239244

240245
// Verify switch state changed
241246
await expect(firstSwitch).toHaveAttribute(
@@ -280,7 +285,7 @@ test.describe(
280285
expect(response.status()).toBe(200);
281286

282287
// Wait for success toast
283-
await expect(page.getByText(/deleted successfully/i)).toBeVisible();
288+
await toastNotification(page, /deleted successfully/i);
284289

285290
// Verify test definition is removed from table
286291
await expect(page.getByText(TEST_DEFINITION_NAME)).not.toBeVisible();
@@ -391,7 +396,7 @@ test.describe(
391396
const responseBody = await responseData.json();
392397
createdTestDefinitionId = responseBody.id;
393398

394-
await expect(page.getByText(/created successfully/i)).toBeVisible();
399+
await toastNotification(page, /created successfully/i);
395400
});
396401
} finally {
397402
if (createdTestDefinitionId) {
@@ -631,7 +636,7 @@ test.describe(
631636
const responseData = await createResponse;
632637
expect(responseData.status()).toBe(201);
633638

634-
await expect(page.getByText(/created successfully/i)).toBeVisible();
639+
await toastNotification(page, /created successfully/i);
635640
await expect(page.getByTestId(EXTERNAL_TEST_NAME)).toBeVisible();
636641
});
637642

@@ -720,7 +725,7 @@ test.describe(
720725
expect(updatedBody.parameterDefinition[0].dataType).toBeUndefined();
721726
expect(updatedBody.parameterDefinition[0].description).toBeUndefined();
722727

723-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
728+
await toastNotification(page, /updated successfully/i);
724729
});
725730

726731
await test.step('Delete external test definition', async () => {
@@ -750,7 +755,7 @@ test.describe(
750755
const response = await deleteResponse;
751756
expect(response.status()).toBe(200);
752757

753-
await expect(page.getByText(/deleted successfully/i)).toBeVisible();
758+
await toastNotification(page, /deleted successfully/i);
754759
await expect(page.getByTestId(EXTERNAL_TEST_NAME)).not.toBeVisible();
755760
});
756761
});
@@ -831,7 +836,7 @@ test.describe(
831836
const createdData = await responseData.json();
832837
createdTestId = createdData.id;
833838

834-
await expect(page.getByText(/created successfully/i)).toBeVisible();
839+
await toastNotification(page, /created successfully/i);
835840
await expect(
836841
page.getByTestId(SUPPORTED_SERVICES_TEST_NAME)
837842
).toBeVisible();
@@ -953,7 +958,7 @@ test.describe(
953958
expect(updatedData.supportedServices).toContain('BigQuery');
954959
expect(updatedData.supportedServices).not.toContain('MySql');
955960

956-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
961+
await toastNotification(page, /updated successfully/i);
957962
});
958963

959964
await test.step('Verify updated supported services are persisted', async () => {
@@ -1034,7 +1039,7 @@ test.describe(
10341039
updatedData.supportedServices.length === 0
10351040
).toBeTruthy();
10361041

1037-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
1042+
await toastNotification(page, /updated successfully/i);
10381043
});
10391044

10401045
await test.step('Delete test definition', async () => {
@@ -1060,7 +1065,7 @@ test.describe(
10601065
const response = await deleteResponse;
10611066
expect(response.status()).toBe(200);
10621067

1063-
await expect(page.getByText(/deleted successfully/i)).toBeVisible();
1068+
await toastNotification(page, /deleted successfully/i);
10641069
await expect(
10651070
page.getByTestId(SUPPORTED_SERVICES_TEST_NAME)
10661071
).not.toBeVisible();
@@ -1114,7 +1119,7 @@ test.describe(
11141119

11151120
const responseData = await createResponse;
11161121
expect(responseData.status()).toBe(201);
1117-
await expect(page.getByText(/created successfully/i)).toBeVisible();
1122+
await toastNotification(page, /created successfully/i);
11181123
});
11191124

11201125
await test.step('Change page size to 25', async () => {
@@ -1190,7 +1195,7 @@ test.describe(
11901195
const updateResponse = await patchResponse;
11911196
expect(updateResponse.status()).toBe(200);
11921197

1193-
await expect(page.getByText(/updated successfully/i)).toBeVisible();
1198+
await toastNotification(page, /updated successfully/i);
11941199

11951200
// Verify we stayed on the same page (previous button state should be unchanged)
11961201
if (prevDisabledBefore) {
@@ -1232,7 +1237,7 @@ test.describe(
12321237
// Wait for the GET that happens after delete (page reset + fetch)
12331238
await getResponse;
12341239

1235-
await expect(page.getByText(/deleted successfully/i)).toBeVisible();
1240+
await toastNotification(page, /deleted successfully/i);
12361241

12371242
// Previous button should be disabled on first page
12381243
const previousButton = page.getByTestId('previous');

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
import { expect, test } from '@playwright/test';
1414
import { PLAYWRIGHT_BASIC_TEST_TAG_OBJ } from '../../constant/config';
1515
import { TableClass } from '../../support/entity/TableClass';
16-
import { createNewPage, redirectToHomePage } from '../../utils/common';
16+
import {
17+
createNewPage,
18+
redirectToHomePage,
19+
toastNotification,
20+
} from '../../utils/common';
1721
import { waitForAllLoadersToDisappear } from '../../utils/entity';
1822

1923
const table = new TableClass();
@@ -101,13 +105,7 @@ test(
101105
await page.click('[data-testid="saveAssociatedTag"]');
102106
await errorResponse;
103107

104-
// Verify that error alert is displayed
105-
await expect(page.getByTestId('alert-bar')).toBeVisible();
106-
107-
// Verify the error message contains information about mutually exclusive tags
108-
await expect(page.getByTestId('alert-message')).toContainText(
109-
'mutually exclusive'
110-
);
108+
await toastNotification(page, /mutually exclusive/i);
111109

112110
// Verify that the dropdown closes after error
113111
await expect(page.locator('.ant-select-dropdown')).not.toBeVisible();

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { PLAYWRIGHT_BASIC_TEST_TAG_OBJ } from '../../constant/config';
1515
import { PersonaClass } from '../../support/persona/PersonaClass';
1616
import { UserClass } from '../../support/user/UserClass';
1717
import { performAdminLogin } from '../../utils/admin';
18-
import { redirectToHomePage } from '../../utils/common';
18+
import { redirectToHomePage, toastNotification } from '../../utils/common';
1919
import {
2020
navigateToCustomizeLandingPage,
2121
removeAndCheckWidget,
@@ -216,10 +216,7 @@ test.describe('Navigation Blocker Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => {
216216
await adminPage.locator('[data-testid="save-button"]').click();
217217
await saveResponse;
218218

219-
// Wait for success toast and save button to be disabled
220-
await expect(
221-
adminPage.locator('[data-testid="alert-message"]')
222-
).toContainText('Page layout created successfully.');
219+
await toastNotification(adminPage, /Page layout created successfully/i);
223220
await expect(
224221
adminPage.locator('[data-testid="save-button"]')
225222
).toBeDisabled();

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import test, { expect } from '@playwright/test';
1414
import { PLAYWRIGHT_INGESTION_TAG_OBJ } from '../../constant/config';
1515
import { GlobalSettingOptions } from '../../constant/settings';
1616
import { TableClass } from '../../support/entity/TableClass';
17-
import { createNewPage, redirectToHomePage } from '../../utils/common';
17+
import {
18+
createNewPage,
19+
redirectToHomePage,
20+
toastNotification,
21+
} from '../../utils/common';
1822
import { settingClick } from '../../utils/sidebar';
1923

2024
// use the admin user to login
@@ -69,9 +73,7 @@ test.describe('Bulk Re-Deploy pipelines ', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
6973
await page.getByRole('button', { name: 'Re Deploy' }).click();
7074
await redeployResponse;
7175

72-
await expect(page.getByTestId('alert-bar')).toHaveText(
73-
/Pipelines Re Deploy Successfully/i
74-
);
76+
await toastNotification(page, /Pipelines Re Deploy Successfully/i);
7577
});
7678

7779
// TODO: Add test to verify the re-deployed pipelines for Database, Dashboard and other entities

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
descriptionBox,
4444
getApiContext,
4545
redirectToHomePage,
46+
toastNotification,
4647
uuid,
4748
visitGlossaryPage,
4849
} from '../../utils/common';
@@ -2729,13 +2730,7 @@ test.describe('Domain Rename Comprehensive Tests', () => {
27292730
// Verify the response status is 409 (Conflict) or 400 (Bad Request)
27302731
expect([400, 409]).toContain(response.status());
27312732

2732-
// Verify an error toast/alert is shown
2733-
await expect(page.getByTestId('alert-bar')).toBeVisible();
2734-
2735-
// Verify the error message contains information about the duplicate name
2736-
await expect(page.getByTestId('alert-message')).toContainText(
2737-
/already exists/i
2738-
);
2733+
await toastNotification(page, /already exists/i);
27392734
} finally {
27402735
await domain1.delete(apiContext);
27412736
await domain2.delete(apiContext);

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
getToken,
4747
redirectToHomePage,
4848
removeSingleSelectDomain,
49+
toastNotification,
4950
uuid,
5051
verifyDomainPropagation,
5152
} from '../../utils/common';
@@ -1597,12 +1598,7 @@ Object.entries(entities).forEach(([key, EntityClass]) => {
15971598
await expect(saveButton).toBeEnabled();
15981599
await saveButton.click();
15991600
await saveResponse;
1600-
await expect(
1601-
page
1602-
.locator('.column-detail-panel')
1603-
.getByTestId('alert-bar')
1604-
.getByTestId('alert-message')
1605-
).toContainText('Description updated successfully');
1601+
await toastNotification(page, /Description updated successfully/i);
16061602

16071603
await waitForAllLoadersToDisappear(page);
16081604

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
clickOutside,
2020
getDefaultAdminAPIContext,
2121
redirectToHomePage,
22+
toastNotification,
2223
visitOwnProfilePage,
2324
} from '../../utils/common';
2425
import { waitForAllLoadersToDisappear } from '../../utils/entity';
@@ -124,9 +125,7 @@ test.describe('Login flow should work properly', () => {
124125
await page.locator('[data-testid="login"]').click();
125126
await loginResponse;
126127

127-
await expect(page.locator('[data-testid="alert-bar"]')).toHaveText(
128-
LOGIN_ERROR_MESSAGE
129-
);
128+
await toastNotification(page, LOGIN_ERROR_MESSAGE);
130129

131130
// Login with invalid password
132131
await page.fill('#email', CREDENTIALS.email);
@@ -135,9 +134,7 @@ test.describe('Login flow should work properly', () => {
135134
await page.locator('[data-testid="login"]').click();
136135
await loginResponse2;
137136

138-
await expect(page.locator('[data-testid="alert-bar"]')).toHaveText(
139-
LOGIN_ERROR_MESSAGE
140-
);
137+
await toastNotification(page, LOGIN_ERROR_MESSAGE);
141138
});
142139

143140
test('Forgot password and login with new password', async ({ page }) => {

0 commit comments

Comments
 (0)