Skip to content

Commit

Permalink
Adds toast to notification template list whenever test notification f…
Browse files Browse the repository at this point in the history
…inishes
  • Loading branch information
mabashian committed Feb 15, 2021
1 parent 4e48118 commit 527df90
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -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 PaginatedDataList, {
ToolbarAddButton,
Expand All @@ -25,6 +31,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`;

Expand Down Expand Up @@ -98,6 +105,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 (
Expand Down Expand Up @@ -181,6 +196,7 @@ function NotificationTemplatesList({ i18n }) {
)}
renderItem={template => (
<NotificationTemplateListItem
onAddToast={addTestToast}
key={template.id}
fetchTemplates={fetchTemplates}
template={template}
Expand All @@ -204,6 +220,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>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -185,6 +191,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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { NotificationTemplatesAPI, NotificationsAPI } from '../../../api';
import DataListCell from '../../../components/DataListCell';
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 DataListAction = styled(_DataListAction)`
Expand All @@ -33,6 +35,7 @@ const NUM_RETRIES = 25;
const RETRY_TIMEOUT = 5000;

function NotificationTemplateListItem({
onAddToast,
template,
detailUrl,
fetchTemplates,
Expand Down Expand Up @@ -80,6 +83,7 @@ function NotificationTemplateListItem({
notificationId
);
if (notification.status !== 'pending') {
onAddToast(notification);
setStatus(notification.status);
return;
}
Expand All @@ -90,9 +94,11 @@ function NotificationTemplateListItem({
}

setTimeout(pollForStatusChange, RETRY_TIMEOUT);
}, [template.id])
}, [template.id, onAddToast])
);

const { error: sendTestError, dismissError } = useDismissableError(error);

useEffect(() => {
if (error) {
setStatus('error');
Expand All @@ -102,76 +108,93 @@ function NotificationTemplateListItem({
const labelId = `template-name-${template.id}`;

return (
<DataListItem key={template.id} aria-labelledby={labelId} id={template.id}>
<DataListItemRow>
<DataListCheck
id={`select-template-${template.id}`}
checked={isSelected}
onChange={onSelect}
aria-labelledby={labelId}
/>
<DataListItemCells
dataListCells={[
<DataListCell key="name" id={labelId}>
<Link to={detailUrl}>
<b>{template.name}</b>
</Link>
</DataListCell>,
<DataListCell key="status">
{status && <StatusLabel status={status} />}
</DataListCell>,
<DataListCell key="type">
<strong>{i18n._(t`Type:`)}</strong>{' '}
{NOTIFICATION_TYPES[template.notification_type] ||
template.notification_type}
</DataListCell>,
]}
/>
<DataListAction
aria-label={i18n._(t`actions`)}
aria-labelledby={labelId}
>
<Tooltip content={i18n._(t`Test Notification`)} position="top">
<Button
aria-label={i18n._(t`Test Notification`)}
variant="plain"
onClick={sendTestNotification}
isDisabled={isLoading || status === 'running'}
>
<BellIcon />
</Button>
</Tooltip>
{template.summary_fields.user_capabilities.edit ? (
<Tooltip
content={i18n._(t`Edit Notification Template`)}
position="top"
>
<>
<DataListItem
key={template.id}
aria-labelledby={labelId}
id={template.id}
>
<DataListItemRow>
<DataListCheck
id={`select-template-${template.id}`}
checked={isSelected}
onChange={onSelect}
aria-labelledby={labelId}
/>
<DataListItemCells
dataListCells={[
<DataListCell key="name" id={labelId}>
<Link to={detailUrl}>
<b>{template.name}</b>
</Link>
</DataListCell>,
<DataListCell key="status">
{status && <StatusLabel status={status} />}
</DataListCell>,
<DataListCell key="type">
<strong>{i18n._(t`Type:`)}</strong>{' '}
{NOTIFICATION_TYPES[template.notification_type] ||
template.notification_type}
</DataListCell>,
]}
/>
<DataListAction
aria-label={i18n._(t`actions`)}
aria-labelledby={labelId}
>
<Tooltip content={i18n._(t`Test Notification`)} position="top">
<Button
aria-label={i18n._(t`Edit Notification Template`)}
aria-label={i18n._(t`Test Notification`)}
variant="plain"
component={Link}
to={`/notification_templates/${template.id}/edit`}
onClick={sendTestNotification}
isDisabled={isLoading || status === 'running'}
>
<PencilAltIcon />
<BellIcon />
</Button>
</Tooltip>
) : (
<div />
)}
{template.summary_fields.user_capabilities.copy && (
<Tooltip content={i18n._(t`Copy Notification Template`)}>
<CopyButton
copyItem={copyTemplate}
isCopyDisabled={isCopyDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
errorMessage={i18n._(t`Failed to copy template.`)}
/>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
{template.summary_fields.user_capabilities.edit ? (
<Tooltip
content={i18n._(t`Edit Notification Template`)}
position="top"
>
<Button
aria-label={i18n._(t`Edit Notification Template`)}
variant="plain"
component={Link}
to={`/notification_templates/${template.id}/edit`}
>
<PencilAltIcon />
</Button>
</Tooltip>
) : (
<div />
)}
{template.summary_fields.user_capabilities.copy && (
<Tooltip content={i18n._(t`Copy Notification Template`)}>
<CopyButton
copyItem={copyTemplate}
isCopyDisabled={isCopyDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
errorMessage={i18n._(t`Failed to copy template.`)}
/>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
{sendTestError && (
<AlertModal
isOpen
variant="error"
title={i18n._(t`Error!`)}
onClose={dismissError}
>
{i18n._(t`Failed to send test notification.`)}
<ErrorDetail error={sendTestError} />
</AlertModal>
)}
</>
);
}

Expand Down

0 comments on commit 527df90

Please sign in to comment.