From 3d7085e9713c5b6843d176be10ce1335a72dee98 Mon Sep 17 00:00:00 2001 From: Aniket Katkar Date: Fri, 2 Feb 2024 01:33:19 +0530 Subject: [PATCH] Minor: Unit tests for observability and notification alert flow components (#14908) --- .../AppRouter/AuthenticatedAppRouter.tsx | 13 +- .../AddNotificationPage.test.tsx | 134 +++++++ .../AddObservabilityPage.test.tsx | 150 ++++++++ .../DestinationFormItem.test.tsx | 100 +++++ .../ObservabilityFormActionItem.test.tsx | 109 ++++++ .../ObservabilityFormActionItem.tsx | 2 +- .../ObservabilityFormFiltersItem.test.tsx | 109 ++++++ .../ObservabilityFormTriggerItem.test.tsx | 81 +++++ .../AlertDetailsPage.interface.ts} | 2 +- .../AlertDetailsPage.tsx} | 10 +- .../NotificationListPage.test.tsx | 69 +++- .../ObservabilityAlertsPage.test.tsx | 66 +++- .../src/test/unit/mocks/observability.mock.ts | 343 ++++++++++++++++++ .../ui/src/utils/Alerts/AlertsUtil.test.tsx | 95 ++++- .../ui/src/utils/Alerts/AlertsUtil.tsx | 20 +- .../ui/src/utils/ObservabilityUtils.test.ts | 90 +++++ 16 files changed, 1334 insertions(+), 59 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddNotificationPage/AddNotificationPage.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/AddObservabilityPage.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/DestinationFormItem/DestinationFormItem.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx rename openmetadata-ui/src/main/resources/ui/src/pages/{ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.interface.ts => AlertDetailsPage/AlertDetailsPage.interface.ts} (92%) rename openmetadata-ui/src/main/resources/ui/src/pages/{ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.tsx => AlertDetailsPage/AlertDetailsPage.tsx} (98%) create mode 100644 openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/observability.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ObservabilityUtils.test.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx index 23a0391cb12..1b61080b03e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx @@ -387,17 +387,12 @@ const ObservabilityAlertsPage = withSuspenseFallback( ) ); -const ObservabilityAlertDetailsPage = withSuspenseFallback( - React.lazy( - () => - import( - '../../pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage' - ) - ) +const AlertDetailsPage = withSuspenseFallback( + React.lazy(() => import('../../pages/AlertDetailsPage/AlertDetailsPage')) ); const NotificationsAlertDetailsPage = () => ( - + ); const AddObservabilityPage = withSuspenseFallback( @@ -1070,7 +1065,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddNotificationPage/AddNotificationPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddNotificationPage/AddNotificationPage.test.tsx new file mode 100644 index 00000000000..38bff4a0d25 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddNotificationPage/AddNotificationPage.test.tsx @@ -0,0 +1,134 @@ +/* + * Copyright 2024 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 { act, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import AddNotificationPage from './AddNotificationPage'; + +const mockPush = jest.fn(); +const mockGoBack = jest.fn(); + +jest.mock('../../rest/alertsAPI', () => ({ + getAlertsFromName: jest.fn().mockImplementation(() => + Promise.resolve({ + name: 'ActivityFeedAlert', + }) + ), + getResourceFunctions: jest.fn(), + updateNotificationAlert: jest.fn().mockImplementation(() => + Promise.resolve({ + id: 'ActivityFeedAlert', + data: [], + }) + ), + createNotificationAlert: jest.fn().mockImplementation(() => + Promise.resolve({ + alert: 'Notification', + }) + ), +})); + +jest.mock('../../utils/RouterUtils', () => ({ + getNotificationAlertDetailsPath: jest.fn(), + getSettingPath: jest.fn(), +})); + +jest.mock('../../components/common/ResizablePanels/ResizablePanels', () => + jest.fn().mockImplementation(({ firstPanel, secondPanel }) => ( + <> +
{firstPanel.children}
+
{secondPanel.children}
+ + )) +); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: jest.fn().mockImplementation(() => ({ + push: mockPush, + goBack: mockGoBack, + })), +})); + +jest.mock('../../hooks/useFqn', () => ({ + useFqn: jest.fn().mockReturnValue({ fqn: '' }), +})); + +describe('AddNotificationPage', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render Add Notification Page', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect( + await screen.findByText('label.notification-plural') + ).toBeInTheDocument(); + }); + + it('should display the correct breadcrumb', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + const breadcrumbLinks = screen.getAllByTestId('breadcrumb-link'); + + expect(breadcrumbLinks[0]).toHaveTextContent('label.setting-plural'); + expect(breadcrumbLinks[1]).toHaveTextContent('label.notification-plural'); + expect(breadcrumbLinks[2]).toHaveTextContent('label.create-entity'); + }); + + it('should display SubTitle', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect( + await screen.findByText(/message.alerts-description/) + ).toBeInTheDocument(); + }); + + it('should render Add alert button', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect(await screen.findByText(/label.create-entity/)).toBeInTheDocument(); + }); + + it('should navigate back when the cancel button is clicked', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const cancelButton = screen.getByTestId('cancel-button'); + + await act(async () => { + fireEvent.click(cancelButton); + }); + + expect(mockGoBack).toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/AddObservabilityPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/AddObservabilityPage.test.tsx new file mode 100644 index 00000000000..6969f8c033e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/AddObservabilityPage.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright 2024 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 { act, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import AddObservabilityPage from './AddObservabilityPage'; + +const MOCK_DATA = [ + { + id: '971a21b3-eeaf-4765-bda7-4e2cdb9788de', + name: 'alert-test', + fullyQualifiedName: 'alert-test', + href: 'http://localhost:8585/api/v1/events/subscriptions/971a21b3-eeaf-4765-bda7-4e2cdb9788de', + version: 0.1, + updatedAt: 1682366749021, + updatedBy: 'admin', + filteringRules: { + resources: ['all'], + rules: [ + { + name: 'matchIngestionPipelineState', + effect: 'include', + condition: "matchIngestionPipelineState('partialSuccess')", + }, + ], + }, + subscriptionType: 'Email', + subscriptionConfig: { + receivers: ['test@gmail.com'], + }, + enabled: true, + batchSize: 10, + timeout: 10, + readTimeout: 12, + deleted: false, + provider: 'user', + }, +]; +const mockPush = jest.fn(); +const mockGoBack = jest.fn(); + +jest.mock('../../rest/observabilityAPI', () => ({ + getObservabilityAlertByFQN: jest.fn().mockImplementation(() => + Promise.resolve({ + fqn: 'alert-test', + }) + ), + createObservabilityAlert: jest.fn().mockImplementation(() => + Promise.resolve({ + alert: 'Observability', + }) + ), + getResourceFunctions: jest.fn(), + updateObservabilityAlert: jest.fn().mockImplementation(() => + Promise.resolve({ + id: 'test', + jsonPatch: MOCK_DATA, + }) + ), +})); + +jest.mock('../../components/common/ResizablePanels/ResizablePanels', () => + jest.fn().mockImplementation(({ firstPanel, secondPanel }) => ( + <> +
{firstPanel.children}
+
{secondPanel.children}
+ + )) +); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: jest.fn().mockImplementation(() => ({ + push: mockPush, + goBack: mockGoBack, + })), +})); + +describe('Add ObservabilityPage Alerts Page Tests', () => { + it('should render Add Observability Page', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect(await screen.findByText('label.observability')).toBeInTheDocument(); + }); + + it('should display SubTitle', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect( + await screen.findByText(/message.alerts-description/) + ).toBeInTheDocument(); + }); + + it('should render Add alert button', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + expect(await screen.findByText(/label.create-entity/)).toBeInTheDocument(); + }); + + it('should display the correct breadcrumb', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + const breadcrumbLinks = screen.getAllByTestId('breadcrumb-link'); + + expect(breadcrumbLinks[0]).toHaveTextContent('label.observability'); + expect(breadcrumbLinks[1]).toHaveTextContent('label.alert-plural'); + expect(breadcrumbLinks[2]).toHaveTextContent('label.create-entity'); + }); + + it('should navigate back when the cancel button is clicked', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const cancelButton = screen.getByTestId('cancel-button'); + + await act(async () => { + fireEvent.click(cancelButton); + }); + + expect(mockGoBack).toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/DestinationFormItem/DestinationFormItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/DestinationFormItem/DestinationFormItem.test.tsx new file mode 100644 index 00000000000..fdacdeb0ca9 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/DestinationFormItem/DestinationFormItem.test.tsx @@ -0,0 +1,100 @@ +/* + * Copyright 2024 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 { render, screen } from '@testing-library/react'; +import { Form, FormInstance } from 'antd'; +import React from 'react'; +import DestinationFormItem from './DestinationFormItem.component'; + +jest.mock('../../../utils/Alerts/AlertsUtil', () => ({ + getDestinationConfigField: jest + .fn() + .mockReturnValue(
), + getSubscriptionTypeOptions: jest.fn().mockReturnValue([]), + listLengthValidator: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +jest.mock('../../../utils/ObservabilityUtils', () => ({ + checkIfDestinationIsInternal: jest.fn().mockImplementation(() => false), + getAlertDestinationCategoryIcons: jest + .fn() + .mockImplementation(() => Icon), +})); + +const mockProps = { + heading: 'heading', + subHeading: 'subHeading', + buttonLabel: 'buttonLabel', +}; + +describe('DestinationFormItem', () => { + it('should renders without crashing', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render(); + + expect(screen.getByText('heading')).toBeInTheDocument(); + expect(screen.getByText('subHeading')).toBeInTheDocument(); + expect(screen.getByText('buttonLabel')).toBeInTheDocument(); + + expect(screen.getByTestId('add-destination-button')).toBeInTheDocument(); + }); + + it('add destination button should be disabled if there is no selected trigger', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => []); + + render(); + + expect(screen.getByTestId('add-destination-button')).toBeDisabled(); + }); + + it('add destination button should be enabled if there is selected trigger', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render(); + + expect(screen.getByTestId('add-destination-button')).toBeEnabled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.test.tsx new file mode 100644 index 00000000000..9004b37cb66 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.test.tsx @@ -0,0 +1,109 @@ +/* + * Copyright 2024 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 { render, screen } from '@testing-library/react'; +import { Form, FormInstance } from 'antd'; +import React from 'react'; +import { EventFilterRule } from '../../../generated/events/eventSubscription'; +import { MOCK_FILTER_RESOURCES } from '../../../test/unit/mocks/observability.mock'; +import ObservabilityFormActionItem from './ObservabilityFormActionItem'; + +jest.mock('../../../utils/Alerts/AlertsUtil', () => ({ + getConditionalField: jest + .fn() + .mockReturnValue(
), + getSupportedFilterOptions: jest.fn().mockReturnValue([]), +})); + +const mockSupportedActions = MOCK_FILTER_RESOURCES.reduce( + (resource, current) => { + resource.push(...(current.supportedActions ?? [])); + + return resource; + }, + [] as EventFilterRule[] +); + +describe('ObservabilityFormActionItem', () => { + it('should renders without crashing', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render( + + ); + + expect(screen.getByText('label.action-plural')).toBeInTheDocument(); + expect( + screen.getByText('message.alerts-action-description') + ).toBeInTheDocument(); + + expect(screen.getByTestId('actions-list')).toBeInTheDocument(); + expect(screen.getByTestId('add-actions')).toBeInTheDocument(); + }); + + it('add actions button should be disabled if there is no selected trigger and filters', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => []); + + render( + + ); + + const addButton = screen.getByTestId('add-actions'); + + expect(addButton).toBeDisabled(); + }); + + it('add actions button should not be disabled if there is selected trigger and filters', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render( + + ); + + const addButton = screen.getByTestId('add-actions'); + + expect(addButton).not.toBeDisabled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.tsx index d08164a3dff..127f5200534 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormActionItem/ObservabilityFormActionItem.tsx @@ -67,7 +67,7 @@ function ObservabilityFormActionItem({ fields.length < (supportedActions?.length ?? 1); return ( - + {fields.map(({ key, name }) => { const effect = form.getFieldValue([ diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx new file mode 100644 index 00000000000..0aef830c9df --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx @@ -0,0 +1,109 @@ +/* + * Copyright 2024 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 { render, screen } from '@testing-library/react'; +import { Form, FormInstance } from 'antd'; +import React from 'react'; +import { EventFilterRule } from '../../../generated/events/eventSubscription'; +import { MOCK_FILTER_RESOURCES } from '../../../test/unit/mocks/observability.mock'; +import ObservabilityFormFiltersItem from './ObservabilityFormFiltersItem'; + +jest.mock('../../../utils/Alerts/AlertsUtil', () => ({ + getConditionalField: jest + .fn() + .mockReturnValue(
), + getSupportedFilterOptions: jest.fn().mockReturnValue([]), +})); + +const mockSupportedFilters = MOCK_FILTER_RESOURCES.reduce( + (resource, current) => { + resource.push(...(current.supportedFilters ?? [])); + + return resource; + }, + [] as EventFilterRule[] +); + +describe('ObservabilityFormFiltersItem', () => { + it('should renders without crashing', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render( + + ); + + expect(screen.getByText('label.filter-plural')).toBeInTheDocument(); + expect( + screen.getByText('message.alerts-filter-description') + ).toBeInTheDocument(); + + expect(screen.getByTestId('filters-list')).toBeInTheDocument(); + expect(screen.getByTestId('add-filters')).toBeInTheDocument(); + }); + + it('add filter button should be disabled if there is no selected trigger', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => []); + + render( + + ); + + const addButton = screen.getByTestId('add-filters'); + + expect(addButton).toBeDisabled(); + }); + + it('add filter button should not be disabled if there is selected trigger', () => { + const setFieldValue = jest.fn(); + const getFieldValue = jest.fn(); + jest.spyOn(Form, 'useFormInstance').mockImplementation( + () => + ({ + setFieldValue, + getFieldValue, + } as unknown as FormInstance) + ); + + const useWatchMock = jest.spyOn(Form, 'useWatch'); + useWatchMock.mockImplementation(() => ['container']); + + render( + + ); + + const addButton = screen.getByTestId('add-filters'); + + expect(addButton).not.toBeDisabled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx new file mode 100644 index 00000000000..2f46ba25ec2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2024 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 { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { MemoryRouter } from 'react-router-dom'; +import { useFqn } from '../../../hooks/useFqn'; +import { MOCK_FILTER_RESOURCES } from '../../../test/unit/mocks/observability.mock'; +import ObservabilityFormTriggerItem from './ObservabilityFormTriggerItem'; + +jest.mock('../../../hooks/useFqn', () => ({ + useFqn: jest.fn().mockReturnValue({ fqn: '' }), +})); + +jest.mock('antd', () => { + const antd = jest.requireActual('antd'); + + return { + ...antd, + Form: { + ...antd.Form, + useFormInstance: jest.fn().mockImplementation(() => ({ + setFieldValue: jest.fn(), + getFieldValue: jest.fn(), + })), + }, + }; +}); + +describe('ObservabilityFormTriggerItem', () => { + it('should renders without crashing', () => { + render( + , + { wrapper: MemoryRouter } + ); + + expect(screen.getByText('label.trigger')).toBeInTheDocument(); + expect( + screen.getByText('message.alerts-trigger-description') + ).toBeInTheDocument(); + + expect(screen.getByTestId('add-trigger-button')).toBeInTheDocument(); + }); + + it('should render the trigger select when fqn is provided', () => { + (useFqn as jest.Mock).mockImplementationOnce(() => ({ + fqn: 'test', + })); + + render( + , + { wrapper: MemoryRouter } + ); + + expect(screen.getByTestId('trigger-select')).toBeInTheDocument(); + }); + + it('should display select dropdown when clicked on add trigger button', async () => { + render( + , + { wrapper: MemoryRouter } + ); + const addButton = screen.getByTestId('add-trigger-button'); + await act(async () => { + userEvent.click(addButton); + }); + + expect(screen.getByTestId('drop-down-menu')).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.interface.ts b/openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.interface.ts similarity index 92% rename from openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.interface.ts rename to openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.interface.ts index 56cb668a53a..ab62564d7b3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.interface.ts @@ -11,6 +11,6 @@ * limitations under the License. */ -export interface ObservabilityAlertDetailsPageProps { +export interface AlertDetailsPageProps { isNotificationAlert: boolean; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.tsx similarity index 98% rename from openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.tsx rename to openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.tsx index ff9d903a4a7..f3badabccc0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertDetailsPage/ObservabilityAlertDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AlertDetailsPage/AlertDetailsPage.tsx @@ -58,11 +58,11 @@ import { } from '../../utils/RouterUtils'; import { getEntityIcon } from '../../utils/TableUtils'; import '../AddObservabilityPage/add-observability-page.less'; -import { ObservabilityAlertDetailsPageProps } from './ObservabilityAlertDetailsPage.interface'; +import { AlertDetailsPageProps } from './AlertDetailsPage.interface'; -function ObservabilityAlertDetailsPage({ - isNotificationAlert, -}: Readonly) { +function AlertDetailsPage({ + isNotificationAlert = false, +}: Readonly) { const { t } = useTranslation(); const { fqn } = useFqn(); const history = useHistory(); @@ -441,4 +441,4 @@ function ObservabilityAlertDetailsPage({ ); } -export default ObservabilityAlertDetailsPage; +export default AlertDetailsPage; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/NotificationListPage/NotificationListPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/NotificationListPage/NotificationListPage.test.tsx index 030fe811a57..f93acea3d0b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/NotificationListPage/NotificationListPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/NotificationListPage/NotificationListPage.test.tsx @@ -10,10 +10,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ROUTES } from '../../constants/constants'; +import { getAllAlerts } from '../../rest/alertsAPI'; import NotificationListPage from './NotificationListPage'; const MOCK_DATA = [ @@ -55,6 +56,12 @@ jest.mock('../../rest/alertsAPI', () => ({ paging: { total: 1 }, }) ), + getAlertsFromName: jest.fn().mockImplementation(() => + Promise.resolve({ + name: 'ActivityFeedAlert', + params: 'all', + }) + ), })); jest.mock('../../utils/GlobalSettingsUtils', () => ({ @@ -77,28 +84,68 @@ jest.mock( } ); -describe('Alerts Page Tests', () => { +describe('Notification Alerts Page Tests', () => { it('Title should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText('label.notification-plural')).toBeInTheDocument(); + expect( + await screen.findByText('label.notification-plural') + ).toBeInTheDocument(); }); it('SubTitle should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText(/message.alerts-description/)).toBeInTheDocument(); + expect( + await screen.findByText(/message.alerts-description/) + ).toBeInTheDocument(); }); it('Add alert button should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText(/label.add-entity/)).toBeInTheDocument(); + expect(await screen.findByText(/label.add-entity/)).toBeInTheDocument(); + }); + + it('Table should render alerts data', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const alertNameElement = await screen.findByTestId('alert-name'); + + expect(alertNameElement).toBeInTheDocument(); + }); + + it('Table should render no data', async () => { + (getAllAlerts as jest.Mock).mockImplementation(() => + Promise.resolve({ + data: [], + paging: { total: 1 }, + }) + ); + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const alertNameElement = await screen.findByText('label.no-entity'); + + expect(alertNameElement).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertsPage/ObservabilityAlertsPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertsPage/ObservabilityAlertsPage.test.tsx index 09cd050943b..cb8bb57c3ad 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertsPage/ObservabilityAlertsPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ObservabilityAlertsPage/ObservabilityAlertsPage.test.tsx @@ -10,9 +10,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { getAllAlerts } from '../../rest/alertsAPI'; import ObservabilityAlertsPage from './ObservabilityAlertsPage'; const MOCK_DATA = [ @@ -55,29 +56,72 @@ jest.mock('../../rest/alertsAPI', () => ({ }) ), })); +jest.mock('../../components/PageLayoutV1/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) =>
{children}
); +}); -describe.skip('Alerts Page Tests', () => { +describe('Observability Alerts Page Tests', () => { it('Title should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText('label.alert-plural')).toBeInTheDocument(); + expect(await screen.findByText('label.observability')).toBeInTheDocument(); }); it('SubTitle should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText(/message.alerts-description/)).toBeInTheDocument(); + expect( + await screen.findByText(/message.alerts-description/) + ).toBeInTheDocument(); }); it('Add alert button should be rendered', async () => { - const { findByText } = render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - expect(await findByText(/label.create-entity/)).toBeInTheDocument(); + expect(await screen.findByText(/label.add-entity/)).toBeInTheDocument(); + }); + + it('Table should render alerts data', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const alertNameElement = await screen.findByText('alert-test'); + + expect(alertNameElement).toBeInTheDocument(); + }); + + it('Table should render no data', async () => { + (getAllAlerts as jest.Mock).mockImplementation(() => + Promise.resolve({ + data: [], + paging: { total: 1 }, + }) + ); + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const alertNameElement = await screen.findByText( + 'message.adding-new-entity-is-easy-just-give-it-a-spin' + ); + + expect(alertNameElement).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/observability.mock.ts b/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/observability.mock.ts new file mode 100644 index 00000000000..9496409246f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/test/unit/mocks/observability.mock.ts @@ -0,0 +1,343 @@ +/* + * Copyright 2024 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 { FilterResourceDescriptor } from '../../../generated/events/filterResourceDescriptor'; + +export const MOCK_FILTER_RESOURCES = [ + { + name: 'container', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Container Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event Filtering By Container Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetContainerSchemaChanges', + displayName: 'Get Schema Changes', + fullyQualifiedName: 'eventSubscription.GetContainerSchemaChanges', + description: 'Get Updates for Schema Changes', + effect: 'include', + condition: "matchAnyFieldChange({'parent','children'})", + arguments: [], + inputType: 'none', + }, + ], + }, + { + name: 'pipeline', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Pipeline Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event Filtering By Pipeline Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetPipelineStatusUpdates', + displayName: 'Get Pipeline Status Updates', + fullyQualifiedName: 'eventSubscription.GetPipelineStatusUpdates', + description: 'Get Updates for Pipeline Status Changes', + effect: 'include', + condition: 'matchIngestionPipelineState(${pipelineStateList})', + arguments: ['pipelineStateList'], + inputType: 'runtime', + }, + ], + }, + { + name: 'table', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Table Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event Filtering By Table Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetTableSchemaChanges', + displayName: 'Get Schema Changes', + fullyQualifiedName: 'eventSubscription.GetTableSchemaChanges', + description: 'Get Updates for Schema Changes', + effect: 'include', + condition: "matchAnyFieldChange({'columns','dataModel','joins'})", + arguments: [], + inputType: 'none', + }, + { + name: 'GetTableMetricsUpdates', + displayName: 'Get Table Metrics Updates', + fullyQualifiedName: 'eventSubscription.GetTableMetricsUpdates', + description: 'Get Updates About Table Metrics', + effect: 'include', + condition: "matchAnyFieldChange({'customMetrics', 'profile'})", + arguments: [], + inputType: 'none', + }, + ], + }, + { + name: 'testCase', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Test Case Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event By Test Case Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + { + name: 'filterByTableNameTestCaseBelongsTo', + displayName: 'Table Name A Test Case Belongs To', + fullyQualifiedName: + 'eventSubscription.filterByTableNameTestCaseBelongsTo', + description: 'Event Filtering By Table Name A Test Case Belongs To', + effect: 'include', + condition: 'filterByTableNameTestCaseBelongsTo(${tableNameList})', + arguments: ['tableNameList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetTestCaseSchemaChanges', + displayName: 'Get Schema Changes', + fullyQualifiedName: 'eventSubscription.filterBySchemaChange', + description: 'Get Updates for Schema Changes', + effect: 'include', + condition: + "matchAnyFieldChange({'testDefinition','parameterValues','description'})", + arguments: [], + inputType: 'none', + }, + { + name: 'GetTestCaseStatusUpdates', + displayName: 'Get Test Case Status Updates', + fullyQualifiedName: 'eventSubscription.GetTestCaseStatusUpdates', + description: 'Get Status Updates Test Cases', + effect: 'include', + condition: 'matchTestResult(${testResultList})', + arguments: ['testResultList'], + inputType: 'runtime', + }, + ], + }, + { + name: 'testSuite', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Test Suite Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event Filtering By Test Suite Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetTestSuiteSchemaChanges', + displayName: 'Get Schema Changes', + fullyQualifiedName: 'eventSubscription.filterBySchemaChange', + description: 'Get Updates for Schema Changes', + effect: 'include', + condition: + "matchAnyFieldChange({'connection','pipelines','description'})", + arguments: [], + inputType: 'none', + }, + { + name: 'GetTestCaseStatusUpdatesUnderSuite', + displayName: 'Get Test Case Status Updates belonging to a Test Suite', + fullyQualifiedName: + 'eventSubscription.GetTestCaseStatusUpdatesUnderSuite', + description: 'Get Status Updates Test Cases belonging to a Test Suite', + effect: 'include', + condition: + 'getTestCaseStatusIfInTestSuite(${testSuiteList}, ${testStatusList})', + arguments: ['testSuiteList', 'testStatusList'], + inputType: 'runtime', + }, + ], + }, + { + name: 'topic', + supportedFilters: [ + { + name: 'filterByFqn', + displayName: 'Topic Name', + fullyQualifiedName: 'eventSubscription.filterByFqn', + description: 'Event Filtering By Topic Name', + effect: 'include', + condition: 'matchAnyEntityFqn(${fqnList})', + arguments: ['fqnList'], + inputType: 'runtime', + }, + { + name: 'filterByDomain', + displayName: 'Domain', + fullyQualifiedName: 'eventSubscription.filterByDomain', + description: 'Event Filtering By Domain a Entity Belongs To', + effect: 'include', + condition: 'matchAnyDomain(${domainList})', + arguments: ['domainList'], + inputType: 'runtime', + }, + { + name: 'filterByOwner', + displayName: 'Owner Name', + fullyQualifiedName: 'eventSubscription.filterByOwner', + description: 'Event Filtering By Owner Name of Asset', + effect: 'include', + condition: 'matchAnyOwnerName(${ownerNameList})', + arguments: ['ownerNameList'], + inputType: 'runtime', + }, + ], + supportedActions: [ + { + name: 'GetTopicSchemaChanges', + displayName: 'Get Schema Changes', + fullyQualifiedName: 'eventSubscription.GetTopicSchemaChanges', + description: 'Get Updates for Schema Changes', + effect: 'include', + condition: "matchAnyFieldChange({'messageSchema'})", + arguments: [], + inputType: 'none', + }, + ], + }, +] as FilterResourceDescriptor[]; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.test.tsx index 744745c56cb..f21e0b3c730 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.test.tsx @@ -10,8 +10,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { getFunctionDisplayName } from './AlertsUtil'; +import React from 'react'; +import { ReactComponent as AllActivityIcon } from '../../assets/svg/all-activity.svg'; +import { ReactComponent as MailIcon } from '../../assets/svg/ic-mail.svg'; +import { ReactComponent as MSTeamsIcon } from '../../assets/svg/ms-teams.svg'; +import { ReactComponent as SlackIcon } from '../../assets/svg/slack.svg'; +import { ReactComponent as WebhookIcon } from '../../assets/svg/webhook.svg'; +import { SubscriptionType } from '../../generated/events/eventSubscription'; +import { + getAlertActionTypeDisplayName, + getAlertsActionTypeIcon, + getDisplayNameForEntities, + getFunctionDisplayName, + listLengthValidator, +} from './AlertsUtil'; describe('AlertsUtil tests', () => { it('getFunctionDisplayName should return correct text for matchAnyEntityFqn', () => { @@ -47,4 +59,83 @@ describe('AlertsUtil tests', () => { 'label.entity-id-match' ); }); + + it('getAlertsActionTypeIcon should return correct icon for Slack', () => { + const icon = getAlertsActionTypeIcon(SubscriptionType.Slack); + + expect(icon).toStrictEqual(); + }); + + it('getAlertsActionTypeIcon should return correct icon for Email', () => { + const icon = getAlertsActionTypeIcon(SubscriptionType.Email); + + expect(icon).toStrictEqual(); + }); + + it('getAlertsActionTypeIcon should return correct icon for MSTeam', () => { + const icon = getAlertsActionTypeIcon(SubscriptionType.MSTeams); + + expect(icon).toStrictEqual(); + }); + + it('getAlertsActionTypeIcon should return correct icon for ActivityFeed', () => { + const icon = getAlertsActionTypeIcon(SubscriptionType.ActivityFeed); + + expect(icon).toStrictEqual(); + }); + + it('getAlertsActionTypeIcon should return correct icon for generic', () => { + const icon = getAlertsActionTypeIcon(SubscriptionType.Generic); + + expect(icon).toStrictEqual(); + }); + + it('listLengthValidator should return error function', async () => { + const error = listLengthValidator('name', 64); + + expect(typeof error).toBe('function'); + }); + + it('getAlertActionTypeDisplayName should return correct text for Slack', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.Slack)).toBe( + 'label.slack' + ); + }); + + it('getAlertActionTypeDisplayName should return correct text for Email', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.Email)).toBe( + 'label.email' + ); + }); + + it('getAlertActionTypeDisplayName should return correct text for MSTeam', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.MSTeams)).toBe( + 'label.ms-team-plural' + ); + }); + + it('getAlertActionTypeDisplayName should return correct text for ActivityFeed', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.ActivityFeed)).toBe( + 'label.activity-feed-plural' + ); + }); + + it('getAlertActionTypeDisplayName should return correct text for generic', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.Generic)).toBe( + 'label.webhook' + ); + }); + + it('getAlertActionTypeDisplayName should return correct text for GChat', () => { + expect(getAlertActionTypeDisplayName(SubscriptionType.GChat)).toBe( + 'label.g-chat' + ); + }); + + it('getDisplayNameForEntities should return correct text', () => { + expect(getDisplayNameForEntities('kpi')).toBe('label.kpi-uppercase'); + expect(getDisplayNameForEntities('mlmodel')).toBe('label.ml-model'); + + expect(getDisplayNameForEntities('unknown')).toBe('Unknown'); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.tsx index 20cf063976e..198641bb962 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Col, Input, Select, Switch, Tooltip, Typography } from 'antd'; +import { Col, Input, Select, Switch, Tooltip } from 'antd'; import Form, { RuleObject } from 'antd/lib/form'; import { AxiosError } from 'axios'; import i18next, { t } from 'i18next'; @@ -91,24 +91,6 @@ export const getFunctionDisplayName = (func: string): string => { } }; -export const StyledCard = ({ - heading, - subHeading, -}: { - heading: string; - subHeading: string; -}) => { - return ( -
- {heading} -
- - {subHeading} - -
- ); -}; - /** * * @param name Field name used to identify which field has error diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ObservabilityUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ObservabilityUtils.test.ts new file mode 100644 index 00000000000..9a56d3419ca --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ObservabilityUtils.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2024 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 { SubscriptionCategory } from '../generated/events/eventSubscription'; +import { + checkIfDestinationIsInternal, + getAlertDestinationCategoryIcons, + getConfigFieldFromDestinationType, +} from './ObservabilityUtils'; + +describe('Observability Utils test', () => { + describe('getAlertDestinationCategoryIcons', () => { + it('should return the correct icon for each type', () => { + const types = [ + 'Teams', + 'Users', + 'Admins', + 'GChat', + 'Slack', + 'Email', + 'MsTeams', + 'Followers', + 'Generic', + 'Owners', + ]; + types.forEach((type) => { + const icon = getAlertDestinationCategoryIcons(type); + + expect(icon).not.toBeNull(); + }); + }); + + it('should return null for an unknown type', () => { + const icon = getAlertDestinationCategoryIcons('Unknown'); + + expect(icon).toBeNull(); + }); + }); + + describe('checkIfDestinationIsInternal', () => { + it('should return true for internal destinations', () => { + const destinationName = SubscriptionCategory.Admins; + const result = checkIfDestinationIsInternal(destinationName); + + expect(result).toBe(true); + }); + + it('should return false for external destinations', () => { + const destinationName = 'Test'; + const result = checkIfDestinationIsInternal(destinationName); + + expect(result).toBe(false); + }); + }); + + describe('getConfigFieldFromDestinationType', () => { + it('should return the correct config field for each type', () => { + const types = [ + SubscriptionCategory.Admins, + SubscriptionCategory.Owners, + SubscriptionCategory.Followers, + ]; + const expectedResults = [ + 'sendToAdmins', + 'sendToOwners', + 'sendToFollowers', + ]; + types.forEach((type, index) => { + const result = getConfigFieldFromDestinationType(type); + + expect(result).toBe(expectedResults[index]); + }); + }); + + it('should return an empty string for an unknown type', () => { + const result = getConfigFieldFromDestinationType('Unknown'); + + expect(result).toBe(''); + }); + }); +});