From f92a49fda9b1bdb3e97823f575775e57ede6b0fe Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Wed, 20 Oct 2021 16:42:37 -0400 Subject: [PATCH] Adds ability to send slack notification to a thread, updates tooltip in ui, and adds test button to notification details view --- awx/main/notifications/slack_backend.py | 11 ++- .../tests/unit/notifications/test_slack.py | 6 +- .../NotificationTemplateDetail.js | 84 ++++++++++++++----- .../NotificationTemplateDetail.test.js | 2 +- .../shared/TypeInputsSubForm.js | 10 ++- 5 files changed, 87 insertions(+), 26 deletions(-) diff --git a/awx/main/notifications/slack_backend.py b/awx/main/notifications/slack_backend.py index 20b7f72f14..73364dc037 100644 --- a/awx/main/notifications/slack_backend.py +++ b/awx/main/notifications/slack_backend.py @@ -36,10 +36,17 @@ class SlackBackend(AWXBaseEmailBackend, CustomNotificationBase): for r in m.recipients(): if r.startswith('#'): r = r[1:] + thread = None + channel = r + thread = None + if ',' in r: + channel, thread = r.split(',') if self.color: - response = client.chat_postMessage(channel=r, as_user=True, attachments=[{"color": self.color, "text": m.subject}]) + response = client.chat_postMessage( + channel=channel, thread_ts=thread, as_user=True, attachments=[{"color": self.color, "text": m.subject}] + ) else: - response = client.chat_postMessage(channel=r, as_user=True, text=m.subject) + response = client.chat_postMessage(channel=channel, thread_ts=thread, as_user=True, text=m.subject) logger.debug(response) if response['ok']: sent_messages += 1 diff --git a/awx/main/tests/unit/notifications/test_slack.py b/awx/main/tests/unit/notifications/test_slack.py index e4bf3d66ac..7428c74722 100644 --- a/awx/main/tests/unit/notifications/test_slack.py +++ b/awx/main/tests/unit/notifications/test_slack.py @@ -24,7 +24,7 @@ def test_send_messages(): message, ] ) - WebClient_mock.chat_postMessage.assert_called_once_with(channel='random', as_user=True, text='test subject') + WebClient_mock.chat_postMessage.assert_called_once_with(channel='random', thread_ts=None, as_user=True, text='test subject') assert sent_messages == 1 @@ -47,7 +47,9 @@ def test_send_messages_with_color(): ] ) - WebClient_mock.chat_postMessage.assert_called_once_with(channel='random', as_user=True, attachments=[{'color': '#006699', 'text': 'test subject'}]) + WebClient_mock.chat_postMessage.assert_called_once_with( + channel='random', as_user=True, thread_ts=None, attachments=[{'color': '#006699', 'text': 'test subject'}] + ) assert sent_messages == 1 diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js index 19b13c189e..0f1e13376a 100644 --- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js +++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useState, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { Button, @@ -20,13 +20,20 @@ import { import CodeDetail from 'components/DetailList/CodeDetail'; import DeleteButton from 'components/DeleteButton'; import ErrorDetail from 'components/ErrorDetail'; -import { NotificationTemplatesAPI } from 'api'; +import { NotificationTemplatesAPI, NotificationsAPI } from 'api'; import useRequest, { useDismissableError } from 'hooks/useRequest'; +import StatusLabel from 'components/StatusLabel'; import hasCustomMessages from '../shared/hasCustomMessages'; import { NOTIFICATION_TYPES } from '../constants'; +const NUM_RETRIES = 25; +const RETRY_TIMEOUT = 5000; + function NotificationTemplateDetail({ template, defaultMessages }) { const history = useHistory(); + const [testStatus, setTestStatus] = useState( + template.summary_fields?.recent_notifications[0]?.status ?? undefined + ); const { created, @@ -64,9 +71,35 @@ function NotificationTemplateDetail({ template, defaultMessages }) { }, [template.id, history]) ); - const { error, dismissError } = useDismissableError(deleteError); - const typeMessageDefaults = defaultMessages[template.notification_type]; + const { request: sendTestNotification, error: testError } = useRequest( + useCallback(async () => { + setTestStatus('running'); + let retries = NUM_RETRIES; + const { + data: { notification: notificationId }, + } = await NotificationTemplatesAPI.test(template.id); + + async function pollForStatusChange() { + const { data: notification } = await NotificationsAPI.readDetail( + notificationId + ); + if (notification.status !== 'pending') { + setTestStatus(notification.status); + return; + } + retries--; + if (retries > 0) { + setTimeout(pollForStatusChange, RETRY_TIMEOUT); + } + } + + setTimeout(pollForStatusChange, RETRY_TIMEOUT); + }, [template.id]) + ); + + const { error, dismissError } = useDismissableError(deleteError || testError); + const typeMessageDefaults = defaultMessages[template.notification_type]; return ( @@ -76,6 +109,12 @@ function NotificationTemplateDetail({ template, defaultMessages }) { value={template.description} dataCy="nt-detail-description" /> + {summary_fields.recent_notifications.length && ( + } + /> + )} {summary_fields.organization ? ( - {summary_fields.user_capabilities && - summary_fields.user_capabilities.edit && ( + {summary_fields.user_capabilities?.edit && ( + <> - )} - {summary_fields.user_capabilities && - summary_fields.user_capabilities.delete && ( - - {t`Delete`} - - )} + + + )} + {summary_fields.user_capabilities?.delete && ( + + {t`Delete`} + + )} {error && ( - {t`Failed to delete notification.`} + {deleteError + ? t`Failed to delete notification.` + : t`Notification test failed.`} )} diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js index 433ab7978a..829f272d45 100644 --- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js +++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js @@ -45,7 +45,7 @@ const mockTemplate = { delete: true, copy: true, }, - recent_notifications: [], + recent_notifications: [{ status: 'success' }], }, created: '2021-06-16T18:52:23.811374Z', modified: '2021-06-16T18:53:37.631371Z', diff --git a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js b/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js index e15518f406..df17414536 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js +++ b/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js @@ -362,8 +362,14 @@ function SlackFields() { type="textarea" validate={required(null)} isRequired - tooltip={t`Enter one Slack channel per line. The pound symbol (#) - is required for channels.`} + tooltip={ + <> + {t`Enter one Slack channel per line. The pound symbol (#) + is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack`}{' '} + {t`documentation`}{' '} + {t`for more information.`} + + } />