Adds toast to notification template list whenever test notification finishes

This commit is contained in:
mabashian 2021-02-15 11:18:36 -05:00
parent c9ec0d31f1
commit 83a9c3470e
3 changed files with 172 additions and 61 deletions

View File

@ -1,8 +1,14 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useLocation, useRouteMatch } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Card, PageSection } from '@patternfly/react-core';
import {
Alert,
AlertActionCloseButton,
AlertGroup,
Card,
PageSection,
} from '@patternfly/react-core';
import { NotificationTemplatesAPI } from '../../../api';
import PaginatedTable, {
HeaderRow,
@ -29,6 +35,7 @@ const QS_CONFIG = getQSConfig('notification-templates', {
function NotificationTemplatesList({ i18n }) {
const location = useLocation();
const match = useRouteMatch();
const [testToasts, setTestToasts] = useState([]);
const addUrl = `${match.url}/add`;
@ -102,6 +109,14 @@ function NotificationTemplatesList({ i18n }) {
setSelected([]);
};
const addTestToast = useCallback(notification => {
setTestToasts(oldToasts => [...oldToasts, notification]);
}, []);
const removeTestToast = notificationId => {
setTestToasts(testToasts.filter(toast => toast.id !== notificationId));
};
const canAdd = actions && actions.POST;
return (
@ -185,6 +200,7 @@ function NotificationTemplatesList({ i18n }) {
}
renderRow={(template, index) => (
<NotificationTemplateListItem
onAddToast={addTestToast}
key={template.id}
fetchTemplates={fetchTemplates}
template={template}
@ -209,6 +225,39 @@ function NotificationTemplatesList({ i18n }) {
{i18n._(t`Failed to delete one or more notification template.`)}
<ErrorDetail error={deletionError} />
</AlertModal>
<AlertGroup isToast>
{testToasts
.filter(notification => notification.status !== 'pending')
.map(notification => (
<Alert
actionClose={
<AlertActionCloseButton
onClose={() => removeTestToast(notification.id)}
/>
}
onTimeout={() => removeTestToast(notification.id)}
timeout={notification.status !== 'failed'}
title={notification.summary_fields.notification_template.name}
variant={notification.status === 'failed' ? 'danger' : 'success'}
key={`notification-template-alert-${notification.id}`}
ouiaId={`notification-template-alert-${notification.id}`}
>
<>
{notification.status === 'successful' && (
<p>{i18n._(t`Notification sent successfully`)}</p>
)}
{notification.status === 'failed' &&
notification?.error === 'timed out' && (
<p>{i18n._(t`Notification timed out`)}</p>
)}
{notification.status === 'failed' &&
notification?.error !== 'timed out' && (
<p>{notification.error}</p>
)}
</>
</Alert>
))}
</AlertGroup>
</>
);
}

View File

@ -1,11 +1,17 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { OrganizationsAPI } from '../../../api';
import {
NotificationsAPI,
NotificationTemplatesAPI,
OrganizationsAPI,
} from '../../../api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import NotificationTemplateList from './NotificationTemplateList';
jest.mock('../../../api');
jest.useFakeTimers();
const mockTemplates = {
data: {
count: 3,
@ -197,6 +203,43 @@ describe('<NotificationTemplateList />', () => {
expect(wrapper.find('ToolbarAddButton').length).toBe(1);
});
test('should show toast after test resolves', async () => {
NotificationTemplatesAPI.test.mockResolvedValueOnce({
data: {
notification: 9182,
},
});
NotificationsAPI.readDetail.mockResolvedValueOnce({
data: {
id: 9182,
status: 'failed',
error: 'There was an error with the notification',
summary_fields: {
notification_template: {
name: 'foobar',
},
},
},
});
await act(async () => {
wrapper = mountWithContexts(<NotificationTemplateList />);
});
wrapper.update();
expect(wrapper.find('Alert').length).toBe(0);
await act(async () => {
wrapper
.find('button[aria-label="Test Notification"]')
.at(0)
.simulate('click');
});
wrapper.update();
await act(async () => {
jest.runAllTimers();
});
wrapper.update();
expect(wrapper.find('Alert').length).toBe(1);
});
test('should hide add button (rbac)', async () => {
OrganizationsAPI.readOptions.mockResolvedValue({
data: {

View File

@ -11,13 +11,16 @@ import { timeOfDay } from '../../../util/dates';
import { NotificationTemplatesAPI, NotificationsAPI } from '../../../api';
import StatusLabel from '../../../components/StatusLabel';
import CopyButton from '../../../components/CopyButton';
import useRequest from '../../../util/useRequest';
import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';
import useRequest, { useDismissableError } from '../../../util/useRequest';
import { NOTIFICATION_TYPES } from '../constants';
const NUM_RETRIES = 25;
const RETRY_TIMEOUT = 5000;
function NotificationTemplateListItem({
onAddToast,
template,
detailUrl,
fetchTemplates,
@ -66,6 +69,7 @@ function NotificationTemplateListItem({
notificationId
);
if (notification.status !== 'pending') {
onAddToast(notification);
setStatus(notification.status);
return;
}
@ -76,9 +80,11 @@ function NotificationTemplateListItem({
}
setTimeout(pollForStatusChange, RETRY_TIMEOUT);
}, [template.id])
}, [template.id, onAddToast])
);
const { error: sendTestError, dismissError } = useDismissableError(error);
useEffect(() => {
if (error) {
setStatus('error');
@ -88,65 +94,78 @@ function NotificationTemplateListItem({
const labelId = `template-name-${template.id}`;
return (
<Tr id={`notification-template-row-${template.id}`}>
<Td
select={{
rowIndex,
isSelected,
onSelect,
}}
dataLabel={i18n._(t`Selected`)}
/>
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
<Link to={`${detailUrl}`}>
<b>{template.name}</b>
</Link>
</Td>
<Td dataLabel={i18n._(t`Status`)}>
{status && <StatusLabel status={status} />}
</Td>
<Td dataLabel={i18n._(t`Type`)}>
{NOTIFICATION_TYPES[template.notification_type] ||
template.notification_type}
</Td>
<ActionsTd dataLabel={i18n._(t`Actions`)}>
<ActionItem visible tooltip={i18n._(t`Test notification`)}>
<Button
aria-label={i18n._(t`Test Notification`)}
variant="plain"
onClick={sendTestNotification}
isDisabled={isLoading || status === 'running'}
<>
<Tr id={`notification-template-row-${template.id}`}>
<Td
select={{
rowIndex,
isSelected,
onSelect,
}}
dataLabel={i18n._(t`Selected`)}
/>
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
<Link to={`${detailUrl}`}>
<b>{template.name}</b>
</Link>
</Td>
<Td dataLabel={i18n._(t`Status`)}>
{status && <StatusLabel status={status} />}
</Td>
<Td dataLabel={i18n._(t`Type`)}>
{NOTIFICATION_TYPES[template.notification_type] ||
template.notification_type}
</Td>
<ActionsTd dataLabel={i18n._(t`Actions`)}>
<ActionItem visible tooltip={i18n._(t`Test notification`)}>
<Button
aria-label={i18n._(t`Test Notification`)}
variant="plain"
onClick={sendTestNotification}
isDisabled={isLoading || status === 'running'}
>
<BellIcon />
</Button>
</ActionItem>
<ActionItem
visible={template.summary_fields.user_capabilities.edit}
tooltip={i18n._(t`Edit`)}
>
<BellIcon />
</Button>
</ActionItem>
<ActionItem
visible={template.summary_fields.user_capabilities.edit}
tooltip={i18n._(t`Edit`)}
>
<Button
aria-label={i18n._(t`Edit Notification Template`)}
variant="plain"
component={Link}
to={`/notification_templates/${template.id}/edit`}
<Button
aria-label={i18n._(t`Edit Notification Template`)}
variant="plain"
component={Link}
to={`/notification_templates/${template.id}/edit`}
>
<PencilAltIcon />
</Button>
</ActionItem>
<ActionItem
visible={template.summary_fields.user_capabilities.copy}
tooltip={i18n._(t`Copy Notification Template`)}
>
<PencilAltIcon />
</Button>
</ActionItem>
<ActionItem
visible={template.summary_fields.user_capabilities.copy}
tooltip={i18n._(t`Copy Notification Template`)}
<CopyButton
copyItem={copyTemplate}
isCopyDisabled={isCopyDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
errorMessage={i18n._(t`Failed to copy template.`)}
/>
</ActionItem>
</ActionsTd>
</Tr>
{sendTestError && (
<AlertModal
isOpen
variant="error"
title={i18n._(t`Error!`)}
onClose={dismissError}
>
<CopyButton
copyItem={copyTemplate}
isCopyDisabled={isCopyDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
errorMessage={i18n._(t`Failed to copy template.`)}
/>
</ActionItem>
</ActionsTd>
</Tr>
{i18n._(t`Failed to send test notification.`)}
<ErrorDetail error={sendTestError} />
</AlertModal>
)}
</>
);
}