aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-03-28 12:13:23 +0200
committerGrégoire Aubert <gregaubert@users.noreply.github.com>2017-03-31 10:29:27 +0200
commit76ad0222a481f307474b2990f264780f42cf3627 (patch)
treee23bfc78a9f43c2afd9d35baf46f54e0118fde0e /server/sonar-web/src/main/js/apps
parent4b2cf358d38ad0e1931246530d3ff6ada7467079 (diff)
downloadsonarqube-76ad0222a481f307474b2990f264780f42cf3627.tar.gz
sonarqube-76ad0222a481f307474b2990f264780f42cf3627.zip
SONAR-8993 Remove member from organization
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/actions.js8
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersList.js2
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js28
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js8
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js6
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap36
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js106
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js44
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap69
10 files changed, 304 insertions, 9 deletions
diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.js b/server/sonar-web/src/main/js/apps/organizations/actions.js
index f21d6bb775f..864ba1f448d 100644
--- a/server/sonar-web/src/main/js/apps/organizations/actions.js
+++ b/server/sonar-web/src/main/js/apps/organizations/actions.js
@@ -140,3 +140,11 @@ export const addOrganizationMember = (key: string, member: Member) => (dispatch:
dispatch(membersActions.removeMember(key, member));
});
};
+
+export const removeOrganizationMember = (key: string, member: Member) => (dispatch: Function) => {
+ dispatch(membersActions.removeMember(key, member));
+ return api.removeMember({ login: member.login, organization: key }).catch((error: Object) => {
+ onFail(dispatch)(error);
+ dispatch(membersActions.addMember(key, member));
+ });
+};
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js
index a5744dfd253..55997c69ba5 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js
@@ -26,6 +26,7 @@ import type { Organization } from '../../../store/organizations/duck';
type Props = {
members: Array<Member>,
organization?: Organization,
+ removeMember: (Member) => void,
};
export default class MembersList extends React.PureComponent {
@@ -40,6 +41,7 @@ export default class MembersList extends React.PureComponent {
key={member.login}
member={member}
organization={this.props.organization}
+ removeMember={this.props.removeMember}
/>
))}
</tbody>
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
index 496d950a8e1..0925526dfda 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
@@ -22,12 +22,14 @@ import React from 'react';
import Avatar from '../../../components/ui/Avatar';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
+import RemoveMemberForm from './forms/RemoveMemberForm';
import type { Member } from '../../../store/organizationsMembers/actions';
import type { Organization } from '../../../store/organizations/duck';
type Props = {
member: Member,
- organization: Organization
+ organization: Organization,
+ removeMember: (Member) => void,
};
const AVATAR_SIZE: number = 36;
@@ -40,14 +42,32 @@ export default class MembersListItem extends React.PureComponent {
return (
<tr>
<td className="thin nowrap">
- <Avatar hash={member.avatar} size={AVATAR_SIZE} />
+ <Avatar hash={member.avatar} email={member.email} size={AVATAR_SIZE} />
</td>
<td className="nowrap text-middle"><strong>{member.name}</strong></td>
{organization.canAdmin &&
<td className="text-right text-middle">
{translate('organization.members.x_group(s)', formatMeasure(member.groupCount, 'INT'))}
- </td>
- }
+ </td>}
+ {organization.canAdmin &&
+ <td className="nowrap text-middle text-right">
+ <div className="dropdown">
+ <button className="dropdown-toggle little-spacer-right" data-toggle="dropdown">
+ <i className="icon-edit" />
+ {' '}
+ <i className="icon-dropdown" />
+ </button>
+ <ul className="dropdown-menu dropdown-menu-right">
+ <li>
+ <RemoveMemberForm
+ organization={this.props.organization}
+ removeMember={this.props.removeMember}
+ member={this.props.member}
+ />
+ </li>
+ </ul>
+ </div>
+ </td>}
</tr>
);
}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
index cf87c5cc85e..b91fe00207d 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
@@ -34,7 +34,8 @@ type Props = {
organization: Organization,
fetchOrganizationMembers: (organizationKey: string, query?: string) => void,
fetchMoreOrganizationMembers: (organizationKey: string, query?: string) => void,
- addOrganizationMember: (organizationKey: string, login: Member) => void
+ addOrganizationMember: (organizationKey: string, login: Member) => void,
+ removeOrganizationMember: (organizationKey: string, login: Member) => void
};
export default class OrganizationMembers extends React.PureComponent {
@@ -59,6 +60,10 @@ export default class OrganizationMembers extends React.PureComponent {
this.props.addOrganizationMember(this.props.organization.key, member);
};
+ removeMember = (member: Member) => {
+ this.props.removeOrganizationMember(this.props.organization.key, member);
+ };
+
render() {
const { organization, status, members } = this.props;
return (
@@ -75,6 +80,7 @@ export default class OrganizationMembers extends React.PureComponent {
<MembersList
members={members}
organization={organization}
+ removeMember={this.removeMember}
/>
{status.total != null &&
<ListFooter
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js
index abea8ce8552..67b07e138df 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js
@@ -28,7 +28,8 @@ import {
import {
fetchOrganizationMembers,
fetchMoreOrganizationMembers,
- addOrganizationMember
+ addOrganizationMember,
+ removeOrganizationMember
} from '../actions';
const mapStateToProps = (state, ownProps) => {
@@ -45,5 +46,6 @@ const mapStateToProps = (state, ownProps) => {
export default connect(mapStateToProps, {
fetchOrganizationMembers,
fetchMoreOrganizationMembers,
- addOrganizationMember
+ addOrganizationMember,
+ removeOrganizationMember
})(OrganizationMembers);
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap
index debea9c01a0..9832ec66cc2 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap
@@ -33,5 +33,41 @@ exports[`test should render actions and groups for admin 1`] = `
className="text-right text-middle">
organization.members.x_group(s).3
</td>
+ <td
+ className="nowrap text-middle text-right">
+ <div
+ className="dropdown">
+ <button
+ className="dropdown-toggle little-spacer-right"
+ data-toggle="dropdown">
+ <i
+ className="icon-edit" />
+
+ <i
+ className="icon-dropdown" />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right">
+ <li>
+ <RemoveMemberForm
+ member={
+ Object {
+ "avatar": "",
+ "groupCount": 3,
+ "login": "admin",
+ "name": "Admin Istrator",
+ }
+ }
+ organization={
+ Object {
+ "canAdmin": true,
+ "key": "foo",
+ "name": "Foo",
+ }
+ } />
+ </li>
+ </ul>
+ </div>
+ </td>
</tr>
`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap
index 87ab6b15a84..e5fd4a477e0 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap
@@ -27,7 +27,8 @@ exports[`test should not render actions for non admin 1`] = `
"key": "foo",
"name": "Foo",
}
- } />
+ }
+ removeMember={[Function]} />
<ListFooter
count={2}
loadMore={[Function]}
@@ -76,7 +77,8 @@ exports[`test should render actions for admin 1`] = `
"key": "foo",
"name": "Foo",
}
- } />
+ }
+ removeMember={[Function]} />
<ListFooter
count={2}
loadMore={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
new file mode 100644
index 00000000000..6d4c9e59adf
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
@@ -0,0 +1,106 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import Modal from 'react-modal';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import type { Member } from '../../../../store/organizationsMembers/actions';
+import type { Organization } from '../../../../store/organizations/duck';
+
+type Props = {
+ member: Member,
+ organization: Organization,
+ removeMember: (member: Member) => void
+};
+
+type State = {
+ open: boolean
+};
+
+export default class RemoveMemberForm extends React.PureComponent {
+ props: Props;
+
+ state: State = {
+ open: false
+ };
+
+ openForm = () => {
+ this.setState({ open: true });
+ };
+
+ closeForm = () => {
+ this.setState({ open: false });
+ };
+
+ handleSubmit = (e: Object) => {
+ e.preventDefault();
+ this.props.removeMember(this.props.member);
+ this.closeForm();
+ };
+
+ renderModal() {
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel="modal form"
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.closeForm}
+ >
+ <header className="modal-head">
+ <h2>{translate('users.remove')}</h2>
+ </header>
+ <form onSubmit={this.handleSubmit}>
+ <div className="modal-body">
+ {translateWithParameters(
+ 'organization.members.remove_x',
+ this.props.member.name,
+ this.props.organization.name
+ )}
+ <ul className="list-styled">
+ <li>{translate('organization.members.browse_projects')}</li>
+ <li>{translate('projects_role.codeviewer')}</li>
+ <li>{translate('projects_role.issueadmin')}</li>
+ </ul>
+ </div>
+ <footer className="modal-foot">
+ <div>
+ <button type="submit" className="button-red" autoFocus={true}>
+ {translate('remove')}
+ </button>
+ <button type="reset" className="button-link" onClick={this.closeForm}>
+ {translate('cancel')}
+ </button>
+ </div>
+ </footer>
+ </form>
+ </Modal>
+ );
+ }
+
+ render() {
+ return (
+ <a onClick={this.openForm} href="#">
+ {translate('organization.members.remove')}
+ {this.state.open && this.renderModal()}
+ </a>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
new file mode 100644
index 00000000000..03df0ec7df4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import { click } from '../../../../../helpers/testUtils';
+import RemoveMemberForm from '../RemoveMemberForm';
+
+const member = {};
+const removeMember = jest.fn();
+const organization = { name: 'MyOrg' };
+
+it('should render and open the modal', () => {
+ const wrapper = shallow(
+ <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} />
+ );
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({ open: true });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly handle user interactions', () => {
+ const wrapper = mount(
+ <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} />
+ );
+ click(wrapper.find('a'));
+ expect(wrapper.state('open')).toBeTruthy();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap
new file mode 100644
index 00000000000..da6b474108b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap
@@ -0,0 +1,69 @@
+exports[`test should render and open the modal 1`] = `
+<a
+ href="#"
+ onClick={[Function]}>
+ organization.members.remove
+</a>
+`;
+
+exports[`test should render and open the modal 2`] = `
+<a
+ href="#"
+ onClick={[Function]}>
+ organization.members.remove
+ <Modal
+ ariaHideApp={true}
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}>
+ <header
+ className="modal-head">
+ <h2>
+ users.remove
+ </h2>
+ </header>
+ <form
+ onSubmit={[Function]}>
+ <div
+ className="modal-body">
+ organization.members.remove_x..MyOrg
+ <ul
+ className="list-styled">
+ <li>
+ organization.members.browse_projects
+ </li>
+ <li>
+ projects_role.codeviewer
+ </li>
+ <li>
+ projects_role.issueadmin
+ </li>
+ </ul>
+ </div>
+ <footer
+ className="modal-foot">
+ <div>
+ <button
+ autoFocus={true}
+ className="button-red"
+ type="submit">
+ remove
+ </button>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ type="reset">
+ cancel
+ </button>
+ </div>
+ </footer>
+ </form>
+ </Modal>
+</a>
+`;