From 83a9c3470e9f29e536b9dc2594e687c648a7773a Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 15 Feb 2021 11:18:36 -0500 Subject: [PATCH 1/4] Adds toast to notification template list whenever test notification finishes --- .../NotificationTemplateList.jsx | 53 ++++++- .../NotificationTemplateList.test.jsx | 45 +++++- .../NotificationTemplateListItem.jsx | 135 ++++++++++-------- 3 files changed, 172 insertions(+), 61 deletions(-) diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx index c1b277b730..ed7d019072 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx @@ -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) => ( + + {testToasts + .filter(notification => notification.status !== 'pending') + .map(notification => ( + 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' && ( +

{i18n._(t`Notification sent successfully`)}

+ )} + {notification.status === 'failed' && + notification?.error === 'timed out' && ( +

{i18n._(t`Notification timed out`)}

+ )} + {notification.status === 'failed' && + notification?.error !== 'timed out' && ( +

{notification.error}

+ )} + +
+ ))} +
); } diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx index 345d2d0a42..f0bef1326c 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx @@ -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('', () => { 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(); + }); + 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: { diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx index 93d1f82999..71d32e9821 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx @@ -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 ( - - - - - {template.name} - - - - {status && } - - - {NOTIFICATION_TYPES[template.notification_type] || - template.notification_type} - - - - + + - - - - - + + - - - - + + + + {sendTestError && ( + - - - - + {i18n._(t`Failed to send test notification.`)} + + + )} + ); } From d96383b3174d0f741d83791f765367a7fe711b8a Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 15 Feb 2021 15:48:04 -0500 Subject: [PATCH 2/4] Fixes bug where some toasts would reappear after being closed --- .../NotificationTemplateList/NotificationTemplateList.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx index ed7d019072..2e8b9a0c80 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx @@ -114,7 +114,9 @@ function NotificationTemplatesList({ i18n }) { }, []); const removeTestToast = notificationId => { - setTestToasts(testToasts.filter(toast => toast.id !== notificationId)); + setTestToasts(oldToasts => + oldToasts.filter(toast => toast.id !== notificationId) + ); }; const canAdd = actions && actions.POST; From 811186308ca9292965b890b9a925cf377afb5dd1 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 18 Feb 2021 09:39:46 -0500 Subject: [PATCH 3/4] Adds note to changelog about notification toasts --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c527b004..39ebc4f856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This is a list of high-level changes for each release of AWX. A full list of com - Added ability to relaunch against failed hosts: https://github.com/ansible/awx/pull/9225 - Added pending workflow approval count to the application header https://github.com/ansible/awx/pull/9334 - Added user interface for management jobs: https://github.com/ansible/awx/pull/9224 +- Added toast message to show notification template test result to notification templates list https://github.com/ansible/awx/pull/9318 # 17.0.1 (January 26, 2021) - Fixed pgdocker directory permissions issue with Local Docker installer: https://github.com/ansible/awx/pull/9152 From 6a3216443829381da2692ddf3fa88329096a7855 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 8 Mar 2021 16:11:22 -0500 Subject: [PATCH 4/4] Adds support for ouiaId's on copy buttons --- awx/ui_next/src/components/CopyButton/CopyButton.jsx | 4 ++++ .../NotificationTemplateList/NotificationTemplateList.jsx | 2 +- .../NotificationTemplateList/NotificationTemplateListItem.jsx | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CopyButton/CopyButton.jsx b/awx/ui_next/src/components/CopyButton/CopyButton.jsx index 2856c69c0c..1f5dd9cff4 100644 --- a/awx/ui_next/src/components/CopyButton/CopyButton.jsx +++ b/awx/ui_next/src/components/CopyButton/CopyButton.jsx @@ -17,6 +17,7 @@ function CopyButton({ onCopyFinish, errorMessage, i18n, + ouiaId, }) { const { isLoading, error: copyError, request: copyItemToAPI } = useRequest( copyItem @@ -35,6 +36,7 @@ function CopyButton({ <>