Skip to content

Commit

Permalink
Restrict users shown in Storage edit dialog to those in project
Browse files Browse the repository at this point in the history
ED-95
  • Loading branch information
jshholland committed Jun 20, 2024
1 parent 60469eb commit 36f1066
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@ const formPropTypes = {
value: PropTypes.string,
}),
).isRequired,
loadUsersPromise: PropTypes.shape({
error: PropTypes.any,
fetching: PropTypes.bool.isRequired,
value: PropTypes.array.isRequired,
}).isRequired,
usersFetching: PropTypes.bool.isRequired,
};

const EditDataStoreForm = ({
handleSubmit, reset, pristine, // from redux form
userList, loadUsersPromise, onCancel, // user provided
userList, usersFetching, onCancel, // user provided
}) => (
<form onSubmit={handleSubmit}>
<Field
Expand All @@ -44,7 +40,7 @@ const EditDataStoreForm = ({
options={userList}
getOptionLabel={option => option.label}
getOptionSelected={(option, value) => option.value === value.value}
loading={loadUsersPromise.fetching}
loading={usersFetching}
/>
<UpdateFormControls onClearChanges={reset} onCancel={onCancel} pristine={pristine}/>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('EditDataStoreForm', () => {
pristine={true}
onCancel={jest.fn().mockName('onCancel')}
userList={[{ label: 'User 1', value: 'user1' }, { label: 'User 2', value: 'user2' }]}
loadUsersPromise={{ error: null, fetching: false, value: [] }}
usersFetching={false}
/>,
).container;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ export const getOnDetailsEditSubmit = (projectKey, stackName, typeName) => async
};

const EditDataStoreDialog = ({
onCancel, title, currentUsers, userList, loadUsersPromise, stack, projectKey, typeName,
onCancel, title, currentUsers, userList, usersFetching, stack, projectKey, typeName,
}) => (
<Dialog open={true} maxWidth="md" fullWidth>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<EditDataStoreForm
userList={sortUsersByLabel(userList.filter(user => user.verified))}
loadUsersPromise={loadUsersPromise}
usersFetching={usersFetching}
onSubmit={getOnDetailsEditSubmit(projectKey, stack.name, typeName)}
onCancel={onCancel}
initialValues={{
Expand Down Expand Up @@ -73,11 +73,7 @@ EditDataStoreDialog.propTypes = {
value: PropTypes.string,
}),
).isRequired,
loadUsersPromise: PropTypes.shape({
error: PropTypes.any,
fetching: PropTypes.bool.isRequired,
value: PropTypes.array.isRequired,
}).isRequired,
usersFetching: PropTypes.bool.isRequired,
stack: PropTypes.shape({
name: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ describe('Edit data store dialog', () => {
title: 'expectedTitle',
currentUsers: [{ label: 'expectedLabelOne', value: 'expectedValueOne', verified: true }],
userList: [{ label: 'expectedLabelTwo', value: 'expectedValueTwo', verified: true }, { label: 'expectedLabelThree', value: 'expectedValueThree', verified: false }],
loadUsersPromise: {
error: null,
fetching: false,
value: [],
},
usersFetching: false,
stack: { name: 'stackName', displayName: 'Stack Display Name', description: 'Stack description' },
projectKey: 'project99',
typeName: 'Data Store',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exports[`Edit data store dialog creates correct snapshot 1`] = `
>
<div>
EditDataStoreForm mock
{"userList":[{"label":"expectedLabelTwo","value":"expectedValueTwo","verified":true}],"loadUsersPromise":{"error":null,"fetching":false,"value":[]},"initialValues":{"displayName":"Stack Display Name","description":"Stack description","users":[{"label":"expectedLabelOne","value":"expectedValueOne","verified":true}]}}
{"userList":[{"label":"expectedLabelTwo","value":"expectedValueTwo","verified":true}],"usersFetching":false,"initialValues":{"displayName":"Stack Display Name","description":"Stack description","users":[{"label":"expectedLabelOne","value":"expectedValueOne","verified":true}]}}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,96 +1,36 @@
import React, { Component } from 'react';
import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { useDispatch, useSelector } from 'react-redux';
import { mapKeys, get, find } from 'lodash';
import dataStorageActions from '../../actions/dataStorageActions';
import userActions from '../../actions/userActions';
import notify from '../../components/common/notify';
import { useProjectUsers } from '../../hooks/projectUsersHooks';

class LoadUserManagementModalWrapper extends Component {
constructor(props, context) {
super(props, context);
this.addUser = this.addUser.bind(this);
this.removeUser = this.removeUser.bind(this);
}
const LoadUserManagementModalWrapper = ({ title, onCancel, Dialog, dataStoreId, projectKey, userKeysMapping, stack, typeName }) => {
const dispatch = useDispatch();

componentDidMount() {
// Added .catch to prevent unhandled promise error, when lacking permission to view content
this.props.actions.listUsers()
.catch(() => {});
}
const dataStorage = useSelector(state => find(state.dataStorage.value, ({ id }) => id === dataStoreId));
const projectUsers = useProjectUsers();

shouldComponentUpdate(nextProps) {
const isFetching = nextProps.dataStorage.fetching;
return !isFetching;
}
const remappedProjectUsers = useMemo(() => (userKeysMapping
? projectUsers.value.map(user => mapKeys(user, (_value, key) => get(userKeysMapping, key)))
: projectUsers.value),
[userKeysMapping, projectUsers]);

addUser({ value }) {
const { name } = this.getDataStore();
this.props.actions.addUserToDataStore(this.props.projectKey, { name, users: [value] })
.then(() => notify.success('User added to data store'))
.then(() => this.props.actions.loadDataStorage(this.props.projectKey));
}
useEffect(() => dispatch(userActions.listUsers()), [dispatch]);

removeUser({ value }) {
if (this.props.loginUserId !== value) {
const { name } = this.getDataStore();
this.props.actions.removeUserFromDataStore(this.props.projectKey, { name, users: [value] })
.then(() => notify.success('User removed from data store'))
.then(() => this.props.actions.loadDataStorage(this.props.projectKey));
} else {
notify.error('Unable to remove self');
}
}
const currentUsers = remappedProjectUsers.filter(user => dataStorage.users.includes(user.value));

remapKeys(users) {
if (this.props.userKeysMapping) {
return users.map(user => mapKeys(user, (value, key) => get(this.props.userKeysMapping, key)));
}

return users;
}

getDataStore() {
return find(this.props.dataStorage.value, ({ id }) => id === this.props.dataStoreId);
}

getCurrentUsers() {
const { users } = this.getDataStore();
const invertedMapping = Object.entries(this.props.userKeysMapping)
.map(([key, value]) => ({ [value]: key }))
.reduce((previous, current) => ({ ...previous, ...current }), {});

let currentUsers;

if (!this.props.users.fetching && this.props.users.value.length > 0) {
currentUsers = users.map(user => find(this.props.users.value, { [invertedMapping.value]: user }));
} else {
currentUsers = [];
}

return this.remapKeys(currentUsers);
}

render() {
const { Dialog } = this.props;

return (
<Dialog
onCancel={this.props.onCancel}
title={this.props.title}
currentUsers={this.getCurrentUsers()}
userList={this.remapKeys(this.props.users.value)}
loadUsersPromise={this.props.users}
addUser={this.addUser}
removeUser={this.removeUser}
stack={this.props.stack}
typeName={this.props.typeName}
projectKey={this.props.projectKey}
/>
);
}
}
return <Dialog
onCancel={onCancel}
title={title}
currentUsers={currentUsers}
userList={remappedProjectUsers}
usersFetching={projectUsers.fetching.inProgress}
stack={stack}
typeName={typeName}
projectKey={projectKey}
/>;
};

LoadUserManagementModalWrapper.propTypes = {
title: PropTypes.string.isRequired,
Expand All @@ -99,20 +39,11 @@ LoadUserManagementModalWrapper.propTypes = {
dataStoreId: PropTypes.string.isRequired,
projectKey: PropTypes.string.isRequired,
userKeysMapping: PropTypes.object.isRequired,
stack: PropTypes.shape({
displayName: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
}).isRequired,
typeName: PropTypes.string.isRequired,
};

function mapStateToProps({ authentication: { identity: { sub } }, dataStorage, users }) {
return { loginUserId: sub, dataStorage, users };
}

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
...dataStorageActions,
...userActions,
}, dispatch),
};
}

export { LoadUserManagementModalWrapper as PureLoadUserManagementModalWrapper }; // export for testing
export default connect(mapStateToProps, mapDispatchToProps)(LoadUserManagementModalWrapper);
export default LoadUserManagementModalWrapper;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import createStore from 'redux-mock-store';
import LoadUserManagementModalWrapper, { PureLoadUserManagementModalWrapper } from './LoadUserManagementModalWrapper';
import LoadUserManagementModalWrapper from './LoadUserManagementModalWrapper';
import dataStorageService from '../../api/dataStorageService';
import listUsersService from '../../api/listUsersService';

Expand Down Expand Up @@ -166,10 +166,6 @@ describe('LoadUserManagement Modal Wrapper', () => {
});

describe('is a container which', () => {
function shallowRenderPure(props) {
return shallow(<PureLoadUserManagementModalWrapper {...props} />);
}

const onCancelMock = jest.fn();

const generateProps = () => ({
Expand Down Expand Up @@ -213,7 +209,7 @@ describe('LoadUserManagement Modal Wrapper', () => {
const props = generateProps();

// Act
shallowRenderPure(props);
shallow(<LoadUserManagementModalWrapper {...props} />);

// Assert
expect(listUsersMock).toHaveBeenCalledTimes(1);
Expand All @@ -224,7 +220,7 @@ describe('LoadUserManagement Modal Wrapper', () => {
const props = generateProps();

// Act
const output = shallowRenderPure(props);
const output = shallow(<LoadUserManagementModalWrapper {...props} />);

// Assert
expect(output).toMatchSnapshot();
Expand Down

0 comments on commit 36f1066

Please sign in to comment.