From a2717f639ee659c12d8a2aacbf7a35fb630ff171 Mon Sep 17 00:00:00 2001
From: Davit Darsavelidze <76407236+DaveDarsa@users.noreply.github.com>
Date: Wed, 15 May 2024 15:36:43 +0400
Subject: [PATCH] Fix: Notification count, pagination, filtering, gql error
displays (#253)
* add pagination/better error display/count to org notifications
* format
* add keys to fragments
---
.../organizations/NotificationsRepository.ts | 2 +-
.../Organizations/Notifications/Styles.tsx | 31 +
.../Organizations/Notifications/index.js | 1286 ++++++++---------
.../PaginatedTable/PaginatedTable.tsx | 62 +-
.../Organizations/PaginatedTable/Styles.tsx | 3 +-
.../RemoveNotificationConfirm/index.js | 25 +-
6 files changed, 747 insertions(+), 662 deletions(-)
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)}>
- {(removeNotification, { called, error, data }) => {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- ))}
- {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)}>
- {(removeNotification, { called, error, data }) => {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
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)}>
- {(removeNotification, { called, error, data }) => {
+ );
+ },
+ actions: notification => {
+ return (
+
+
+ setEditState({ open: true, current: notification, type: 'email' })}>
+
+
+
+
+
+
+
+
+
+
+
+ {!isValidEmail &&
Invalid email address
}
+
+
+
-
+
+
+
+
+
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)}>
- {(removeNotification, { called, error, data }) => {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
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)}>
- {(removeNotification, { called, error, data }) => {
+ );
+ },
+ actions: notification => {
+ return (
+
+
+ setEditState({ open: true, current: notification, type: 'teams' })}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- ))}
-
+
+
+
+
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.