diff --git a/__tests__/components/DataListToolbar.test.jsx b/__tests__/components/DataListToolbar.test.jsx
index facc3b1128..958573e2f1 100644
--- a/__tests__/components/DataListToolbar.test.jsx
+++ b/__tests__/components/DataListToolbar.test.jsx
@@ -36,6 +36,7 @@ describe('', () => {
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
+ showSelectAll
/>
);
diff --git a/__tests__/components/NotificationList.test.jsx b/__tests__/components/NotificationList.test.jsx
new file mode 100644
index 0000000000..893f7a58a0
--- /dev/null
+++ b/__tests__/components/NotificationList.test.jsx
@@ -0,0 +1,160 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { MemoryRouter } from 'react-router-dom';
+import { I18nProvider } from '@lingui/react';
+import Notifications from '../../src/components/NotificationsList/Notifications.list';
+
+describe('', () => {
+ test('initially renders succesfully', () => {
+ mount(
+
+
+
+
+
+ );
+ });
+ test('fetches notifications on mount', () => {
+ const spy = jest.spyOn(Notifications.prototype, 'fetchNotifications');
+ mount(
+
+
+
+
+
+ );
+ expect(spy).toHaveBeenCalled();
+ });
+ test('toggle success calls post', () => {
+ const spy = jest.spyOn(Notifications.prototype, 'postToSuccess');
+ const wrapper = mount(
+
+
+
+
+
+ ).find('Notifications');
+ wrapper.instance().toggleNotification(1, true, 'success');
+ expect(spy).toHaveBeenCalledWith(1, true);
+ });
+ test('post success makes request and updates state properly', async () => {
+ const postSuccessFn = jest.fn();
+ const wrapper = mount(
+
+
+
+
+
+ ).find('Notifications');
+ wrapper.setState({ successTemplateIds: [44] });
+ await wrapper.instance().postToSuccess(44, true);
+ expect(postSuccessFn).toHaveBeenCalledWith(1, { id: 44, disassociate: true });
+ expect(wrapper.state('successTemplateIds')).not.toContain(44);
+ await wrapper.instance().postToSuccess(44, false);
+ expect(postSuccessFn).toHaveBeenCalledWith(1, { id: 44 });
+ expect(wrapper.state('successTemplateIds')).toContain(44);
+ });
+ test('toggle error calls post', () => {
+ const spy = jest.spyOn(Notifications.prototype, 'postToError');
+ const wrapper = mount(
+
+
+
+
+
+ ).find('Notifications');
+ wrapper.instance().toggleNotification(1, true, 'error');
+ expect(spy).toHaveBeenCalledWith(1, true);
+ });
+ test('post error makes request and updates state properly', async () => {
+ const postErrorFn = jest.fn();
+ const wrapper = mount(
+
+
+
+
+
+ ).find('Notifications');
+ wrapper.setState({ errorTemplateIds: [44] });
+ await wrapper.instance().postToError(44, true);
+ expect(postErrorFn).toHaveBeenCalledWith(1, { id: 44, disassociate: true });
+ expect(wrapper.state('errorTemplateIds')).not.toContain(44);
+ await wrapper.instance().postToError(44, false);
+ expect(postErrorFn).toHaveBeenCalledWith(1, { id: 44 });
+ expect(wrapper.state('errorTemplateIds')).toContain(44);
+ });
+ test('fetchNotifications', async () => {
+ const mockQueryParams = {
+ page: 44,
+ page_size: 10,
+ order_by: 'name'
+ };
+ const getNotificationsFn = jest.fn().mockResolvedValue({
+ data: {
+ results: [
+ { id: 1 },
+ { id: 2 },
+ { id: 3 }
+ ]
+ }
+ });
+ const getSuccessFn = jest.fn().mockResolvedValue({
+ data: {
+ results: [
+ { id: 1 }
+ ]
+ }
+ });
+ const getErrorFn = jest.fn().mockResolvedValue({
+ data: {
+ results: [
+ { id: 2 }
+ ]
+ }
+ });
+ const wrapper = mount(
+
+
+
+
+
+ ).find('Notifications');
+ wrapper.instance().updateUrl = jest.fn();
+ await wrapper.instance().fetchNotifications(mockQueryParams);
+ expect(getNotificationsFn).toHaveBeenCalledWith(1, mockQueryParams);
+ expect(getSuccessFn).toHaveBeenCalledWith(1, {
+ id__in: '1,2,3'
+ });
+ expect(getErrorFn).toHaveBeenCalledWith(1, {
+ id__in: '1,2,3'
+ });
+ expect(wrapper.state('successTemplateIds')).toContain(1);
+ expect(wrapper.state('errorTemplateIds')).toContain(2);
+ });
+});
diff --git a/__tests__/components/NotificationListItem.test.jsx b/__tests__/components/NotificationListItem.test.jsx
new file mode 100644
index 0000000000..f162782338
--- /dev/null
+++ b/__tests__/components/NotificationListItem.test.jsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { MemoryRouter } from 'react-router-dom';
+import { I18nProvider } from '@lingui/react';
+import NotificationListItem from '../../src/components/NotificationsList/NotificationListItem';
+
+describe('', () => {
+ let wrapper;
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.unmount();
+ wrapper = null;
+ }
+ });
+
+ test('initially renders succesfully', () => {
+ wrapper = mount(
+
+
+
+
+
+ );
+ expect(wrapper.length).toBe(1);
+ });
+
+ test('handles success click when toggle is on', () => {
+ const toggleNotification = jest.fn();
+ wrapper = mount(
+
+
+
+
+
+ );
+ wrapper.find('Switch').first().find('input').simulate('change');
+ expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'success');
+ });
+
+ test('handles success click when toggle is off', () => {
+ const toggleNotification = jest.fn();
+ wrapper = mount(
+
+
+
+
+
+ );
+ wrapper.find('Switch').first().find('input').simulate('change');
+ expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'success');
+ });
+
+ test('handles error click when toggle is on', () => {
+ const toggleNotification = jest.fn();
+ wrapper = mount(
+
+
+
+
+
+ );
+ wrapper.find('Switch').at(1).find('input').simulate('change');
+ expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'error');
+ });
+
+ test('handles error click when toggle is off', () => {
+ const toggleNotification = jest.fn();
+ wrapper = mount(
+
+
+
+
+
+ );
+ wrapper.find('Switch').at(1).find('input').simulate('change');
+ expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'error');
+ });
+});
diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx
index aaaa7adde4..7262ae85a9 100644
--- a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx
+++ b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx
@@ -12,6 +12,7 @@ describe('', () => {
diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx
new file mode 100644
index 0000000000..9716eadf8a
--- /dev/null
+++ b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { MemoryRouter } from 'react-router-dom';
+import OrganizationNotifications from '../../../../../src/pages/Organizations/screens/Organization/OrganizationNotifications';
+
+describe('', () => {
+ test('initially renders succesfully', () => {
+ mount(
+
+
+
+ );
+ });
+ test('handles api requests', () => {
+ const getOrganizationNotifications = jest.fn();
+ const getOrganizationNotificationSuccess = jest.fn();
+ const getOrganizationNotificationError = jest.fn();
+ const createOrganizationNotificationSuccess = jest.fn();
+ const createOrganizationNotificationError = jest.fn();
+ const wrapper = mount(
+
+
+
+ ).find('OrganizationNotifications');
+ wrapper.instance().getOrgNotifications(1, { foo: 'bar' });
+ expect(getOrganizationNotifications).toHaveBeenCalledWith(1, { foo: 'bar' });
+ wrapper.instance().getOrgNotificationSuccess(1, { foo: 'bar' });
+ expect(getOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { foo: 'bar' });
+ wrapper.instance().getOrgNotificationError(1, { foo: 'bar' });
+ expect(getOrganizationNotificationError).toHaveBeenCalledWith(1, { foo: 'bar' });
+ wrapper.instance().createOrgNotificationSuccess(1, { id: 2 });
+ expect(createOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { id: 2 });
+ wrapper.instance().createOrgNotificationError(1, { id: 2 });
+ expect(createOrganizationNotificationError).toHaveBeenCalledWith(1, { id: 2 });
+ });
+});
diff --git a/src/api.js b/src/api.js
index ce915bd136..d0281a09a4 100644
--- a/src/api.js
+++ b/src/api.js
@@ -70,6 +70,36 @@ class APIClient {
return this.http.get(endpoint);
}
+ getOrganizationNotifications (id, params = {}) {
+ const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
+
+ return this.http.get(endpoint, { params });
+ }
+
+ getOrganizationNotificationSuccess (id, params = {}) {
+ const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`;
+
+ return this.http.get(endpoint, { params });
+ }
+
+ getOrganizationNotificationError (id, params = {}) {
+ const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`;
+
+ return this.http.get(endpoint, { params });
+ }
+
+ createOrganizationNotificationSuccess (id, data) {
+ const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`;
+
+ return this.http.post(endpoint, data);
+ }
+
+ createOrganizationNotificationError (id, data) {
+ const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`;
+
+ return this.http.post(endpoint, data);
+ }
+
getInstanceGroups () {
return this.http.get(API_INSTANCE_GROUPS);
}
diff --git a/src/app.scss b/src/app.scss
index 5d2f72d701..d9b34ab4ad 100644
--- a/src/app.scss
+++ b/src/app.scss
@@ -82,6 +82,10 @@
--pf-c-data-list__item--PaddingTop: 16px;
--pf-c-data-list__item--PaddingBottom: 16px;
+
+ .pf-c-badge:not(:last-child), .pf-c-switch:not(:last-child) {
+ margin-right: 18px;
+ }
}
.pf-c-data-list__item {
@@ -107,10 +111,6 @@
margin-right: 8px;
}
-.pf-c-data-list__cell span {
- margin-right: 18px;
-}
-
//
// about modal overrides
//
@@ -158,6 +158,16 @@
border-top: 1px solid #d7d7d7;
border-bottom: 1px solid #d7d7d7;
}
+.at-c-listCardBody {
+ --pf-c-card__footer--PaddingX: 0;
+ --pf-c-card__footer--PaddingY: 0;
+ --pf-c-card__body--PaddingX: 0;
+ --pf-c-card__body--PaddingY: 0;
+}
+.pf-c-data-list__item {
+ --pf-c-data-list__item--PaddingLeft: 20px;
+ --pf-c-data-list__item--PaddingRight: 20px;
+}
//
// pf modal overrides
//
diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx
index 0936e7bdc8..8ea46641a8 100644
--- a/src/components/DataListToolbar/DataListToolbar.jsx
+++ b/src/components/DataListToolbar/DataListToolbar.jsx
@@ -106,7 +106,9 @@ class DataListToolbar extends React.Component {
sortedColumnKey,
sortOrder,
addUrl,
- showExpandCollapse
+ showExpandCollapse,
+ showDelete,
+ showSelectAll
} = this.props;
const {
// isActionDropdownOpen,
@@ -149,19 +151,19 @@ class DataListToolbar extends React.Component {
-
-
-
-
-
-
+
+ { showSelectAll && (
+
+
+
+
+
+ )}
@@ -248,17 +250,19 @@ class DataListToolbar extends React.Component {
-
-
-
+
+
+ )}
{addUrl && (