diff --git a/cypress/support/repositories/organizations/NotificationsRepository.ts b/cypress/support/repositories/organizations/NotificationsRepository.ts index 24551338..8694e2cb 100644 --- a/cypress/support/repositories/organizations/NotificationsRepository.ts +++ b/cypress/support/repositories/organizations/NotificationsRepository.ts @@ -1,6 +1,6 @@ export default class NotificationsRepository { getNotificationRowParents(notification: string) { - return cy.getBySel('notification-row').contains(notification).parent().parent(); + return cy.getBySel('notification-row').contains(notification).parent().parent().parent(); } getAddNotification() { return cy.getBySel('addNotification'); diff --git a/src/components/Organizations/Notifications/Styles.tsx b/src/components/Organizations/Notifications/Styles.tsx index d74e8ad2..c01ecdae 100644 --- a/src/components/Organizations/Notifications/Styles.tsx +++ b/src/components/Organizations/Notifications/Styles.tsx @@ -130,6 +130,26 @@ export const StyledOrgNotifications = styled.div` } font-weight: normal; } + .notificationdata { + font-family: 'roboto', sans-serif; + font-size: 1rem; + min-height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + p { + margin: unset; + line-height: 24px; + } + word-wrap: break-word; + word-wrap: break-word; + word-break: break-all; + .comment { + font-size: 10px; + } + font-weight: normal; + } + .notifdata { font-family: 'roboto', sans-serif; font-size: 1rem; @@ -287,6 +307,17 @@ export const StyledOrgNotifications = styled.div` line-height: 24px; } `; + +export const NameTagColumn = styled.div` + display: flex; + width: 100%; + justify-content: space-between; + height: 100%; + display: flex; + align-items: center; + transition: all 0.3s ease; +`; + export const NameTagCol = styled.div` width: 25%; display: flex; diff --git a/src/components/Organizations/Notifications/index.js b/src/components/Organizations/Notifications/index.js index 88e64b61..cfa06c0b 100644 --- a/src/components/Organizations/Notifications/index.js +++ b/src/components/Organizations/Notifications/index.js @@ -1,17 +1,17 @@ -import React, { useState } from 'react'; +import React, { Fragment, useState } from 'react'; import { Mutation } from 'react-apollo'; import { EditOutlined } from '@ant-design/icons'; -import { Tooltip } from 'antd'; +import { Tooltip, notification } from 'antd'; import Button from 'components/Button'; import Modal from 'components/Modal'; import RemoveNotificationConfirm from 'components/Organizations/RemoveNotificationConfirm'; import gql from 'graphql-tag'; -import OrgHeader from '../Orgheader'; -import { AddButtonContent, Footer, ModalChildren, ViewMore } from '../SharedStyles'; +import PaginatedTable from '../PaginatedTable/PaginatedTable'; +import { AddButtonContent, Footer, ModalChildren, TableActions, ViewMore } from '../SharedStyles'; import AddNotifications from './AddNotifications'; -import { AddNotifButton, NameTagCol, StyledOrgNotifications } from './Styles'; +import { AddNotifButton, NameTagColumn, StyledOrgNotifications } from './Styles'; const REMOVE_NOTIFICATION_SLACK = gql` mutation removeNotification($name: String!) { @@ -92,44 +92,6 @@ const OrgNotifications = ({ refresh, organizationId, }) => { - const [searchInput, setSearchInput] = useState(''); - - const filteredSlackNotifications = slacks.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase()); - const sortByChannel = key.channel.toLowerCase().includes(searchInput.toLowerCase()); - const sortByWebhook = key.webhook.toLowerCase().includes(searchInput.toLowerCase()); - return ['name', '__typename', 'webhook', 'channel'].includes(key) - ? false - : (true && sortByName) || (true && sortByChannel) || (true && sortByWebhook); - }); - - const filteredRocketChatNotifications = rocketchats.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase()); - const sortByChannel = key.channel.toLowerCase().includes(searchInput.toLowerCase()); - const sortByWebhook = key.webhook.toLowerCase().includes(searchInput.toLowerCase()); - return ['name', '__typename', 'webhook', 'channel'].includes(key) - ? false - : (true && sortByName) || (true && sortByChannel) || (true && sortByWebhook); - }); - - const filteredTeamsNotifications = teams.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase()); - const sortByWebhook = key.webhook.toLowerCase().includes(searchInput.toLowerCase()); - return ['name', '__typename', 'webhook'].includes(key) ? false : (true && sortByName) || (true && sortByWebhook); - }); - - const filteredWebhookNotifications = webhooks.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase()); - const sortByWebhook = key.webhook.toLowerCase().includes(searchInput.toLowerCase()); - return ['name', '__typename', 'webhook'].includes(key) ? false : (true && sortByName) || (true && sortByWebhook); - }); - - const filteredEmailNotifications = emails.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase()); - const sortByEmail = key.emailAddress.toLowerCase().includes(searchInput.toLowerCase()); - return ['name', '__typename', 'emailAddress'].includes(key) ? false : (true && sortByName) || (true && sortByEmail); - }); - const [modalOpen, setModalOpen] = useState(false); const [valueModalOpen, setValueModalOpen] = useState(false); @@ -142,6 +104,18 @@ const OrgNotifications = ({ const [editState, setEditState] = useState(initialEditState); + const [api, contextHolder] = notification.useNotification({ maxCount: 1 }); + + const openNotificationWithIcon = errorMessage => { + api['error']({ + message: 'There was a problem deleting notification.', + description: errorMessage, + placement: 'top', + duration: 0, + style: { width: '500px' }, + }); + }; + const closeEditModal = () => { setEditState(initialEditState); }; @@ -181,673 +155,689 @@ const OrgNotifications = ({

); }; - return ( - - setSearchInput(e.target.value), - }} - /> -
- {!slacks.length && !rocketchats.length && !emails.length && !teams.length && !webhooks.length && ( -
No notifications
- )} - {searchInput && - !filteredSlackNotifications.length && - !filteredEmailNotifications.length && - !filteredRocketChatNotifications.length && - !filteredTeamsNotifications.length && - !filteredWebhookNotifications.length && ( -
No notifications matching "{searchInput}"
- )} - - {filteredSlackNotifications.map(project => ( -
- -
{project.name}
-
- -
-
-
- {renderWebbook(project.webhook, () => { - setEditState({ open: false, current: project }); - })} -

Channel: {project.channel}

-
-
- - { - setEditState({ open: true, current: project, type: 'slack' }); - }} - > - - - - - SLACK, + + notifData: notification => { + return ( +
+ {renderWebbook(notification.webhook, () => { + setEditState({ open: false, current: notification }); + })} +

Channel: {notification.channel}

+
+ ); + }, + + actions: notification => { + return ( + + + { + setEditState({ open: true, current: notification, type: 'slack' }); + }} > - -
- -
- -
- -
- -
- -
-
-
- console.error(e)}> - {(updateSlack, { called, error, data }) => { - if (error) { - return
{error.message}
; - } - if (data) { - refresh().then(() => { - closeEditModal(); - }); - } - return ( - - ); - }} -
- -
-
-
- console.error(e)}> - {(removeNotification, { called, error, data }) => { + + + + + + +
+ +
+ +
+ +
+ +
+ +
+
+
+ console.error(e)}> + {(updateSlack, { called, error, data }) => { if (error) { - return
{error.message}
; + return
{error.message}
; } if (data) { - refresh(); + refresh().then(() => { + closeEditModal(); + }); } return ( - - removeNotification({ + disabled={called} + action={() => { + updateSlack({ variables: { - name: project.name, + name: notification.name, + patch: { + ...(editState.current.name ? { name: editState.current.name } : {}), + ...(editState.current.channel ? { channel: editState.current.channel } : {}), + ...(editState.current.webhook ? { webhook: editState.current.webhook } : {}), + }, }, - }) - } - /> + }); + }} + > + Continue + ); }}
-
-
-
- ))} - {filteredRocketChatNotifications.map(project => ( -
- -
{project.name}
-
- -
-
-
- {renderWebbook(project.webhook, () => { - setEditState({ open: false, current: project }); - })} -

Channel: {project.channel}

-
-
- - setEditState({ open: true, current: project, type: 'rocketchat' })} - > - - - - - + Cancel + + + + + console.error(e)}> + {(removeNotification, { called, error, data }) => { + if (data) { + refresh(); + } + return ( + + removeNotification({ + variables: { + name: notification.name, + }, + }) + } + /> + ); + }} + + + ); + }, + }, + NotificationRocketChat: { + label: , + notifData: notification => { + return notificationColumnMap.NotificationSlack.notifData(notification); + }, + actions: notification => { + return ( + + + setEditState({ open: true, current: notification, type: 'rocketchat' })} > - -
- -
- -
- -
- -
- -
-
-
- console.error(e)}> - {(updateRocketChat, { called, error, data }) => { - if (error) { - return
{error.message}
; - } - if (data) { - refresh().then(() => { - closeEditModal(); - }); - } - return ( - - ); - }} -
- -
- -
- console.error(e)}> - {(removeNotification, { called, error, data }) => { + + + + + + +
+ +
+ +
+ +
+ +
+ +
+
+
+ console.error(e)}> + {(updateRocketChat, { called, error, data }) => { if (error) { - return
{error.message}
; + return
{error.message}
; } if (data) { - refresh(); + refresh().then(() => { + closeEditModal(); + }); } return ( - - removeNotification({ + disabled={called} + action={() => { + updateRocketChat({ variables: { - name: project.name, + name: notification.name, + patch: { + ...(editState.current.name ? { name: editState.current.name } : {}), + ...(editState.current.channel ? { channel: editState.current.channel } : {}), + ...(editState.current.webhook ? { webhook: editState.current.webhook } : {}), + }, }, - }) - } - /> + }); + }} + > + Continue + ); }}
-
-
+ + + + console.error(e)}> + {(removeNotification, { called, error, data }) => { + if (error) { + openNotificationWithIcon(error.message); + } + if (data) { + refresh(); + } + return ( + + removeNotification({ + variables: { + name: notification.name, + }, + }) + } + /> + ); + }} + + + ); + }, + }, + NotificationEmail: { + label: , + notifData: notification => { + return ( +
+

Address: {notification.emailAddress}

- ))} - {filteredEmailNotifications.map(project => ( -
- -
{project.name}
-
- -
-
-
-

Address: {project.emailAddress}

-
-
- - setEditState({ open: true, current: project, type: 'email' })}> - - - - - -
- -
- -
- - {!isValidEmail &&

Invalid email address

} -
-
-
- console.error(e)}> - {(updateEmail, { called, error, data }) => { - if (error) { - return
{error.message}
; - } - if (data) { - refresh().then(() => { - closeEditModal(); - }); - } - return ( - - ); - }} -
- -
-
-
- console.error(e)}> - {(removeNotification, { called, error, data }) => { + ); + }, + actions: notification => { + return ( + + + setEditState({ open: true, current: notification, type: 'email' })}> + + + + + +
+ +
+ +
+ + {!isValidEmail &&

Invalid email address

} +
+
+
+ console.error(e)}> + {(updateEmail, { called, error, data }) => { if (error) { - return
{error.message}
; + return
{error.message}
; } if (data) { - refresh(); + refresh().then(() => { + closeEditModal(); + }); } return ( - - removeNotification({ + disabled={called || !isValidEmail} + variant="primary" + action={() => { + updateEmail({ variables: { - name: project.name, + name: notification.name, + patch: { + ...(editState.current.name ? { name: editState.current.name } : {}), + ...(editState.current.emailAddress + ? { emailAddress: editState.current.emailAddress } + : {}), + }, }, - }) - } - /> + }); + }} + > + Continue + ); }}
-
-
+ + + + + console.error(e)}> + {(removeNotification, { called, error, data }) => { + if (data) { + refresh(); + } + return ( + + removeNotification({ + variables: { + name: notification.name, + }, + }) + } + /> + ); + }} + + + ); + }, + }, + NotificationWebhook: { + label: , + + notifData: notification => { + return ( +
+ {renderWebbook(notification.webhook, () => { + setEditState({ open: false, current: notification }); + })}
- ))} - {filteredWebhookNotifications.map(project => ( -
- -
{project.name}
-
- -
-
-
- {renderWebbook(project.webhook, () => { - setEditState({ open: false, current: project }); - })} -
-
- - setEditState({ open: true, current: project, type: 'webhook' })}> - - - - { + return ( + + + setEditState({ open: true, current: notification, type: 'webhook' })} > - -
- -
- -
- -
-
-
- console.error(e)}> - {(updateWebhook, { called, error, data }) => { - if (error) { - return
{error.message}
; - } - if (data) { - refresh().then(() => { - closeEditModal(); - }); - } - return ( - - ); - }} -
- -
-
- -
- console.error(e)}> - {(removeNotification, { called, error, data }) => { + + + + + +
+ +
+ +
+ +
+
+
+ console.error(e)}> + {(updateWebhook, { called, error, data }) => { if (error) { - return
{error.message}
; + return
{error.message}
; } if (data) { - refresh(); + refresh().then(() => { + closeEditModal(); + }); } return ( - - removeNotification({ + disabled={called} + variant="primary" + action={() => { + updateWebhook({ variables: { - name: project.name, + name: notification.name, + patch: { + ...(editState.current.name ? { name: editState.current.name } : {}), + ...(editState.current.webhook ? { webhook: editState.current.webhook } : {}), + }, }, - }) - } - /> + }); + }} + > + Continue + ); }}
-
-
+ + + + + console.error(e)}> + {(removeNotification, { called, error, data }) => { + if (data) { + refresh(); + } + return ( + + removeNotification({ + variables: { + name: notification.name, + }, + }) + } + /> + ); + }} + + + ); + }, + }, + NotificationMicrosoftTeams: { + label: , + notifData: notification => { + return ( +
+ {renderWebbook(notification.webhook, () => { + setEditState({ open: false, current: notification }); + })}
- ))} - {filteredTeamsNotifications.map(project => ( -
- -
{project.name}
-
- -
-
- -
- {renderWebbook(project.webhook, () => { - setEditState({ open: false, current: project }); - })} -
-
- - setEditState({ open: true, current: project, type: 'teams' })}> - - - - - -
- -
- -
- -
-
-
- console.error(e)}> - {(updateTeams, { called, error, data }) => { - if (error) { - return
{error.message}
; - } - if (data) { - refresh().then(() => { - closeEditModal(); - }); - } - return ( - - ); - }} -
- -
-
-
- console.error(e)}> - {(removeNotification, { called, error, data }) => { + ); + }, + actions: notification => { + return ( + + + setEditState({ open: true, current: notification, type: 'teams' })}> + + + + + +
+ +
+ +
+ +
+
+
+ console.error(e)}> + {(updateTeams, { called, error, data }) => { if (error) { - return
{error.message}
; + return
{error.message}
; } if (data) { - refresh(); + refresh().then(() => { + closeEditModal(); + }); } return ( - - removeNotification({ + disabled={called} + variant="primary" + action={() => { + updateTeams({ variables: { - name: project.name, + name: notification.name, + patch: { + ...(editState.current.name ? { name: editState.current.name } : {}), + ...(editState.current.webhook ? { webhook: editState.current.webhook } : {}), + }, }, - }) - } - /> + }); + }} + > + Continue + ); }}
-
-
-
- ))} -
+ + + + console.error(e)}> + {(removeNotification, { called, error, data }) => { + if (data) { + refresh(); + } + return ( + + removeNotification({ + variables: { + name: notification.name, + }, + }) + } + /> + ); + }} + + + ); + }, + }, + }; + + const notificationColumns = [ + { + width: '25%', + key: 'name', + render: notification => { + return ( + // data-cy="notification-row"> + +
{notification.name}
+
{notificationColumnMap[notification.__typename].label}
+
+ ); + }, + }, + { + width: '50%', + name: 'notifdata', + key: 'notifdata', + render: notification => { + return notificationColumnMap[notification.__typename].notifData(notification); + }, + }, + { + width: '25%', + name: 'actions', + key: 'actions', + render: notification => { + return {notificationColumnMap[notification.__typename].actions(notification)}; + }, + }, + ]; + + return ( + + {contextHolder} + diff --git a/src/components/Organizations/PaginatedTable/PaginatedTable.tsx b/src/components/Organizations/PaginatedTable/PaginatedTable.tsx index 35672ad6..3b5e7676 100644 --- a/src/components/Organizations/PaginatedTable/PaginatedTable.tsx +++ b/src/components/Organizations/PaginatedTable/PaginatedTable.tsx @@ -49,6 +49,7 @@ interface Props { labelText?: string; limit: number; emptyText: string; + rowTestName?: string; } // currently possible... @@ -76,6 +77,7 @@ const PaginatedTable: FC = ({ labelText, limit = 10, disableUrlMutation = false, + rowTestName, }) => { const params = new URLSearchParams(window.location.search); @@ -107,13 +109,57 @@ const PaginatedTable: FC = ({ }, [data]); const sortedFilteredData = useMemo(() => { - let filtered = !searchStr - ? unfilteredData - : unfilteredData.filter(key => { - // @ts-ignore - const k = !usersTable ? key.name : ((key.user ? key.user?.email : key.email) as string); - return k.toLowerCase().includes(searchStr.toLowerCase()); - }); + let filtered; + + if (labelText !== 'Notifications') { + filtered = !searchStr + ? unfilteredData + : unfilteredData.filter(key => { + // @ts-ignore + const k = !usersTable ? key.name : ((key.user ? key.user?.email : key.email) as string); + return k.toLowerCase().includes(searchStr.toLowerCase()); + }); + } else { + filtered = !searchStr + ? unfilteredData + : unfilteredData.filter(item => { + const sortByName = item.name.toLowerCase().includes(searchStr.toLowerCase()); + const sortByChannel = item.channel && String(item.channel).toLowerCase().includes(searchStr.toLowerCase()); + const sortByWebhook = item.webhook && String(item.webhook).toLowerCase().includes(searchStr.toLowerCase()); + const sortByEmail = + item.emailAddress && String(item.emailAddress).toLowerCase().includes(searchStr.toLowerCase()); + + switch (item.__typename) { + case 'NotificationSlack': + return ['name', '__typename', 'webhook', 'channel'].includes(item.__typename) + ? false + : (true && sortByName) || (true && sortByChannel) || (true && sortByWebhook); + + case 'NotificationRocketChat': + return ['name', '__typename', 'webhook', 'channel'].includes(item.__typename) + ? false + : (true && sortByName) || (true && sortByChannel) || (true && sortByWebhook); + + case 'NotificationMicrosoftTeams': + return ['name', '__typename', 'webhook'].includes(item.__typename) + ? false + : (true && sortByName) || (true && sortByWebhook); + + case 'NotificationWebhook': + return ['name', '__typename', 'webhook'].includes(item.__typename) + ? false + : (true && sortByName) || (true && sortByWebhook); + + case 'NotificationEmail': + return ['name', '__typename', 'emailAddress'].includes(item.__typename) + ? false + : (true && sortByName) || (true && sortByEmail); + + default: + return true && sortByName; + } + }); + } if (!defaultsSelected) { if (defaultViewOptions?.type === 'group') { @@ -327,7 +373,7 @@ const PaginatedTable: FC = ({ resultsToDisplay.map((i, idx) => { return ( diff --git a/src/components/Organizations/PaginatedTable/Styles.tsx b/src/components/Organizations/PaginatedTable/Styles.tsx index a7fc2e99..956b504a 100644 --- a/src/components/Organizations/PaginatedTable/Styles.tsx +++ b/src/components/Organizations/PaginatedTable/Styles.tsx @@ -63,8 +63,7 @@ export const TableColumn = styled.div<{ width: string }>` border-right: 2px solid ${props => props.theme.borders.tableRow}; } transition: all 0.3s ease; - padding: 25px 13px; - height: 61px; + padding: 15px 13px; width: ${props => props.width}; align-items: center; display: flex; diff --git a/src/components/Organizations/RemoveNotificationConfirm/index.js b/src/components/Organizations/RemoveNotificationConfirm/index.js index 80d4511f..ec9cd06b 100644 --- a/src/components/Organizations/RemoveNotificationConfirm/index.js +++ b/src/components/Organizations/RemoveNotificationConfirm/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { DeleteOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; @@ -11,7 +11,20 @@ import { Footer, RemoveModalHeader, RemoveModalParagraph } from '../SharedStyles /** * Confirms the removal of the specified email from group */ -export const RemoveNotificationConfirm = ({ info, onRemove, open, openModal, closeModal, loading }) => { +export const RemoveNotificationConfirm = ({ + info, + onRemove, + open, + openModal, + closeModal, + loading, + error, + openNotificationWithIcon, +}) => { + useEffect(() => { + if (error) openNotificationWithIcon(error.message); + }, [error]); + return ( @@ -27,7 +40,13 @@ export const RemoveNotificationConfirm = ({ info, onRemove, open, openModal, clo This action will delete {info.name} notification.