mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 04:04:36 +08:00
adds remove team action
This commit is contained in:
parent
1b3b3dfea9
commit
705d07f60e
11 changed files with 215 additions and 8 deletions
|
@ -31,7 +31,6 @@ module ClientApi
|
|||
current_user: current_user,
|
||||
params: team_params)
|
||||
team_service.update_team!
|
||||
|
||||
success_response('/client_api/teams/update_details',
|
||||
team_service.single_team_details_data)
|
||||
rescue ClientApi::CustomTeamError => error
|
||||
|
|
|
@ -29,6 +29,19 @@ module ClientApi
|
|||
unsuccess_response(error.to_s)
|
||||
end
|
||||
|
||||
def remove_user
|
||||
ut_service = ClientApi::UserTeamService.new(
|
||||
user: current_user,
|
||||
team_id: params[:team],
|
||||
user_team_id: params[:user_team]
|
||||
)
|
||||
ut_service.destroy_user_team_and_assign_new_team_owner!
|
||||
success_response('/client_api/teams/team_users',
|
||||
ut_service.team_users_data)
|
||||
rescue ClientApi::CustomUserTeamError => error
|
||||
unsuccess_response(error.to_s)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def success_response(template, locals)
|
||||
|
|
|
@ -27,6 +27,7 @@ export const CONTACT_US_LINK =
|
|||
// user teams
|
||||
export const LEAVE_TEAM_PATH = "/client_api/users/leave_team";
|
||||
export const UPDATE_USER_TEAM_ROLE_PATH = "/client_api/users/update_role";
|
||||
export const REMOVE_USER_FROM_TEAM_PATH = "/client_api/users/remove_user";
|
||||
|
||||
// settings
|
||||
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
|
||||
|
|
|
@ -70,6 +70,15 @@ export default {
|
|||
warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.",
|
||||
leave_team: "Leave"
|
||||
},
|
||||
remove_user_modal: {
|
||||
title: "Remove user {user} from team {team}",
|
||||
subtitle: "Are you sure you wish to remove user {user} from team {team}?",
|
||||
warnings: "Removing user from team has following consequences:",
|
||||
warning_message_one: "user will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
|
||||
warning_message_two: "all projects in the team where user was the sole <b>Owner</b> will be reassigned onto you as a new owner;",
|
||||
warning_message_three: "all repository protocols in the team belonging to user will be reassigned onto you.",
|
||||
remove_user: "Remove user"
|
||||
},
|
||||
update_team_description_modal: {
|
||||
title: "Edit team description",
|
||||
label: "Description"
|
||||
|
|
|
@ -163,7 +163,7 @@ class SettingsTeamPageContainer extends Component {
|
|||
<TeamsMembers
|
||||
members={this.state.users}
|
||||
updateUsersCallback={this.updateUsersCallback}
|
||||
teamId={this.state.team.id}
|
||||
team={this.state.team}
|
||||
/>
|
||||
<UpdateTeamDescriptionModal
|
||||
showModal={this.state.showModal}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes, { bool, number, string, func } from "prop-types";
|
||||
import { Modal, Button, Alert, Glyphicon } from "react-bootstrap";
|
||||
import { FormattedMessage, FormattedHTMLMessage } from "react-intl";
|
||||
|
||||
import axios from "../../../../../app/axios";
|
||||
|
||||
import { REMOVE_USER_FROM_TEAM_PATH } from "../../../../../app/routes";
|
||||
|
||||
class RemoveUserModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onCloseModal = this.onCloseModal.bind(this);
|
||||
this.removeUser = this.removeUser.bind(this);
|
||||
}
|
||||
|
||||
onCloseModal() {
|
||||
this.props.hideModal();
|
||||
}
|
||||
|
||||
removeUser() {
|
||||
const { team_id, team_user_id } = this.props.userToRemove;
|
||||
axios({
|
||||
method: "DELETE",
|
||||
url: REMOVE_USER_FROM_TEAM_PATH,
|
||||
withCredentials: true,
|
||||
data: {
|
||||
team: team_id,
|
||||
user_team: team_user_id
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
this.props.updateUsersCallback(response.data.team_users);
|
||||
this.props.hideModal();
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { teamName, userName } = this.props.userToRemove;
|
||||
return (
|
||||
<Modal show={this.props.showModal} onHide={this.onCloseModal}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id="settings_page.remove_user_modal.title"
|
||||
values={{ user: userName, team: teamName }}
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="settings_page.remove_user_modal.subtitle"
|
||||
values={{ user: userName, team: teamName }}
|
||||
/>
|
||||
</p>
|
||||
<Alert bsStyle="danger">
|
||||
<Glyphicon glyph="exclamation-sign" />
|
||||
<FormattedMessage id="settings_page.remove_user_modal.warnings" />
|
||||
<ul>
|
||||
<li>
|
||||
<FormattedMessage id="settings_page.remove_user_modal.warning_message_one" />
|
||||
</li>
|
||||
<li>
|
||||
<FormattedHTMLMessage id="settings_page.remove_user_modal.warning_message_two" />
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage id="settings_page.remove_user_modal.warning_message_three" />
|
||||
</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.onCloseModal}>
|
||||
<FormattedMessage id="general.close" />
|
||||
</Button>
|
||||
<Button bsStyle="success" onClick={this.removeUser}>
|
||||
<FormattedMessage id="settings_page.remove_user_modal.remove_user" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveUserModal.propTypes = {
|
||||
showModal: bool.isRequired,
|
||||
hideModal: func.isRequired,
|
||||
userToRemove: PropTypes.shape({
|
||||
userName: string.isRequired,
|
||||
team_user_id: number.isRequired,
|
||||
teamName: string.isRequired,
|
||||
team_id: number.isRequired
|
||||
}).isRequired,
|
||||
updateUsersCallback: func.isRequired
|
||||
};
|
||||
|
||||
export default RemoveUserModal;
|
|
@ -10,13 +10,22 @@ import {
|
|||
import { FormattedMessage } from "react-intl";
|
||||
import axios from "../../../../../app/axios";
|
||||
|
||||
import RemoveUserModal from "./RemoveUserModal";
|
||||
import DataTable from "../../../../../shared/data_table";
|
||||
import { UPDATE_USER_TEAM_ROLE_PATH } from "../../../../../app/routes";
|
||||
|
||||
const initalUserToRemove = {
|
||||
userName: "",
|
||||
team_user_id: 0,
|
||||
teamName: "",
|
||||
team_id: 0
|
||||
};
|
||||
class TeamsMembers extends Component {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this.state = { showModal: false, userToRemove: initalUserToRemove };
|
||||
this.memberAction = this.memberAction.bind(this);
|
||||
this.hideModal = this.hideModal.bind(this);
|
||||
}
|
||||
|
||||
currentRole(memberRole, role) {
|
||||
|
@ -26,7 +35,7 @@ class TeamsMembers extends Component {
|
|||
updateRole(userTeamId, role) {
|
||||
axios
|
||||
.put(UPDATE_USER_TEAM_ROLE_PATH, {
|
||||
team: this.props.teamId,
|
||||
team: this.props.team.id,
|
||||
user_team: userTeamId,
|
||||
role
|
||||
})
|
||||
|
@ -36,7 +45,13 @@ class TeamsMembers extends Component {
|
|||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
removeUser(userTeamId) {}
|
||||
hideModal() {
|
||||
this.setState({ showModal: false, userToRemove: initalUserToRemove });
|
||||
}
|
||||
|
||||
userToRemove(userToRemove) {
|
||||
this.setState({ showModal: true, userToRemove });
|
||||
}
|
||||
|
||||
memberAction(data, row) {
|
||||
return (
|
||||
|
@ -83,7 +98,12 @@ class TeamsMembers extends Component {
|
|||
<MenuItem divider />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
this.removeUser(data.team_user_id);
|
||||
this.userToRemove({
|
||||
userName: row.name,
|
||||
team_user_id: data.team_user_id,
|
||||
teamName: this.props.team.name,
|
||||
team_id: this.props.team.id
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="settings_page.single_team.actions.remove_user" />
|
||||
|
@ -155,6 +175,12 @@ class TeamsMembers extends Component {
|
|||
</Button>
|
||||
|
||||
<DataTable data={this.props.members} columns={columns} />
|
||||
<RemoveUserModal
|
||||
showModal={this.state.showModal}
|
||||
hideModal={this.hideModal}
|
||||
updateUsersCallback={this.props.updateUsersCallback}
|
||||
userToRemove={this.state.userToRemove}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
@ -162,7 +188,10 @@ class TeamsMembers extends Component {
|
|||
|
||||
TeamsMembers.propTypes = {
|
||||
updateUsersCallback: func.isRequired,
|
||||
teamId: number.isRequired,
|
||||
team: PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired
|
||||
}).isRequired,
|
||||
members: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
|
|
|
@ -9,10 +9,10 @@ module ClientApi
|
|||
@user = parsed_args.fetch(:user)
|
||||
@user_team = UserTeam.find_by_id(parsed_args.fetch(:user_team_id).to_i)
|
||||
@role = args.fetch(:role) { false }
|
||||
raise ClientApi::CustomUserTeamError unless @user_team && @user && @team
|
||||
end
|
||||
|
||||
def destroy_user_team_and_assign_new_team_owner!
|
||||
binding.pry
|
||||
raise ClientApi::CustomUserTeamError unless user_cant_leave?
|
||||
new_owner = @team.user_teams
|
||||
.where(role: 2)
|
||||
|
@ -59,7 +59,7 @@ module ClientApi
|
|||
end
|
||||
|
||||
def user_cant_leave?
|
||||
@user.teams.includes @team &&
|
||||
@user.teams.includes(@team) &&
|
||||
@user_team.admin? &&
|
||||
@team.user_teams.where(role: 2).count <= 1
|
||||
end
|
||||
|
|
|
@ -29,6 +29,7 @@ Rails.application.routes.draw do
|
|||
get '/current_user_info', to: 'users#current_user_info'
|
||||
|
||||
namespace :users do
|
||||
delete '/remove_user', to: 'user_teams#remove_user'
|
||||
delete '/leave_team', to: 'user_teams#leave_team'
|
||||
put '/update_role', to: 'user_teams#update_role'
|
||||
end
|
||||
|
|
56
spec/services/client_api/user_team_service_spec.rb
Normal file
56
spec/services/client_api/user_team_service_spec.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ClientApi::UserTeamService do
|
||||
let(:team_one) { create :team }
|
||||
let(:user_one) { create :user, email: Faker::Internet.email }
|
||||
let(:user_team) { create :user_team, user: user_one, team: team_one }
|
||||
|
||||
it 'should raise ClientApi::CustomUserTeamError if user is not assigned' do
|
||||
expect {
|
||||
ClientApi::UserTeamService.new(
|
||||
team_id: team_one.id,
|
||||
user_team_id: user_team.id
|
||||
)
|
||||
}.to raise_error(ClientApi::CustomUserTeamError)
|
||||
end
|
||||
|
||||
it 'should raise ClientApi::CustomUserTeamError if team is not assigned' do
|
||||
expect {
|
||||
ClientApi::UserTeamService.new(user: user_one, user_team_id: user_team.id)
|
||||
}.to raise_error(ClientApi::CustomUserTeamError)
|
||||
end
|
||||
|
||||
it 'should raise ClientApi::CustomUserTeamError if ' \
|
||||
'user_team is not assigned' do
|
||||
expect {
|
||||
ClientApi::UserTeamService.new(user: user_one, team_id: team_one.id)
|
||||
}.to raise_error(ClientApi::CustomUserTeamError)
|
||||
end
|
||||
|
||||
describe '#destroy_user_team_and_assign_new_team_owner!' do
|
||||
let(:user_two) { create :user, email: Faker::Internet.email }
|
||||
|
||||
it 'should raise ClientApi::CustomUserTeamError if user ' \
|
||||
'can\'t leave the team' do
|
||||
ut_service = ClientApi::UserTeamService.new(
|
||||
team_id: team_one.id,
|
||||
user_team_id: user_team.id,
|
||||
user: user_one
|
||||
)
|
||||
expect {
|
||||
ut_service.destroy_user_team_and_assign_new_team_owner!
|
||||
}.to raise_error(ClientApi::CustomUserTeamError)
|
||||
end
|
||||
|
||||
it 'should destroy the user_team relation' do
|
||||
new_user_team = create :user_team, team: team_one, user: user_two
|
||||
ut_service = ClientApi::UserTeamService.new(
|
||||
team_id: team_one.id,
|
||||
user_team_id: new_user_team.id,
|
||||
user: user_two
|
||||
)
|
||||
ut_service.destroy_user_team_and_assign_new_team_owner!
|
||||
expect(new_user_team).to_not exist
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue