Minor: Unit tests for observability and notification alert flow components (#14908)

This commit is contained in:
Aniket Katkar 2024-02-02 01:33:19 +05:30 committed by GitHub
parent ecfcdc89db
commit 3d7085e971
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1334 additions and 59 deletions

View File

@ -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 = () => (
<ObservabilityAlertDetailsPage isNotificationAlert />
<AlertDetailsPage isNotificationAlert />
);
const AddObservabilityPage = withSuspenseFallback(
@ -1070,7 +1065,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<AdminProtectedRoute
exact
component={ObservabilityAlertDetailsPage}
component={AlertDetailsPage}
path={ROUTES.OBSERVABILITY_ALERT_DETAILS}
/>

View File

@ -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 }) => (
<>
<div>{firstPanel.children}</div>
<div>{secondPanel.children}</div>
</>
))
);
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(<AddNotificationPage />, {
wrapper: MemoryRouter,
});
});
expect(
await screen.findByText('label.notification-plural')
).toBeInTheDocument();
});
it('should display the correct breadcrumb', async () => {
await act(async () => {
render(<AddNotificationPage />, {
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(<AddNotificationPage />, {
wrapper: MemoryRouter,
});
});
expect(
await screen.findByText(/message.alerts-description/)
).toBeInTheDocument();
});
it('should render Add alert button', async () => {
await act(async () => {
render(<AddNotificationPage />, {
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(<AddNotificationPage />, {
wrapper: MemoryRouter,
});
});
const cancelButton = screen.getByTestId('cancel-button');
await act(async () => {
fireEvent.click(cancelButton);
});
expect(mockGoBack).toHaveBeenCalled();
});
});

View File

@ -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 }) => (
<>
<div>{firstPanel.children}</div>
<div>{secondPanel.children}</div>
</>
))
);
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(<AddObservabilityPage />, {
wrapper: MemoryRouter,
});
});
expect(await screen.findByText('label.observability')).toBeInTheDocument();
});
it('should display SubTitle', async () => {
await act(async () => {
render(<AddObservabilityPage />, {
wrapper: MemoryRouter,
});
});
expect(
await screen.findByText(/message.alerts-description/)
).toBeInTheDocument();
});
it('should render Add alert button', async () => {
await act(async () => {
render(<AddObservabilityPage />, {
wrapper: MemoryRouter,
});
});
expect(await screen.findByText(/label.create-entity/)).toBeInTheDocument();
});
it('should display the correct breadcrumb', async () => {
await act(async () => {
render(<AddObservabilityPage />, {
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(<AddObservabilityPage />, {
wrapper: MemoryRouter,
});
});
const cancelButton = screen.getByTestId('cancel-button');
await act(async () => {
fireEvent.click(cancelButton);
});
expect(mockGoBack).toHaveBeenCalled();
});
});

View File

@ -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(<div data-testid="destination-field" />),
getSubscriptionTypeOptions: jest.fn().mockReturnValue([]),
listLengthValidator: jest.fn().mockImplementation(() => Promise.resolve()),
}));
jest.mock('../../../utils/ObservabilityUtils', () => ({
checkIfDestinationIsInternal: jest.fn().mockImplementation(() => false),
getAlertDestinationCategoryIcons: jest
.fn()
.mockImplementation(() => <span data-testid="icon">Icon</span>),
}));
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(<DestinationFormItem {...mockProps} />);
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(<DestinationFormItem {...mockProps} />);
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(<DestinationFormItem {...mockProps} />);
expect(screen.getByTestId('add-destination-button')).toBeEnabled();
});
});

View File

@ -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(<div data-testid="condition-field" />),
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(
<ObservabilityFormActionItem supportedActions={mockSupportedActions} />
);
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(
<ObservabilityFormActionItem supportedActions={mockSupportedActions} />
);
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(
<ObservabilityFormActionItem supportedActions={mockSupportedActions} />
);
const addButton = screen.getByTestId('add-actions');
expect(addButton).not.toBeDisabled();
});
});

View File

@ -67,7 +67,7 @@ function ObservabilityFormActionItem({
fields.length < (supportedActions?.length ?? 1);
return (
<Row gutter={[16, 16]} key="actions">
<Row data-testid="actions-list" gutter={[16, 16]} key="actions">
{fields.map(({ key, name }) => {
const effect =
form.getFieldValue([

View File

@ -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(<div data-testid="condition-field" />),
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(
<ObservabilityFormFiltersItem supportedFilters={mockSupportedFilters} />
);
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(
<ObservabilityFormFiltersItem supportedFilters={mockSupportedFilters} />
);
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(
<ObservabilityFormFiltersItem supportedFilters={mockSupportedFilters} />
);
const addButton = screen.getByTestId('add-filters');
expect(addButton).not.toBeDisabled();
});
});

View File

@ -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(
<ObservabilityFormTriggerItem filterResources={MOCK_FILTER_RESOURCES} />,
{ 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(
<ObservabilityFormTriggerItem filterResources={MOCK_FILTER_RESOURCES} />,
{ wrapper: MemoryRouter }
);
expect(screen.getByTestId('trigger-select')).toBeInTheDocument();
});
it('should display select dropdown when clicked on add trigger button', async () => {
render(
<ObservabilityFormTriggerItem filterResources={MOCK_FILTER_RESOURCES} />,
{ wrapper: MemoryRouter }
);
const addButton = screen.getByTestId('add-trigger-button');
await act(async () => {
userEvent.click(addButton);
});
expect(screen.getByTestId('drop-down-menu')).toBeInTheDocument();
});
});

View File

@ -11,6 +11,6 @@
* limitations under the License.
*/
export interface ObservabilityAlertDetailsPageProps {
export interface AlertDetailsPageProps {
isNotificationAlert: boolean;
}

View File

@ -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<ObservabilityAlertDetailsPageProps>) {
function AlertDetailsPage({
isNotificationAlert = false,
}: Readonly<AlertDetailsPageProps>) {
const { t } = useTranslation();
const { fqn } = useFqn();
const history = useHistory();
@ -441,4 +441,4 @@ function ObservabilityAlertDetailsPage({
);
}
export default ObservabilityAlertDetailsPage;
export default AlertDetailsPage;

View File

@ -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(<NotificationListPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<NotificationListPage />, {
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(<NotificationListPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<NotificationListPage />, {
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(<NotificationListPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<NotificationListPage />, {
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(<NotificationListPage />, {
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(<NotificationListPage />, {
wrapper: MemoryRouter,
});
});
const alertNameElement = await screen.findByText('label.no-entity');
expect(alertNameElement).toBeInTheDocument();
});
});

View File

@ -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 }) => <div>{children}</div>);
});
describe.skip('Alerts Page Tests', () => {
describe('Observability Alerts Page Tests', () => {
it('Title should be rendered', async () => {
const { findByText } = render(<ObservabilityAlertsPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<ObservabilityAlertsPage />, {
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(<ObservabilityAlertsPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<ObservabilityAlertsPage />, {
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(<ObservabilityAlertsPage />, {
wrapper: MemoryRouter,
await act(async () => {
render(<ObservabilityAlertsPage />, {
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(<ObservabilityAlertsPage />, {
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(<ObservabilityAlertsPage />, {
wrapper: MemoryRouter,
});
});
const alertNameElement = await screen.findByText(
'message.adding-new-entity-is-easy-just-give-it-a-spin'
);
expect(alertNameElement).toBeInTheDocument();
});
});

View File

@ -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[];

View File

@ -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(<SlackIcon height={16} width={16} />);
});
it('getAlertsActionTypeIcon should return correct icon for Email', () => {
const icon = getAlertsActionTypeIcon(SubscriptionType.Email);
expect(icon).toStrictEqual(<MailIcon height={16} width={16} />);
});
it('getAlertsActionTypeIcon should return correct icon for MSTeam', () => {
const icon = getAlertsActionTypeIcon(SubscriptionType.MSTeams);
expect(icon).toStrictEqual(<MSTeamsIcon height={16} width={16} />);
});
it('getAlertsActionTypeIcon should return correct icon for ActivityFeed', () => {
const icon = getAlertsActionTypeIcon(SubscriptionType.ActivityFeed);
expect(icon).toStrictEqual(<AllActivityIcon height={16} width={16} />);
});
it('getAlertsActionTypeIcon should return correct icon for generic', () => {
const icon = getAlertsActionTypeIcon(SubscriptionType.Generic);
expect(icon).toStrictEqual(<WebhookIcon height={16} width={16} />);
});
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');
});
});

View File

@ -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 (
<div className="bg-grey p-sm rounded-4 min-h-24">
<Typography.Text>{heading}</Typography.Text>
<br />
<Typography.Text className="text-xs text-grey-muted">
{subHeading}
</Typography.Text>
</div>
);
};
/**
*
* @param name Field name used to identify which field has error

View File

@ -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('');
});
});
});