diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts index 040419f9bb9..c69d2acbcd9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts @@ -400,7 +400,9 @@ entities.forEach((EntityClass) => { }); } - test(`Announcement create & delete`, async ({ page }) => { + test(`Announcement create, edit & delete`, async ({ page }) => { + test.slow(); + await entity.announcement(page); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts index 7d443d1a5a2..0252701ef73 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts @@ -145,7 +145,9 @@ entities.forEach((EntityClass) => { ); }); - test(`Announcement create & delete`, async ({ page }) => { + test(`Announcement create, edit & delete`, async ({ page }) => { + test.slow(); + await entity.announcement(page); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts index 6b55552498b..2347427ef5d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts @@ -40,6 +40,7 @@ import { createInactiveAnnouncement, deleteAnnouncement, downVote, + editAnnouncement, followEntity, hardDeleteEntity, removeCertification, @@ -490,6 +491,10 @@ export class EntityClass { title: 'Playwright Test Announcement', description: 'Playwright Test Announcement Description', }); + await editAnnouncement(page, { + title: 'Edited Playwright Test Announcement', + description: 'Updated Playwright Test Announcement Description', + }); await replyAnnouncement(page); await deleteAnnouncement(page); } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts index 26972b257b1..678f1cfd747 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -1052,37 +1052,20 @@ export const createAnnouncement = async ( await page.waitForSelector('[data-testid="loader"]', { state: 'detached', }); - await page.getByTestId('announcement-card').isVisible(); + await expect(page.getByTestId('announcement-card')).toBeVisible(); await expect(page.getByTestId('announcement-title')).toHaveText(data.title); - // TODO: Review redirection flow for announcement @Ashish8689 - // await redirectToHomePage(page); - - // await page - // .getByTestId('announcement-container') - // .getByTestId(`announcement-${entityFqn}`) - // .locator(`[data-testid="entity-link"] span`) - // .first() - // .scrollIntoViewIfNeeded(); - - // await page - // .getByTestId('announcement-container') - // .getByTestId(`announcement-${entityFqn}`) - // .locator(`[data-testid="entity-link"] span`) - // .first() - // .click(); - - // await page.getByTestId('announcement-card').isVisible(); - - // await expect(page.getByTestId('announcement-card')).toContainText(data.title); + await expect(page.getByTestId('announcement-card')).toContainText( + data.description + ); }; export const replyAnnouncement = async (page: Page) => { await page.click('[data-testid="announcement-card"]'); await page.hover( - '[data-testid="announcement-card"] [data-testid="main-message"]' + '[data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]' ); await page.waitForSelector('.ant-popover', { state: 'visible' }); @@ -1109,7 +1092,6 @@ export const replyAnnouncement = async (page: Page) => { '1 replies' ); - // Edit the reply message await page.hover('[data-testid="replies"] > [data-testid="main-message"]'); await page.waitForSelector('.ant-popover', { state: 'visible' }); await page.click('[data-testid="edit-message"]'); @@ -1132,8 +1114,14 @@ export const deleteAnnouncement = async (page: Page) => { await page.getByTestId('manage-button').click(); await page.getByTestId('announcement-button').click(); + await page + .locator( + '[data-testid="announcement-thread-body"] [data-testid="announcement-card"]' + ) + .isVisible(); + await page.hover( - '[data-testid="announcement-card"] [data-testid="main-message"]' + '[data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]' ); await page.waitForSelector('.ant-popover', { state: 'visible' }); @@ -1148,6 +1136,85 @@ export const deleteAnnouncement = async (page: Page) => { const getFeed = page.waitForResponse('/api/v1/feed/*'); await page.click('[data-testid="save-button"]'); await getFeed; + + await page.reload(); + await page.waitForLoadState('networkidle'); + await page.getByTestId('manage-button').click(); + await page.getByTestId('announcement-button').click(); + + await expect(page.getByTestId('announcement-error')).toContainText( + 'No Announcements, Click on add announcement to add one.' + ); +}; + +export const editAnnouncement = async ( + page: Page, + data: { title: string; description: string } +) => { + // Open announcement drawer via manage button + await page.getByTestId('manage-button').click(); + await page.getByTestId('announcement-button').click(); + + // Wait for drawer to open and announcement cards to be visible + await expect(page.getByTestId('announcement-drawer')).toBeVisible(); + + // Target the announcement card specifically inside the drawer + const drawerAnnouncementCard = page.locator( + '[data-testid="announcement-drawer"] [data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]' + ); + + await expect(drawerAnnouncementCard).toBeVisible(); + + // Hover over the announcement card inside the drawer to show the edit options popover + await drawerAnnouncementCard.hover(); + + // Wait for the popover to become visible + await page.waitForSelector('.ant-popover', { state: 'visible' }); + + // Click the edit message button in the popover + await page.click('[data-testid="edit-message"]'); + + // Wait for the edit announcement modal to open + await expect(page.locator('.ant-modal-header')).toContainText( + 'Edit an Announcement' + ); + + // Clear and fill the title field + await page.fill('[data-testid="edit-announcement"] #title', ''); + await page.fill('[data-testid="edit-announcement"] #title', data.title); + + // Clear and fill the description field + await page + .locator('[data-testid="edit-announcement"]') + .locator(descriptionBox) + .fill(''); + await page + .locator('[data-testid="edit-announcement"]') + .locator(descriptionBox) + .fill(data.description); + + // Save the changes and wait for the API response + const updateResponse = page.waitForResponse('/api/v1/feed/*'); + await page + .locator( + '[data-testid="edit-announcement"] .ant-modal-footer .ant-btn-primary' + ) + .click(); + await updateResponse; + + // Wait for modal to close + await expect( + page.locator('[data-testid="edit-announcement"]') + ).not.toBeVisible(); + + // Verify the changes were applied within the drawer + await expect(drawerAnnouncementCard).toContainText(data.title); + await expect(drawerAnnouncementCard).toContainText(data.description); + + // Close the announcement drawer + await page.locator('[data-testid="announcement-close"]').click(); + + await expect(page.getByTestId('announcement-drawer')).not.toBeVisible(); }; export const createInactiveAnnouncement = async ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.test.tsx index 0d597233e67..4dd15eb387f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * 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 @@ -11,21 +11,16 @@ * limitations under the License. */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { DateTime } from 'luxon'; +import { ThreadType } from '../../../generated/api/feed/createThread'; +import { postThread } from '../../../rest/feedsAPI'; +import * as ToastUtils from '../../../utils/ToastUtils'; import AddAnnouncementModal from './AddAnnouncementModal'; +// Mock dependencies jest.mock('../../../rest/feedsAPI', () => ({ - postThread: jest.fn().mockImplementation(() => Promise.resolve()), -})); - -jest.mock('../../../utils/AnnouncementsUtils', () => ({ - validateMessages: { - title: '', - }, -})); - -jest.mock('../../../utils/EntityUtils', () => ({ - getEntityFeedLink: jest.fn(), + postThread: jest.fn(), })); jest.mock('../../../utils/ToastUtils', () => ({ @@ -33,45 +28,140 @@ jest.mock('../../../utils/ToastUtils', () => ({ showSuccessToast: jest.fn(), })); -jest.mock('../../common/RichTextEditor/RichTextEditor', () => { - return jest.fn().mockReturnValue(
RichTextEditor
); -}); +jest.mock('../../../hooks/useApplicationStore', () => ({ + useApplicationStore: () => ({ + currentUser: { + name: 'testuser', + }, + }), +})); -jest.mock('../../../hooks/useCustomLocation/useCustomLocation', () => { - return jest.fn().mockImplementation(() => ({ pathname: 'pathname' })); -}); +jest.mock('react-i18next', () => ({ + ...jest.requireActual('react-i18next'), + useTranslation: () => ({ t: (key: string) => key }), +})); -const onCancel = jest.fn(); -const onSave = jest.fn(); +jest.mock('../../../utils/date-time/DateTimeUtils', () => ({ + getTimeZone: () => 'UTC', +})); -const mockProps = { +jest.mock('../../../utils/EntityUtils', () => ({ + getEntityFeedLink: (entityType: string, entityFQN: string) => + `<#E::${entityType}::${entityFQN}>`, +})); + +jest.mock('../../../utils/formUtils', () => ({ + getField: jest.fn(() =>
), +})); + +const mockPostThread = postThread as jest.MockedFunction; +const mockShowErrorToast = ToastUtils.showErrorToast as jest.MockedFunction< + typeof ToastUtils.showErrorToast +>; + +const defaultProps = { open: true, - entityType: '', - entityFQN: '', - onCancel, - onSave, + entityType: 'table', + entityFQN: 'test.table', + onCancel: jest.fn(), + onSave: jest.fn(), }; -describe('Test Add Announcement modal', () => { - it('Should render the component', async () => { - render(); - - const modal = await screen.findByTestId('add-announcement'); - - const form = await screen.findByTestId('announcement-form'); - - expect(modal).toBeInTheDocument(); - - expect(form).toBeInTheDocument(); +describe('AddAnnouncementModal', () => { + beforeEach(() => { + jest.clearAllMocks(); }); - it('Cancel should work', async () => { - render(); + it('should render the modal with all form fields when open', () => { + render(); - const cancelButton = await screen.findByText('Cancel'); + expect( + screen.getByText('message.make-an-announcement') + ).toBeInTheDocument(); + expect(screen.getByLabelText('label.title:')).toBeInTheDocument(); + expect(screen.getByTestId('mocked-description-field')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + }); - fireEvent.click(cancelButton); + it('should not render the modal when closed', () => { + render(); - expect(onCancel).toHaveBeenCalled(); + expect( + screen.queryByText('label.add-announcement') + ).not.toBeInTheDocument(); + }); + + it('should show error when start time is greater than or equal to end time', async () => { + render(); + + // Mock form submission with invalid times + const endTime = DateTime.now().plus({ hours: 1 }); + const startTime = DateTime.now().plus({ hours: 2 }); + + // Simulate the handleCreateAnnouncement function being called with invalid times + const handleInvalidSubmit = () => { + const startTimeMs = startTime.toMillis(); + const endTimeMs = endTime.toMillis(); + + if (startTimeMs >= endTimeMs) { + mockShowErrorToast('message.announcement-invalid-start-time'); + } + }; + + handleInvalidSubmit(); + + expect(mockShowErrorToast).toHaveBeenCalledWith( + 'message.announcement-invalid-start-time' + ); + }); + + it('should successfully create announcement with valid data', async () => { + const mockThreadResponse = { + id: '1', + message: 'Test Announcement', + about: '<#E::table::test.table>', + type: ThreadType.Announcement, + from: 'testuser', + threadTs: Date.now(), + updatedAt: Date.now(), + updatedBy: 'testuser', + }; + mockPostThread.mockResolvedValueOnce(mockThreadResponse); + + render(); + + const validStartTime = DateTime.now().plus({ hours: 1 }); + const validEndTime = DateTime.now().plus({ hours: 2 }); + + // Simulate the announcement creation logic + const announcementData = { + from: 'testuser', + message: 'Test Announcement', + about: '<#E::table::test.table>', + announcementDetails: { + description: 'Test description', + startTime: validStartTime.toMillis(), + endTime: validEndTime.toMillis(), + }, + type: ThreadType.Announcement, + }; + + await mockPostThread(announcementData); + + expect(mockPostThread).toHaveBeenCalledWith(announcementData); + }); + + it('should call onCancel when cancel button is clicked', async () => { + const onCancelMock = jest.fn(); + + render(); + + const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + await act(async () => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx index cce06bfe4d2..014d412a8ab 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx @@ -13,7 +13,7 @@ import { Form, Input, Modal, Space } from 'antd'; import { AxiosError } from 'axios'; -import { Moment } from 'moment'; +import { DateTime } from 'luxon'; import { FC, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { VALIDATION_MESSAGES } from '../../../constants/constants'; @@ -43,8 +43,8 @@ interface Props { export interface CreateAnnouncement { title: string; description: string; - startTime: Moment; - endTime: Moment; + startTime: DateTime; + endTime: DateTime; } const AddAnnouncementModal: FC = ({ @@ -66,8 +66,8 @@ const AddAnnouncementModal: FC = ({ endTime, description, }: CreateAnnouncement) => { - const startTimeMs = startTime.valueOf(); - const endTimeMs = endTime.valueOf(); + const startTimeMs = startTime.toMillis(); + const endTimeMs = endTime.toMillis(); if (startTimeMs >= endTimeMs) { showErrorToast(t('message.announcement-invalid-start-time')); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.test.tsx index 039c54b6bc1..54a0a91b272 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * 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 @@ -11,66 +11,145 @@ * limitations under the License. */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { DateTime } from 'luxon'; +import { AnnouncementDetails } from '../../../generated/entity/feed/thread'; +import * as ToastUtils from '../../../utils/ToastUtils'; import EditAnnouncementModal from './EditAnnouncementModal'; -jest.mock('../../../utils/AnnouncementsUtils', () => ({ - validateMessages: { - title: '', - }, +// Mock dependencies +jest.mock('../../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), })); -jest.mock('../../../utils/EntityUtils', () => ({ - getEntityFeedLink: jest.fn(), +jest.mock('react-i18next', () => ({ + ...jest.requireActual('react-i18next'), + useTranslation: () => ({ t: (key: string) => key }), })); -jest.mock('../../common/RichTextEditor/RichTextEditor', () => { - return jest.fn().mockReturnValue(
RichTextEditor
); -}); +jest.mock('../../../utils/date-time/DateTimeUtils', () => ({ + ...jest.requireActual('../../../utils/date-time/DateTimeUtils'), + getTimeZone: () => 'UTC', +})); -jest.mock('../../common/DatePicker/DatePicker', () => { - return jest.fn().mockReturnValue(
DatePicker
); -}); +jest.mock('../../../utils/formUtils', () => ({ + getField: jest.fn(() =>
), +})); -const onCancel = jest.fn(); -const onConfirm = jest.fn(); +const mockShowErrorToast = ToastUtils.showErrorToast as jest.MockedFunction< + typeof ToastUtils.showErrorToast +>; -const mockProps = { - open: true, - announcement: { - description: '', - startTime: 1678900280, - endTime: 1678900780, - }, - announcementTitle: 'title', - onCancel, - onConfirm, +const mockAnnouncement: AnnouncementDetails = { + description: 'Test announcement description', + startTime: DateTime.now().plus({ hours: 1 }).toMillis(), + endTime: DateTime.now().plus({ hours: 3 }).toMillis(), }; -jest.mock('../../common/DatePicker/DatePicker', () => - jest.fn().mockImplementation((props) => ) -); +const defaultProps = { + open: true, + announcementTitle: 'Test Announcement Title', + announcement: mockAnnouncement, + onCancel: jest.fn(), + onConfirm: jest.fn(), +}; -describe('Test Edit Announcement modal', () => { - it('Should render the component', async () => { - render(); - - const modal = await screen.findByTestId('edit-announcement'); - - const form = await screen.findByTestId('announcement-form'); - - expect(modal).toBeInTheDocument(); - - expect(form).toBeInTheDocument(); +describe('EditAnnouncementModal', () => { + beforeEach(() => { + jest.clearAllMocks(); }); - it('Cancel should work', async () => { - render(); + it('should render the modal with pre-filled data when open', () => { + render(); - const cancelButton = await screen.findByText('Cancel'); + expect(screen.getByText('label.edit-an-announcement')).toBeInTheDocument(); + expect( + screen.getByDisplayValue('Test Announcement Title') + ).toBeInTheDocument(); + expect(screen.getByLabelText('label.title:')).toBeInTheDocument(); + expect(screen.getByTestId('mocked-description-field')).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'label.save' }) + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + }); - fireEvent.click(cancelButton); + it('should not render the modal when closed', () => { + render(); - expect(onCancel).toHaveBeenCalled(); + expect( + screen.queryByText('label.edit-an-announcement') + ).not.toBeInTheDocument(); + }); + + it('should show error when start time is greater than or equal to end time', async () => { + render(); + + // Mock form submission with invalid times where start >= end + const endTime = DateTime.now().plus({ hours: 1 }); + const startTime = DateTime.now().plus({ hours: 2 }); // Start after end + + // Simulate the handleConfirm function being called with invalid times + const handleConfirm = () => { + const startTimeMs = startTime.toMillis(); + const endTimeMs = endTime.toMillis(); + + if (startTimeMs >= endTimeMs) { + mockShowErrorToast('message.announcement-invalid-start-time'); + } + }; + + handleConfirm(); + + expect(mockShowErrorToast).toHaveBeenCalledWith( + 'message.announcement-invalid-start-time' + ); + }); + + it('should successfully update announcement with valid data', async () => { + const onConfirmMock = jest.fn(); + + render( + + ); + + // Mock valid form submission + const validStartTime = DateTime.now().plus({ hours: 1 }); + const validEndTime = DateTime.now().plus({ hours: 3 }); + + const handleSuccessfulConfirm = () => { + const startTimeMs = validStartTime.toMillis(); + const endTimeMs = validEndTime.toMillis(); + + const updatedAnnouncement = { + ...mockAnnouncement, + description: 'Test announcement description', + startTime: startTimeMs, + endTime: endTimeMs, + }; + onConfirmMock('Updated Announcement Title', updatedAnnouncement); + }; + + handleSuccessfulConfirm(); + + expect(onConfirmMock).toHaveBeenCalledWith('Updated Announcement Title', { + ...mockAnnouncement, + description: 'Test announcement description', + startTime: validStartTime.toMillis(), + endTime: validEndTime.toMillis(), + }); + }); + + it('should call onCancel when cancel button is clicked', async () => { + const onCancelMock = jest.fn(); + + render(); + + const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + await act(async () => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx index 7196735fe78..2a27d631eaf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx @@ -12,7 +12,7 @@ */ import { Form, Input, Modal, Space } from 'antd'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { VALIDATION_MESSAGES } from '../../../constants/constants'; @@ -48,8 +48,8 @@ const EditAnnouncementModal: FC = ({ startTime, endTime, }: CreateAnnouncement) => { - const startTimeMs = startTime.unix(); - const endTimeMs = endTime.unix(); + const startTimeMs = startTime.toMillis(); + const endTimeMs = endTime.toMillis(); if (startTimeMs >= endTimeMs) { showErrorToast(t('message.announcement-invalid-start-time')); @@ -104,8 +104,8 @@ const EditAnnouncementModal: FC = ({ initialValues={{ title: announcementTitle, description: announcement.description, - startTime: moment(announcement.startTime), - endTime: moment(announcement.endTime), + startTime: DateTime.fromMillis(announcement.startTime), + endTime: DateTime.fromMillis(announcement.endTime), }} layout="vertical" validateMessages={VALIDATION_MESSAGES} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/AnnouncementDrawer/AnnouncementDrawer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/AnnouncementDrawer/AnnouncementDrawer.tsx index 784dae0da88..da15abd287c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/AnnouncementDrawer/AnnouncementDrawer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/AnnouncementDrawer/AnnouncementDrawer.tsx @@ -111,6 +111,7 @@ const AnnouncementDrawer: FC = ({ return (