Skip to content

Commit

Permalink
Merge 2eed32d into e56f9fb
Browse files Browse the repository at this point in the history
  • Loading branch information
kenware committed Oct 18, 2018
2 parents e56f9fb + 2eed32d commit be2d4b6
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 36 deletions.
54 changes: 38 additions & 16 deletions client/src/modules/Teams/components/InviteMembers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class InviteMember extends Component {
super(props);
this.state = {
searchInput: '',
accounts: { value: 'ghoullies-bot', label: 'ghoullies-bot', type: 'slack_channel' },
accounts: [this.multiSelectOptions()[0]],
ismultiSelectDisabled: false,
selectAllDisabled: true,
user: null,
Expand All @@ -33,6 +33,7 @@ export class InviteMember extends Component {
this.isDisabled = this.isDisabled.bind(this);
this.selectUser = this.selectUser.bind(this);
this.toggleSearch = this.toggleSearch.bind(this);
this.multiSelectOptions = this.multiSelectOptions.bind(this);
}

handleSubmit(event) {
Expand Down Expand Up @@ -77,13 +78,25 @@ export class InviteMember extends Component {
e.preventDefault();
const { addMember } = this.props;
const userId = this.state.user.id;
addMember(e, userId);
let accounts;
if (!this.state.ismultiSelectDisabled && this.state.selectAllDisabled) {
const { accounts } = this.state;
console.log(accounts);
// eslint-disable-next-line prefer-destructuring
accounts = this.state.accounts;
} else if (this.state.ismultiSelectDisabled && !this.state.selectAllDisabled) {
// const { addMember } = this.props;
accounts = this.props.accounts.map(account => {
const accountObj = {
type: account.type,
accountId: account.id,
name: account.name
};
return accountObj;
});
}
const data = {
accounts,
userId
};
addMember(e, data);
}

isDisabled(value) {
Expand All @@ -106,6 +119,16 @@ export class InviteMember extends Component {
}
}

multiSelectOptions() {
return this.props.accounts.map(account => ({
value: account.name,
label: account.name,
type: account.type,
accountId: account.id,
name: account.name
}));
}

render() {
const { searchInput } = this.state;
const { users } = this.props;
Expand All @@ -118,7 +141,7 @@ export class InviteMember extends Component {
{` ${props.data.label}`}
</label> :
<label>
{props.data.type === 'slack_channel' ?
{props.data.type === 'slack_channel' || props.data.type === 'slack_private_channel' ?
<label className="select-account-label">
<img src="/resources/images/slack.png" className="account-icon" alt="slack" />
{` ${props.data.label}`}
Expand All @@ -139,7 +162,7 @@ export class InviteMember extends Component {
{` ${props.data.label}`}
</label> :
<label>
{props.data.type === 'slack_channel' ?
{props.data.type === 'slack_channel' || props.data.type === 'slack_private_channel' ?
<label className="select-account-dropdown">
<img src="/resources/images/slack.png" className="account-icon" alt="slack" />
{` ${props.data.label}`}
Expand All @@ -152,12 +175,6 @@ export class InviteMember extends Component {
</components.Option>
);

const options = [
{ value: 'ah-tap', label: 'ah-tap', type: 'github_repo' },
{ value: 'ghoullies-bot', label: 'ghoullies-bot', type: 'slack_channel' },
{ value: 'Ghoullies-taps', label: 'Ghoullies-taps', type: 'pt_project' }
];

return (
<React.Fragment>
<div className="row">
Expand Down Expand Up @@ -222,9 +239,9 @@ export class InviteMember extends Component {
styles={{
multiValueRemove: (base) => ({ ...base, fontSize: '15px', color: '#385cd7' })
}}
defaultValue={[options[1]]}
defaultValue={[this.multiSelectOptions()[0]]}
isMulti
options={options}
options={this.multiSelectOptions()}
onChange={this.handleSelected}
isDisabled={this.state.ismultiSelectDisabled}
/>
Expand All @@ -250,9 +267,14 @@ const mapStateToProps = ({
users: {
data: { users }
}
},
accounts: {
accounts: {
data: { accounts }
}
}
}) => ({
users
users, accounts
});

export default connect(mapStateToProps, { searchUser, clearUser })(InviteMember);
6 changes: 4 additions & 2 deletions client/src/modules/Teams/container/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ class Teams extends Component {
this.props.renderSubContent(content);
}

addNewMember(event, userId) {
addNewMember(event, data) {
const {
match: {
params: { id }
}
} = this.props;
event.preventDefault();
this.props.addMember(id, userId);
data.teamId = id;
data.teamName = this.props.members.data.memberships[0].team.name;
this.props.addMember(data);
}

toggleSidenav(event) {
Expand Down
57 changes: 39 additions & 18 deletions client/src/redux/actions/teams/members.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import { stringify } from 'qs';

import { FETCH_MEMBERS, ADD_MEMBER } from '../types';
import api from '../../../utils/api';
import { success, isErrored, isLoading } from '../index';
import instance from '../../../config/axios';
import { successMessage, errorMessage } from '../../../toasts';
Expand All @@ -19,22 +20,42 @@ export const fetchMembers = id => dispatch => {
});
};

export const addMember = (teamId, userId) => dispatch => {
export const addMember = data => async dispatch => {
dispatch(isLoading(true));
return instance
.post(`teams/${teamId}/members/${userId}`, null)
.then(response => {
if (response.data.errors) {
errorMessage(response.data.errors[0]);
dispatch(isLoading(false));
return;
}
// dispatch(success(ADD_MEMBER, response.data));
successMessage('user successfully added to this team');
dispatch(isLoading(false));
})
.catch(error => {
dispatch(isErrored(ADD_MEMBER, error.response.data));
dispatch(isLoading(false));
});

try {
let request;
await api(`teams/${data.teamId}/members/${data.userId}`, 'post', { role: 'developer' });

const member = { type: 'team', name: data.teamName, invited: true };

if (data.accounts.length) {
request = data.accounts.map(async (account) => {
try {
const response = await api(`teams/${data.teamId}/accounts/${account.accountId}/members/${data.userId}`, 'post', null);
return {
type: account.type,
name: account.name,
invited: response.data.response.invitedUser.ok
};
} catch (error) {
return {
type: account.type,
name: account.name,
invited: false,
error
};
}
});
}

const allResponse = await Promise.all(request);
allResponse.push(member);
dispatch(success(ADD_MEMBER, allResponse));
successMessage('user successfully added to this team');
dispatch(isLoading(false));
} catch (error) {
dispatch(isLoading(false));
errorMessage(error);
}
};
73 changes: 73 additions & 0 deletions client/src/tests/__mocks__/__mockData__/memberMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export const addMemberRequest = {
userId: 2,
teamId: 3,
teamName: 'team',
accounts: [
{
type: 'github_repo',
name: 'github_repo',
accountId: 4
}
]
};

export const addMemberResponse =
{
type: 'github_repo',
name: 'github_repo',
invited: true,
response: {
data: {
response: {
invitedUser: {
ok: true
}
}
}
}
};

export const response =
{
type: 'github_repo',
name: 'github_repo',
invited: true,
data: {
response: {
invitedUser: {
ok: true
}
}
}
};

export const expectedActions = [
{
name: "EZE",
payload: true,
type: "[auth]: check if user is logged in"
},
{
payload: true,
type: "[ui]: show preloader"
},
{
payload: [
{
invited: true,
name: "github_repo",
type: "github_repo"
},
{
invited: true,
name: "team",
type: "team"
}
],
type: "[users]: add member to a team"
},
{
payload: false,
type: "[ui]: show preloader"
}
];
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ describe('<Test Component />', () => {
wrapper.instance().handleSubmit({ preventDefault: jest.fn() });
expect(handleSubmit).toHaveBeenCalled();
});

it('toggleSearch class methods should be called', () => {
const toggleSearch = jest.spyOn(InviteMember.prototype, 'toggleSearch');
const wrapper = shallow(<InviteMember {...props} />);
wrapper.instance().toggleSearch();
expect(toggleSearch).toHaveBeenCalled();
});

it('multiSelectOptions class methods should be called', () => {
const multiSelectOptions = jest.spyOn(InviteMember.prototype, 'multiSelectOptions');
const wrapper = shallow(<InviteMember {...props} />);
wrapper.instance().multiSelectOptions();
expect(multiSelectOptions).toHaveBeenCalled();
});
});
8 changes: 8 additions & 0 deletions client/src/tests/modules/teams/teamsMockData.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ export const props = {
clearUser: jest.fn(),
searchUser: jest.fn(),
addMember: jest.fn(),
accounts: [
{
type: 'github_repo',
name: 'github_repo',
accountId: 4
}
],
users: [{
role: 'admin',
email: 'eze.kevin@andela.com',
Expand All @@ -18,6 +25,7 @@ export const props = {
};

export const emptyProps = {
accounts: [],
searchUser: jest.fn(),
addMember: jest.fn()
};
35 changes: 35 additions & 0 deletions client/src/tests/redux/actions/teams/member.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import expect from 'expect';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

import { addMember } from '../../../../redux/actions/teams/members';

import { addMemberRequest, addMemberResponse, response, expectedActions } from '../../../__mocks__/__mockData__/memberMock';


const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('Add a new user to team accounts', () => {
beforeAll(() => {
localStorage.setItem('aTeamsToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImV6ZS5rZXZpbkBhbmRlbGEuY29tIiwiaWF0IjoxNTM5ODYxNDU2fQ._FOXQtpzUnwtbaP2-Fn6HOhFDZpsWmS3GPxmOVSAWdE');
});
beforeEach(() => {
mock.reset();
});

it('Adds a user to a team accounts', async () => {
const store = mockStore({});

await mock
.onPost('teams/3/members/2', { role: 'developer' })
.reply(201, addMemberResponse);

await mock
.onPost('teams/3/accounts/4/members/2')
.reply(201, response);

await store.dispatch(addMember(addMemberRequest));
expect(store.getActions()).toEqual(expectedActions);
});
});

0 comments on commit be2d4b6

Please sign in to comment.