diff --git a/src/users/UserSummary.jsx b/src/users/UserSummary.jsx
index 1503778e4..6849d3e2d 100644
--- a/src/users/UserSummary.jsx
+++ b/src/users/UserSummary.jsx
@@ -1,41 +1,22 @@
-import React, { useState } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { Modal, Button, Input } from '@edx/paragon';
-import { postTogglePasswordStatus, postResetPassword } from './data/api';
import Table from '../Table';
import { formatDate } from '../utils';
import { getAccountActivationUrl } from './data/urls';
import IdentityVerificationStatus from './IdentityVerificationStatus';
import OnboardingStatus from './OnboardingStatus';
import SingleSignOnRecords from './SingleSignOnRecords';
+import TogglePasswordStatus from './account-actions/TogglePasswordStatus';
+import ResetPassword from './account-actions/ResetPassword';
+import PasswordHistory from './account-actions/PasswordHistory';
export default function UserSummary({
userData,
changeHandler,
}) {
- const [disableUserModalIsOpen, setDisableUserModalIsOpen] = useState(false);
- const [disableHistoryModalIsOpen, setDisableHistoryModalIsOpen] = useState(false);
- const [resetPasswordModalIsOpen, setResetPasswordModalIsOpen] = useState(false);
- const [comment, setComment] = useState('');
- const [userPasswordHistoryData, setUserPasswordHistoryData] = useState([]);
const userToggleVisible = true;
// TO-DO: Only expose "Disable/Enable User" for specific roles
- const PASSWORD_STATUS = {
- USABLE: 'Usable',
- UNUSABLE: 'Unusable',
- };
-
- const togglePasswordStatus = () => {
- postTogglePasswordStatus(userData.username, comment);
- changeHandler();
- };
-
- const resetPassword = () => {
- postResetPassword(userData.email);
- changeHandler();
- };
-
const userAccountData = [
{
dataName: 'Full Name',
@@ -86,36 +67,6 @@ export default function UserSummary({
},
];
- const userPasswordHistoryColumns = [
- {
- label: 'Date',
- key: 'created',
- },
- {
- label: 'Comment',
- key: 'comment',
- },
- {
- label: 'Action',
- key: 'disabled',
- },
- {
- label: 'By',
- key: 'createdBy',
- },
- ];
-
- const openHistoryModel = () => {
- const tableData = userData.passwordStatus.passwordToggleHistory.map(result => ({
- created: formatDate(result.created),
- comment: result.comment,
- disabled: result.disabled ? 'Disabled' : 'Enabled',
- createdBy: result.createdBy,
- }));
- setUserPasswordHistoryData(tableData);
- setDisableHistoryModalIsOpen(true);
- };
-
if (!userData.isActive) {
let dataValue;
if (userData.activationKey !== null) {
@@ -144,29 +95,19 @@ export default function UserSummary({
columns={columns}
/>
{userToggleVisible && (
-
-
-
- {userData.passwordStatus.passwordToggleHistory.length > 0 && (
-
- )}
+
)}
@@ -178,66 +119,6 @@ export default function UserSummary({
- setDisableHistoryModalIsOpen(false)}
- title="Enable/Disable History"
- id="password-history"
- body={(
-
- )}
- />
-
- Confirm
- ,
- ]}
- onClose={() => setDisableUserModalIsOpen(false)}
- title={`${userData.passwordStatus.status === PASSWORD_STATUS.USABLE ? 'Disable user confirmation' : 'Enable user confirmation'}`}
- body={(
-
-
- setComment(event.target.value)}
- />
-
- )}
- />
-
- Confirm
- ,
- ]}
- onClose={() => setResetPasswordModalIsOpen(false)}
- title="Reset Password"
- body={(
-
- { /* eslint-disable-next-line jsx-a11y/label-has-associated-control */ }
-
-
- )}
- />
);
diff --git a/src/users/UserSummary.test.jsx b/src/users/UserSummary.test.jsx
index b68ba492a..52ac6d023 100644
--- a/src/users/UserSummary.test.jsx
+++ b/src/users/UserSummary.test.jsx
@@ -96,142 +96,4 @@ describe('User Summary Component Tests', () => {
expect(rowValue.text()).toEqual('N/A');
});
});
-
- describe('Disable User Button', () => {
- it('Disable User button for active user', () => {
- const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
- expect(passwordActionButton.text()).toEqual('Disable User');
- expect(passwordActionButton.disabled).toBeFalsy();
- });
-
- it('Disable User Modal', () => {
- const mockApiCall = jest.spyOn(api, 'postTogglePasswordStatus').mockImplementation(() => {});
- const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
- let disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
-
- expect(disableDialogModal.prop('open')).toEqual(false);
- expect(passwordActionButton.text()).toEqual('Disable User');
- expect(passwordActionButton.disabled).toBeFalsy();
-
- passwordActionButton.simulate('click');
- disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
-
- expect(disableDialogModal.prop('open')).toEqual(true);
- expect(disableDialogModal.prop('title')).toEqual('Disable user confirmation');
- disableDialogModal.find('input[name="comment"]').simulate('change', { target: { value: 'Disable Test User' } });
- disableDialogModal.find('button.btn-danger').hostNodes().simulate('click');
-
- expect(UserSummaryData.changeHandler).toHaveBeenCalled();
- disableDialogModal.find('button.btn-link').simulate('click');
- disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
- expect(disableDialogModal.prop('open')).toEqual(false);
- mockApiCall.mockRestore();
- });
- });
-
- describe('Enable User Button', () => {
- beforeEach(() => {
- const passwordStatusData = { ...UserSummaryData.userData.passwordStatus, status: 'Unusable' };
- const userData = { ...UserSummaryData.userData, passwordStatus: passwordStatusData };
- mountUserSummaryWrapper({ ...UserSummaryData, userData });
- });
-
- it('Enable User button for disabled user', () => {
- const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
- expect(passwordActionButton.text()).toEqual('Enable User');
- expect(passwordActionButton.disabled).toBeFalsy();
- });
-
- it('Enable User Modal', () => {
- const mockApiCall = jest.spyOn(api, 'postTogglePasswordStatus').mockImplementation(() => {});
- const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
- let enableUserModal = wrapper.find('Modal#user-account-status-toggle');
-
- expect(enableUserModal.prop('open')).toEqual(false);
- expect(passwordActionButton.text()).toEqual('Enable User');
- expect(passwordActionButton.disabled).toBeFalsy();
-
- passwordActionButton.simulate('click');
- enableUserModal = wrapper.find('Modal#user-account-status-toggle');
-
- expect(enableUserModal.prop('open')).toEqual(true);
- expect(enableUserModal.prop('title')).toEqual('Enable user confirmation');
- enableUserModal.find('input[name="comment"]').simulate('change', { target: { value: 'Enable Test User' } });
- enableUserModal.find('button.btn-danger').hostNodes().simulate('click');
-
- expect(UserSummaryData.changeHandler).toHaveBeenCalled();
- mockApiCall.mockRestore();
- });
- });
-
- describe('Reset Password Button', () => {
- it('Reset Password button for a User', () => {
- const passwordResetButton = wrapper.find('#reset-password').hostNodes();
- expect(passwordResetButton.text()).toEqual('Reset Password');
- });
-
- it('Reset Password Modal', () => {
- const mockApiCall = jest.spyOn(api, 'postResetPassword').mockImplementation(() => {});
- const passwordResetButton = wrapper.find('#reset-password').hostNodes();
- let resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
-
- expect(resetPasswordModal.prop('open')).toEqual(false);
- expect(passwordResetButton.text()).toEqual('Reset Password');
-
- passwordResetButton.simulate('click');
- resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
-
- expect(resetPasswordModal.prop('open')).toEqual(true);
- expect(resetPasswordModal.prop('title')).toEqual('Reset Password');
- const confirmLabel = resetPasswordModal.find('label');
- expect(confirmLabel.text()).toContain('Do you wish to proceed?');
- resetPasswordModal.find('button.btn-danger').hostNodes().simulate('click');
-
- expect(UserSummaryData.changeHandler).toHaveBeenCalled();
- resetPasswordModal.find('button.btn-link').simulate('click');
- resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
- expect(resetPasswordModal.prop('open')).toEqual(false);
- mockApiCall.mockRestore();
- });
- });
-
- describe('Password Toggle History', () => {
- beforeEach(() => {
- const passwordHistory = [
- {
- created: Date().toLocaleString(),
- comment: 'Test Disabled',
- disabled: false,
- createdBy: 'staff',
- },
- {
- created: Date().toLocaleString(),
- comment: 'Test Enable',
- disabled: true,
- createdBy: 'staff',
- },
- ];
- const passwordStatusData = { ...UserSummaryData.userData.passwordStatus, passwordToggleHistory: passwordHistory };
- const userData = { ...UserSummaryData.userData, passwordStatus: passwordStatusData };
- mountUserSummaryWrapper({ ...UserSummaryData, userData });
- });
- it('Password History Modal', () => {
- const passwordHistoryButton = wrapper.find('button#toggle-password-history');
- let historyModal = wrapper.find('Modal#password-history');
-
- expect(historyModal.prop('open')).toEqual(false);
- expect(passwordHistoryButton.text()).toEqual('Show History');
- expect(passwordHistoryButton.disabled).toBeFalsy();
-
- passwordHistoryButton.simulate('click');
- historyModal = wrapper.find('Modal#password-history');
-
- expect(historyModal.prop('open')).toEqual(true);
- expect(historyModal.find('table tbody tr')).toHaveLength(2);
-
- historyModal.find('button.btn-link').simulate('click');
- historyModal = wrapper.find('Modal#password-history');
- expect(historyModal.prop('open')).toEqual(false);
- });
- });
});
diff --git a/src/users/account-actions/PasswordHistory.jsx b/src/users/account-actions/PasswordHistory.jsx
new file mode 100644
index 000000000..804a27aa5
--- /dev/null
+++ b/src/users/account-actions/PasswordHistory.jsx
@@ -0,0 +1,73 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Modal, Button, Table } from '@edx/paragon';
+import { formatDate } from '../../utils';
+
+export default function PasswordHistory({
+ passwordStatus,
+}) {
+ const [passwordHistoryData, setPasswordHistoryData] = useState([]);
+ const [passwordHistoryModalIsOpen, setPasswordHistoryModalIsOpen] = useState(false);
+
+ const openHistoryModal = () => {
+ const tableData = passwordStatus.passwordToggleHistory.map(result => ({
+ created: formatDate(result.created),
+ comment: result.comment,
+ disabled: result.disabled ? 'Disabled' : 'Enabled',
+ createdBy: result.createdBy,
+ }));
+ setPasswordHistoryData(tableData);
+ setPasswordHistoryModalIsOpen(true);
+ };
+
+ const userPasswordHistoryColumns = [
+ {
+ label: 'Date',
+ key: 'created',
+ },
+ {
+ label: 'Comment',
+ key: 'comment',
+ },
+ {
+ label: 'Action',
+ key: 'disabled',
+ },
+ {
+ label: 'By',
+ key: 'createdBy',
+ },
+ ];
+
+ return (
+
+
+ {passwordStatus.passwordToggleHistory.length > 0 && (
+
+ )}
+
+
setPasswordHistoryModalIsOpen(false)}
+ title="Enable/Disable History"
+ id="password-history"
+ body={(
+
+ )}
+ />
+
+ );
+}
+
+PasswordHistory.propTypes = {
+ passwordStatus: PropTypes.string.isRequired,
+};
diff --git a/src/users/account-actions/PasswordHistory.test.jsx b/src/users/account-actions/PasswordHistory.test.jsx
new file mode 100644
index 000000000..f94072069
--- /dev/null
+++ b/src/users/account-actions/PasswordHistory.test.jsx
@@ -0,0 +1,39 @@
+import { mount } from 'enzyme';
+import React from 'react';
+
+import PasswordHistory from './PasswordHistory';
+import UserSummaryData from '../data/test/userSummary';
+
+describe('Password History Component Tests', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ const data = {
+ passwordStatus: UserSummaryData.userData.passwordStatus,
+ };
+ wrapper = mount();
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ it('Password History Modal', () => {
+ const passwordHistoryButton = wrapper.find('button#toggle-password-history');
+ let historyModal = wrapper.find('Modal#password-history');
+
+ expect(historyModal.prop('open')).toEqual(false);
+ expect(passwordHistoryButton.text()).toEqual('Show History');
+ expect(passwordHistoryButton.disabled).toBeFalsy();
+
+ passwordHistoryButton.simulate('click');
+ historyModal = wrapper.find('Modal#password-history');
+
+ expect(historyModal.prop('open')).toEqual(true);
+ expect(historyModal.find('table tbody tr')).toHaveLength(2);
+
+ historyModal.find('button.btn-link').simulate('click');
+ historyModal = wrapper.find('Modal#password-history');
+ expect(historyModal.prop('open')).toEqual(false);
+ });
+});
diff --git a/src/users/account-actions/ResetPassword.jsx b/src/users/account-actions/ResetPassword.jsx
new file mode 100644
index 000000000..00ca3bcf1
--- /dev/null
+++ b/src/users/account-actions/ResetPassword.jsx
@@ -0,0 +1,56 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Modal, Button } from '@edx/paragon';
+import { postResetPassword } from '../data/api';
+
+export default function ResetPassword({
+ email,
+ changeHandler,
+}) {
+ const [resetPasswordModalIsOpen, setResetPasswordModalIsOpen] = useState(false);
+
+ const resetPassword = () => {
+ postResetPassword(email);
+ changeHandler();
+ };
+
+ return (
+
+
+
+
+ Confirm
+ ,
+ ]}
+ onClose={() => setResetPasswordModalIsOpen(false)}
+ title="Reset Password"
+ body={(
+
+ { /* eslint-disable-next-line jsx-a11y/label-has-associated-control */ }
+
+
+ )}
+ />
+
+ );
+}
+
+ResetPassword.propTypes = {
+ email: PropTypes.string.isRequired,
+ changeHandler: PropTypes.func.isRequired,
+};
diff --git a/src/users/account-actions/ResetPassword.test.jsx b/src/users/account-actions/ResetPassword.test.jsx
new file mode 100644
index 000000000..cc5c2ad63
--- /dev/null
+++ b/src/users/account-actions/ResetPassword.test.jsx
@@ -0,0 +1,51 @@
+import { mount } from 'enzyme';
+import React from 'react';
+
+import * as api from '../data/api';
+import ResetPassword from './ResetPassword';
+import UserSummaryData from '../data/test/userSummary';
+
+describe('Reset Password Component Tests', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ const data = {
+ email: UserSummaryData.userData.email,
+ changeHandler: UserSummaryData.changeHandler,
+ };
+ wrapper = mount();
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ it('Reset Password button for a User', () => {
+ const passwordResetButton = wrapper.find('#reset-password').hostNodes();
+ expect(passwordResetButton.text()).toEqual('Reset Password');
+ });
+
+ it('Reset Password Modal', () => {
+ const mockApiCall = jest.spyOn(api, 'postResetPassword').mockImplementation(() => {});
+ const passwordResetButton = wrapper.find('#reset-password').hostNodes();
+ let resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
+
+ expect(resetPasswordModal.prop('open')).toEqual(false);
+ expect(passwordResetButton.text()).toEqual('Reset Password');
+
+ passwordResetButton.simulate('click');
+ resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
+
+ expect(resetPasswordModal.prop('open')).toEqual(true);
+ expect(resetPasswordModal.prop('title')).toEqual('Reset Password');
+ const confirmLabel = resetPasswordModal.find('label');
+ expect(confirmLabel.text()).toContain('Do you wish to proceed?');
+ resetPasswordModal.find('button.btn-danger').hostNodes().simulate('click');
+
+ expect(UserSummaryData.changeHandler).toHaveBeenCalled();
+ resetPasswordModal.find('button.btn-link').simulate('click');
+ resetPasswordModal = wrapper.find('Modal#user-account-reset-password');
+ expect(resetPasswordModal.prop('open')).toEqual(false);
+ mockApiCall.mockRestore();
+ });
+});
diff --git a/src/users/account-actions/TogglePasswordStatus.jsx b/src/users/account-actions/TogglePasswordStatus.jsx
new file mode 100644
index 000000000..8f0571020
--- /dev/null
+++ b/src/users/account-actions/TogglePasswordStatus.jsx
@@ -0,0 +1,65 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Modal, Button, Input } from '@edx/paragon';
+import { postTogglePasswordStatus } from '../data/api';
+
+import {
+ DISABLE_USER, ENABLE_USER, DISABLE_USER_CONFIRMATION, ENABLE_USER_CONFIRMATION, PASSWORD_STATUS,
+} from './constants';
+
+export default function TogglePasswordStatus({
+ username,
+ passwordStatus,
+ changeHandler,
+}) {
+ const [disableUserModalIsOpen, setDisableUserModalIsOpen] = useState(false);
+ const [comment, setComment] = useState('');
+
+ const togglePasswordStatus = () => {
+ postTogglePasswordStatus(username, comment);
+ changeHandler();
+ };
+
+ return (
+
+
+
+ Confirm
+ ,
+ ]}
+ onClose={() => setDisableUserModalIsOpen(false)}
+ title={`${passwordStatus.status === PASSWORD_STATUS.USABLE ? DISABLE_USER_CONFIRMATION : ENABLE_USER_CONFIRMATION}`}
+ body={(
+
+
+ setComment(event.target.value)}
+ />
+
+ )}
+ />
+
+ );
+}
+
+TogglePasswordStatus.propTypes = {
+ username: PropTypes.string.isRequired,
+ passwordStatus: PropTypes.string.isRequired,
+ changeHandler: PropTypes.func.isRequired,
+};
diff --git a/src/users/account-actions/TogglePasswordStatus.test.jsx b/src/users/account-actions/TogglePasswordStatus.test.jsx
new file mode 100644
index 000000000..1a2700c26
--- /dev/null
+++ b/src/users/account-actions/TogglePasswordStatus.test.jsx
@@ -0,0 +1,94 @@
+import { mount } from 'enzyme';
+import React from 'react';
+
+import * as api from '../data/api';
+import TogglePasswordStatus from './TogglePasswordStatus';
+import UserSummaryData from '../data/test/userSummary';
+
+describe('Toggle Password Status Component Tests', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ const data = {
+ username: UserSummaryData.userData.username,
+ passwordStatus: UserSummaryData.userData.passwordStatus,
+ changeHandler: UserSummaryData.changeHandler,
+ };
+ wrapper = mount();
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ describe('Disable User Button', () => {
+ it('Disable User button for active user', () => {
+ const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
+ expect(passwordActionButton.text()).toEqual('Disable User');
+ expect(passwordActionButton.disabled).toBeFalsy();
+ });
+
+ it('Disable User Modal', () => {
+ const mockApiCall = jest.spyOn(api, 'postTogglePasswordStatus').mockImplementation(() => {});
+ const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
+ let disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
+
+ expect(disableDialogModal.prop('open')).toEqual(false);
+ expect(passwordActionButton.text()).toEqual('Disable User');
+ expect(passwordActionButton.disabled).toBeFalsy();
+
+ passwordActionButton.simulate('click');
+ disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
+
+ expect(disableDialogModal.prop('open')).toEqual(true);
+ expect(disableDialogModal.prop('title')).toEqual('Disable user confirmation');
+ disableDialogModal.find('input[name="comment"]').simulate('change', { target: { value: 'Disable Test User' } });
+ disableDialogModal.find('button.btn-danger').hostNodes().simulate('click');
+
+ expect(UserSummaryData.changeHandler).toHaveBeenCalled();
+ disableDialogModal.find('button.btn-link').simulate('click');
+ disableDialogModal = wrapper.find('Modal#user-account-status-toggle');
+ expect(disableDialogModal.prop('open')).toEqual(false);
+ mockApiCall.mockRestore();
+ });
+ });
+
+ describe('Enable User Button', () => {
+ beforeEach(() => {
+ const passwordStatusData = { ...UserSummaryData.userData.passwordStatus, status: 'Unusable' };
+ const data = {
+ username: UserSummaryData.userData.username,
+ passwordStatus: passwordStatusData,
+ changeHandler: UserSummaryData.changeHandler,
+ };
+ wrapper = mount();
+ });
+
+ it('Enable User button for disabled user', () => {
+ const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
+ expect(passwordActionButton.text()).toEqual('Enable User');
+ expect(passwordActionButton.disabled).toBeFalsy();
+ });
+
+ it('Enable User Modal', () => {
+ const mockApiCall = jest.spyOn(api, 'postTogglePasswordStatus').mockImplementation(() => {});
+ const passwordActionButton = wrapper.find('#toggle-password').hostNodes();
+ let enableUserModal = wrapper.find('Modal#user-account-status-toggle');
+
+ expect(enableUserModal.prop('open')).toEqual(false);
+ expect(passwordActionButton.text()).toEqual('Enable User');
+ expect(passwordActionButton.disabled).toBeFalsy();
+
+ passwordActionButton.simulate('click');
+ enableUserModal = wrapper.find('Modal#user-account-status-toggle');
+
+ expect(enableUserModal.prop('open')).toEqual(true);
+ expect(enableUserModal.prop('title')).toEqual('Enable user confirmation');
+ enableUserModal.find('input[name="comment"]').simulate('change', { target: { value: 'Enable Test User' } });
+ enableUserModal.find('button.btn-danger').hostNodes().simulate('click');
+
+ expect(UserSummaryData.changeHandler).toHaveBeenCalled();
+ mockApiCall.mockRestore();
+ });
+ });
+});
diff --git a/src/users/account-actions/constants.js b/src/users/account-actions/constants.js
new file mode 100644
index 000000000..bf702a2df
--- /dev/null
+++ b/src/users/account-actions/constants.js
@@ -0,0 +1,9 @@
+export const DISABLE_USER = 'Disable User';
+export const ENABLE_USER = 'Enable User';
+export const DISABLE_USER_CONFIRMATION = 'Disable user confirmation';
+export const ENABLE_USER_CONFIRMATION = 'Enable user confirmation';
+
+export const PASSWORD_STATUS = {
+ USABLE: 'Usable',
+ UNUSABLE: 'Unusable',
+};
diff --git a/src/users/data/test/userSummary.js b/src/users/data/test/userSummary.js
index 157e517cd..71c49476b 100644
--- a/src/users/data/test/userSummary.js
+++ b/src/users/data/test/userSummary.js
@@ -10,7 +10,19 @@ const UserSummaryData = {
dateJoined: null,
lastLogin: null,
passwordStatus: {
- passwordToggleHistory: [],
+ passwordToggleHistory: [
+ {
+ created: Date().toLocaleString(),
+ comment: 'Test Disabled',
+ disabled: false,
+ createdBy: 'staff',
+ },
+ {
+ created: Date().toLocaleString(),
+ comment: 'Test Enable',
+ disabled: true,
+ createdBy: 'staff',
+ }],
status: 'Usable',
},
},