PLT-2863 adding remove user from team (#3429)

* PLT-2863 adding remove user from team

* PLT-2863 adding the client side UI

* Fixing trailing space

* Fixing reported issues

* Adding documentatino

* Switching to final javascript driver
Этот коммит содержится в:
Corey Hulen
2016-07-06 13:40:59 -08:00
коммит произвёл GitHub
родитель 3eee51f74e
Коммит d5f243dad6
24 изменённых файлов: 572 добавлений и 19 удалений

Просмотреть файл

@@ -334,6 +334,43 @@ func TestCliJoinTeam(t *testing.T) {
} }
} }
func TestCliLeaveTeam(t *testing.T) {
if disableCliTests {
return
}
th := Setup().InitBasic()
cmd := exec.Command("bash", "-c", `go run ../mattermost.go -leave_team -team_name="`+th.BasicTeam.Name+`" -email="`+th.BasicUser.Email+`"`)
output, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(output))
t.Fatal(err)
}
profiles := th.BasicClient.Must(th.BasicClient.GetProfiles(th.BasicTeam.Id, "")).Data.(map[string]*model.User)
found := false
for _, user := range profiles {
if user.Email == th.BasicUser.Email {
found = true
}
}
if !found {
t.Fatal("profile still should be in team even if deleted")
}
if result := <-Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
teamMembers := result.Data.([]*model.TeamMember)
if len(teamMembers) > 0 {
t.Fatal("Shouldn't be in team")
}
}
}
func TestCliResetPassword(t *testing.T) { func TestCliResetPassword(t *testing.T) {
if disableCliTests { if disableCliTests {
return return

Просмотреть файл

@@ -17,7 +17,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
@@ -39,6 +38,7 @@ func InitTeam() {
BaseRoutes.NeedTeam.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") BaseRoutes.NeedTeam.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST")
BaseRoutes.NeedTeam.Handle("/add_user_to_team", ApiUserRequired(addUserToTeam)).Methods("POST") BaseRoutes.NeedTeam.Handle("/add_user_to_team", ApiUserRequired(addUserToTeam)).Methods("POST")
BaseRoutes.NeedTeam.Handle("/remove_user_from_team", ApiUserRequired(removeUserFromTeam)).Methods("POST")
// These should be moved to the global admin console // These should be moved to the global admin console
BaseRoutes.NeedTeam.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST") BaseRoutes.NeedTeam.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST")
@@ -266,12 +266,24 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
channelRole = model.CHANNEL_ROLE_ADMIN channelRole = model.CHANNEL_ROLE_ADMIN
} }
if tmr := <-Srv.Store.Team().SaveMember(tm); tmr.Err != nil { if etmr := <-Srv.Store.Team().GetMember(team.Id, user.Id); etmr.Err == nil {
if tmr.Err.Id == store.TEAM_MEMBER_EXISTS_ERROR { // Membership alredy exists. Check if deleted and and update, otherwise do nothing
rtm := etmr.Data.(model.TeamMember)
// Do nothing if already added
if rtm.DeleteAt == 0 {
return nil return nil
} }
if tmr := <-Srv.Store.Team().UpdateMember(tm); tmr.Err != nil {
return tmr.Err return tmr.Err
} }
} else {
// Membership appears to be missing. Lets try to add.
if tmr := <-Srv.Store.Team().SaveMember(tm); tmr.Err != nil {
return tmr.Err
}
}
if uua := <-Srv.Store.User().UpdateUpdateAt(user.Id); uua.Err != nil { if uua := <-Srv.Store.User().UpdateUpdateAt(user.Id); uua.Err != nil {
return uua.Err return uua.Err
@@ -291,6 +303,56 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
return nil return nil
} }
func LeaveTeam(team *model.Team, user *model.User) *model.AppError {
var teamMember model.TeamMember
if result := <-Srv.Store.Team().GetMember(team.Id, user.Id); result.Err != nil {
return model.NewLocAppError("RemoveUserFromTeam", "api.team.remove_user_from_team.missing.app_error", nil, result.Err.Error())
} else {
teamMember = result.Data.(model.TeamMember)
}
var channelMembers *model.ChannelList
if result := <-Srv.Store.Channel().GetChannels(team.Id, user.Id); result.Err != nil {
if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" {
channelMembers = &model.ChannelList{make([]*model.Channel, 0), make(map[string]*model.ChannelMember)}
} else {
return result.Err
}
} else {
channelMembers = result.Data.(*model.ChannelList)
}
for _, channel := range channelMembers.Channels {
if channel.Type != model.CHANNEL_DIRECT {
if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil {
return result.Err
}
}
}
teamMember.Roles = ""
teamMember.DeleteAt = model.GetMillis()
if result := <-Srv.Store.Team().UpdateMember(&teamMember); result.Err != nil {
return result.Err
}
if uua := <-Srv.Store.User().UpdateUpdateAt(user.Id); uua.Err != nil {
return uua.Err
}
RemoveAllSessionsForUserId(user.Id)
InvalidateCacheForUser(user.Id)
go Publish(model.NewMessage(team.Id, "", user.Id, model.ACTION_LEAVE_TEAM))
return nil
}
func isTeamCreationAllowed(c *Context, email string) bool { func isTeamCreationAllowed(c *Context, email string) bool {
email = strings.ToLower(email) email = strings.ToLower(email)
@@ -483,6 +545,51 @@ func addUserToTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(params))) w.Write([]byte(model.MapToJson(params)))
} }
func removeUserFromTeam(c *Context, w http.ResponseWriter, r *http.Request) {
params := model.MapFromJson(r.Body)
userId := params["user_id"]
if len(userId) != 26 {
c.SetInvalidParam("removeUserFromTeam", "user_id")
return
}
tchan := Srv.Store.Team().Get(c.TeamId)
uchan := Srv.Store.User().Get(userId)
var team *model.Team
if result := <-tchan; result.Err != nil {
c.Err = result.Err
return
} else {
team = result.Data.(*model.Team)
}
var user *model.User
if result := <-uchan; result.Err != nil {
c.Err = result.Err
return
} else {
user = result.Data.(*model.User)
}
if c.Session.UserId != user.Id {
if !c.IsTeamAdmin() {
c.Err = model.NewLocAppError("removeUserFromTeam", "api.team.update_team.permissions.app_error", nil, "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
}
}
err := LeaveTeam(team, user)
if err != nil {
c.Err = err
return
}
w.Write([]byte(model.MapToJson(params)))
}
func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) { func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) {
params := model.MapFromJson(r.Body) params := model.MapFromJson(r.Body)

Просмотреть файл

@@ -158,7 +158,7 @@ func TestAddUserToTeam(t *testing.T) {
} }
user2 := th.CreateUser(th.BasicClient) user2 := th.CreateUser(th.BasicClient)
if result, err := th.BasicClient.AddUserToTeam(user2.Id); err != nil { if result, err := th.BasicClient.AddUserToTeam("", user2.Id); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
rm := result.Data.(map[string]string) rm := result.Data.(map[string]string)
@@ -168,6 +168,39 @@ func TestAddUserToTeam(t *testing.T) {
} }
} }
func TestRemoveUserFromTeam(t *testing.T) {
th := Setup().InitSystemAdmin().InitBasic()
if _, err := th.BasicClient.RemoveUserFromTeam(th.SystemAdminTeam.Id, th.SystemAdminUser.Id); err == nil {
t.Fatal("should fail not enough permissions")
} else {
if err.Id != "api.context.permissions.app_error" {
t.Fatal("wrong error")
}
}
if _, err := th.BasicClient.RemoveUserFromTeam("", th.SystemAdminUser.Id); err == nil {
t.Fatal("should fail not enough permissions")
} else {
if err.Id != "api.team.update_team.permissions.app_error" {
t.Fatal("wrong error")
}
}
if _, err := th.BasicClient.RemoveUserFromTeam("", th.BasicUser.Id); err != nil {
t.Fatal("should have removed the user from the team")
}
th.BasicClient.Logout()
th.LoginSystemAdmin()
th.SystemAdminClient.Must(th.SystemAdminClient.AddUserToTeam(th.BasicTeam.Id, th.BasicUser.Id))
if _, err := th.SystemAdminClient.RemoveUserFromTeam(th.BasicTeam.Id, th.BasicUser.Id); err != nil {
t.Fatal("should have removed the user from the team")
}
}
func TestAddUserToTeamFromInvite(t *testing.T) { func TestAddUserToTeamFromInvite(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
th.BasicClient.Logout() th.BasicClient.Logout()

Просмотреть файл

@@ -1263,6 +1263,10 @@
"id": "api.slackimport.slack_import.zip.app_error", "id": "api.slackimport.slack_import.zip.app_error",
"translation": "Unable to open zip file" "translation": "Unable to open zip file"
}, },
{
"id": "api.team.remove_user_from_team.missing.app_error",
"translation": "The user does not appear to be part of this team."
},
{ {
"id": "api.team.create_team.email_disabled.app_error", "id": "api.team.create_team.email_disabled.app_error",
"translation": "Team sign-up with email is disabled." "translation": "Team sign-up with email is disabled."
@@ -4223,6 +4227,27 @@
"id": "utils.mail.test.configured.error", "id": "utils.mail.test.configured.error",
"translation": "SMTP server settings do not appear to be configured properly err=%v details=%v" "translation": "SMTP server settings do not appear to be configured properly err=%v details=%v"
}, },
{
"id": "utils.mail.test.configured.error",
"translation": "SMTP server settings do not appear to be configured properly err=%v details=%v"
},
{
"id": "leave_team_modal.title",
"translation": "Leave the team?"
},
{
"id": "leave_team_modal.desc",
"translation": "You will be removed from all public channels and private groups. If the team is private you will not be able to rejoin the team. Are you sure?"
},
{
"id": "leave_team_modal.no",
"translation": "No"
},
{
"id": "leave_team_modal.yes",
"translation": "Yes"
},
{ {
"id": "web.admin_console.title", "id": "web.admin_console.title",
"translation": "Admin Console" "translation": "Admin Console"

Просмотреть файл

@@ -50,6 +50,7 @@ var flagCmdLeaveChannel bool
var flagCmdListChannels bool var flagCmdListChannels bool
var flagCmdRestoreChannel bool var flagCmdRestoreChannel bool
var flagCmdJoinTeam bool var flagCmdJoinTeam bool
var flagCmdLeaveTeam bool
var flagCmdVersion bool var flagCmdVersion bool
var flagCmdRunWebClientTests bool var flagCmdRunWebClientTests bool
var flagCmdRunJavascriptClientTests bool var flagCmdRunJavascriptClientTests bool
@@ -286,6 +287,7 @@ func parseCmds() {
flag.BoolVar(&flagCmdListChannels, "list_channels", false, "") flag.BoolVar(&flagCmdListChannels, "list_channels", false, "")
flag.BoolVar(&flagCmdRestoreChannel, "restore_channel", false, "") flag.BoolVar(&flagCmdRestoreChannel, "restore_channel", false, "")
flag.BoolVar(&flagCmdJoinTeam, "join_team", false, "") flag.BoolVar(&flagCmdJoinTeam, "join_team", false, "")
flag.BoolVar(&flagCmdLeaveTeam, "leave_team", false, "")
flag.BoolVar(&flagCmdVersion, "version", false, "") flag.BoolVar(&flagCmdVersion, "version", false, "")
flag.BoolVar(&flagCmdRunWebClientTests, "run_web_client_tests", false, "") flag.BoolVar(&flagCmdRunWebClientTests, "run_web_client_tests", false, "")
flag.BoolVar(&flagCmdRunJavascriptClientTests, "run_javascript_client_tests", false, "") flag.BoolVar(&flagCmdRunJavascriptClientTests, "run_javascript_client_tests", false, "")
@@ -303,6 +305,7 @@ func parseCmds() {
flagRunCmds = (flagCmdCreateTeam || flagRunCmds = (flagCmdCreateTeam ||
flagCmdCreateUser || flagCmdCreateUser ||
flagCmdInviteUser || flagCmdInviteUser ||
flagCmdLeaveTeam ||
flagCmdAssignRole || flagCmdAssignRole ||
flagCmdJoinChannel || flagCmdJoinChannel ||
flagCmdLeaveChannel || flagCmdLeaveChannel ||
@@ -328,6 +331,7 @@ func runCmds() {
cmdCreateTeam() cmdCreateTeam()
cmdCreateUser() cmdCreateUser()
cmdInviteUser() cmdInviteUser()
cmdLeaveTeam()
cmdAssignRole() cmdAssignRole()
cmdJoinChannel() cmdJoinChannel()
cmdLeaveChannel() cmdLeaveChannel()
@@ -1187,6 +1191,47 @@ func cmdJoinTeam() {
} }
} }
func cmdLeaveTeam() {
if flagCmdLeaveTeam {
if len(flagTeamName) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
flag.Usage()
os.Exit(1)
}
if len(flagEmail) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
flag.Usage()
os.Exit(1)
}
var team *model.Team
if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
team = result.Data.(*model.Team)
}
var user *model.User
if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
user = result.Data.(*model.User)
}
err := api.LeaveTeam(team, user)
if err != nil {
l4g.Error("%v", err)
flushLogAndExit(1)
}
os.Exit(0)
}
}
func cmdResetPassword() { func cmdResetPassword() {
if flagCmdResetPassword { if flagCmdResetPassword {
if len(flagEmail) == 0 { if len(flagEmail) == 0 {
@@ -1517,7 +1562,12 @@ COMMANDS:
Example: Example:
platform -invite_user -team_name="name" -email="user@example.com" -site_url="https://mattermost.example.com" platform -invite_user -team_name="name" -email="user@example.com" -site_url="https://mattermost.example.com"
-join_team Joins a user to the team. It requires the -email and -leave_team Removes a user from a team. It requires the -team_name
and -email.
Example:
platform -remove_user_from_team -team_name="name" -email="user@example.com"
-join_team Joins a user to the team. It required the -email and
-team_name. You may need to logout of your current session -team_name. You may need to logout of your current session
for the new team to be applied. for the new team to be applied.
Example: Example:

Просмотреть файл

@@ -345,10 +345,18 @@ func (c *Client) FindTeamByName(name string) (*Result, *AppError) {
} }
} }
func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) { // Adds a user directly to the team without sending an invite.
// The teamId and userId are required. You must be a valid member of the team and/or
// have the correct role to add new users to the team. Returns a map of user_id=userId
// if successful, otherwise returns an AppError.
func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError) {
if len(teamId) == 0 {
teamId = c.GetTeamId()
}
data := make(map[string]string) data := make(map[string]string)
data["user_id"] = userId data["user_id"] = userId
if r, err := c.DoApiPost(c.GetTeamRoute()+"/add_user_to_team", MapToJson(data)); err != nil { if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/add_user_to_team", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
defer closeBody(r) defer closeBody(r)
@@ -371,6 +379,26 @@ func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Re
} }
} }
// Removes a user directly from the team.
// The teamId and userId are required. You must be a valid member of the team and/or
// have the correct role to remove a user from the team. Returns a map of user_id=userId
// if successful, otherwise returns an AppError.
func (c *Client) RemoveUserFromTeam(teamId string, userId string) (*Result, *AppError) {
if len(teamId) == 0 {
teamId = c.GetTeamId()
}
data := make(map[string]string)
data["user_id"] = userId
if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/remove_user_from_team", MapToJson(data)); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil {
return nil, err return nil, err

Просмотреть файл

@@ -17,6 +17,7 @@ const (
ACTION_CHANNEL_VIEWED = "channel_viewed" ACTION_CHANNEL_VIEWED = "channel_viewed"
ACTION_DIRECT_ADDED = "direct_added" ACTION_DIRECT_ADDED = "direct_added"
ACTION_NEW_USER = "new_user" ACTION_NEW_USER = "new_user"
ACTION_LEAVE_TEAM = "leave_team"
ACTION_USER_ADDED = "user_added" ACTION_USER_ADDED = "user_added"
ACTION_USER_REMOVED = "user_removed" ACTION_USER_REMOVED = "user_removed"
ACTION_PREFERENCE_CHANGED = "preference_changed" ACTION_PREFERENCE_CHANGED = "preference_changed"

Просмотреть файл

@@ -17,6 +17,7 @@ type TeamMember struct {
TeamId string `json:"team_id"` TeamId string `json:"team_id"`
UserId string `json:"user_id"` UserId string `json:"user_id"`
Roles string `json:"roles"` Roles string `json:"roles"`
DeleteAt int64 `json:"delete_at"`
} }
func (o *TeamMember) ToJson() string { func (o *TeamMember) ToJson() string {

Просмотреть файл

@@ -75,7 +75,13 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+rtcs.Err.Error()) result.Err = model.NewLocAppError("SqlSessionStore.Save", "store.sql_session.save.app_error", nil, "id="+session.Id+", "+rtcs.Err.Error())
return return
} else { } else {
session.TeamMembers = rtcs.Data.([]*model.TeamMember) tempMembers := rtcs.Data.([]*model.TeamMember)
session.TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
for _, tm := range tempMembers {
if tm.DeleteAt == 0 {
session.TeamMembers = append(session.TeamMembers, tm)
}
}
} }
storeChannel <- result storeChannel <- result
@@ -106,7 +112,13 @@ func (me SqlSessionStore) Get(sessionIdOrToken string) StoreChannel {
result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+rtcs.Err.Error()) result.Err = model.NewLocAppError("SqlSessionStore.Get", "store.sql_session.get.app_error", nil, "sessionIdOrToken="+sessionIdOrToken+", "+rtcs.Err.Error())
return return
} else { } else {
sessions[0].TeamMembers = rtcs.Data.([]*model.TeamMember) tempMembers := rtcs.Data.([]*model.TeamMember)
sessions[0].TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
for _, tm := range tempMembers {
if tm.DeleteAt == 0 {
sessions[0].TeamMembers = append(sessions[0].TeamMembers, tm)
}
}
} }
} }
@@ -144,7 +156,13 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
return return
} else { } else {
for _, session := range sessions { for _, session := range sessions {
session.TeamMembers = rtcs.Data.([]*model.TeamMember) tempMembers := rtcs.Data.([]*model.TeamMember)
session.TeamMembers = make([]*model.TeamMember, 0, len(tempMembers))
for _, tm := range tempMembers {
if tm.DeleteAt == 0 {
session.TeamMembers = append(session.TeamMembers, tm)
}
}
} }
} }

Просмотреть файл

@@ -41,6 +41,7 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore {
} }
func (s SqlTeamStore) UpgradeSchemaIfNeeded() { func (s SqlTeamStore) UpgradeSchemaIfNeeded() {
s.CreateColumnIfNotExists("TeamMembers", "DeleteAt", "bigint(20)", "bigint", "0")
} }
func (s SqlTeamStore) CreateIndexesIfNotExists() { func (s SqlTeamStore) CreateIndexesIfNotExists() {

Просмотреть файл

@@ -288,6 +288,13 @@ export function showInviteMemberModal() {
}); });
} }
export function showLeaveTeamModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_LEAVE_TEAM_MODAL,
value: true
});
}
export function showRegisterAppModal() { export function showRegisterAppModal() {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_REGISTER_APP_MODAL, type: ActionTypes.TOGGLE_REGISTER_APP_MODAL,

Просмотреть файл

@@ -135,6 +135,10 @@ function handleMessage(msg) {
handleNewUserEvent(); handleNewUserEvent();
break; break;
case SocketEvents.LEAVE_TEAM:
handleLeaveTeamEvent(msg);
break;
case SocketEvents.USER_ADDED: case SocketEvents.USER_ADDED:
handleUserAddedEvent(msg); handleUserAddedEvent(msg);
break; break;
@@ -219,6 +223,19 @@ function handleNewUserEvent() {
AsyncClient.getChannelExtraInfo(); AsyncClient.getChannelExtraInfo();
} }
function handleLeaveTeamEvent(msg) {
if (UserStore.getCurrentId() === msg.user_id) {
TeamStore.removeTeamMember(msg.team_id);
// if the are on the team begin removed redirect them to the root
if (TeamStore.getCurrentId() === msg.team_id) {
browserHistory.push('/');
}
} else if (TeamStore.getCurrentId() === msg.team_id) {
GlobalActions.emitProfilesForDmList();
}
}
function handleDirectAddedEvent(msg) { function handleDirectAddedEvent(msg) {
AsyncClient.getChannel(msg.channel_id); AsyncClient.getChannel(msg.channel_id);
AsyncClient.getDirectProfiles(); AsyncClient.getDirectProfiles();

Просмотреть файл

@@ -186,6 +186,10 @@ export default class UserList extends React.Component {
var memberList = this.state.users.map((user) => { var memberList = this.state.users.map((user) => {
var teamMember = this.getTeamMemberForUser(user.id); var teamMember = this.getTeamMemberForUser(user.id);
if (teamMember.delete_at > 0) {
return null;
}
return ( return (
<UserItem <UserItem
team={this.state.team} team={this.state.team}

Просмотреть файл

@@ -18,6 +18,7 @@ export default class UserItem extends React.Component {
super(props); super(props);
this.handleMakeMember = this.handleMakeMember.bind(this); this.handleMakeMember = this.handleMakeMember.bind(this);
this.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this);
this.handleMakeActive = this.handleMakeActive.bind(this); this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this); this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
@@ -56,6 +57,19 @@ export default class UserItem extends React.Component {
} }
} }
handleRemoveFromTeam() {
Client.removeUserFromTeam(
this.props.team.id,
this.props.user.id,
() => {
this.props.refreshProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeActive(e) { handleMakeActive(e) {
e.preventDefault(); e.preventDefault();
Client.updateActive(this.props.user.id, true, Client.updateActive(this.props.user.id, true,
@@ -222,6 +236,7 @@ export default class UserItem extends React.Component {
); );
} }
const me = UserStore.getCurrentUser();
const email = user.email; const email = user.email;
let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin';
let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
@@ -299,6 +314,24 @@ export default class UserItem extends React.Component {
); );
} }
let removeFromTeam = null;
if (this.props.user.id !== me.id) {
removeFromTeam = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleRemoveFromTeam}
>
<FormattedMessage
id='team_members_dropdown.leave_team'
defaultMessage='Remove From Team'
/>
</a>
</li>
);
}
let makeActive = null; let makeActive = null;
if (showMakeActive) { if (showMakeActive) {
makeActive = ( makeActive = (
@@ -428,7 +461,6 @@ export default class UserItem extends React.Component {
passwordReset = null; passwordReset = null;
} }
const me = UserStore.getCurrentUser();
let makeDemoteModal = null; let makeDemoteModal = null;
if (this.props.user.id === me.id) { if (this.props.user.id === me.id) {
const title = ( const title = (
@@ -511,6 +543,7 @@ export default class UserItem extends React.Component {
className='dropdown-menu member-menu' className='dropdown-menu member-menu'
role='menu' role='menu'
> >
{removeFromTeam}
{makeAdmin} {makeAdmin}
{makeMember} {makeMember}
{makeActive} {makeActive}

Просмотреть файл

@@ -39,17 +39,24 @@ class FilteredUserList extends React.Component {
this.state = { this.state = {
filter: '', filter: '',
users: this.filterUsers(props.teamMembers, props.users), users: this.filterUsers(props.teamMembers, props.users),
selected: 'team' selected: 'team',
teamMembers: props.teamMembers
}; };
} }
componentWillUpdate(nextProps) { componentWillReceiveProps(nextProps) {
// assume the user list is immutable // assume the user list is immutable
if (this.props.users !== nextProps.users) { if (this.props.users !== nextProps.users) {
this.setState({ this.setState({
users: this.filterUsers(nextProps.teamMembers, nextProps.users) users: this.filterUsers(nextProps.teamMembers, nextProps.users)
}); });
} }
if (this.props.teamMembers !== nextProps.teamMembers) {
this.setState({
users: this.filterUsers(nextProps.teamMembers, nextProps.users)
});
}
} }
componentDidMount() { componentDidMount() {
@@ -70,6 +77,10 @@ class FilteredUserList extends React.Component {
var filteredUsers = users.filter((user) => { var filteredUsers = users.filter((user) => {
for (const index in teamMembers) { for (const index in teamMembers) {
if (teamMembers.hasOwnProperty(index) && teamMembers[index].user_id === user.id) { if (teamMembers.hasOwnProperty(index) && teamMembers[index].user_id === user.id) {
if (teamMembers[index].delete_at > 0) {
return false;
}
return true; return true;
} }
} }

115
webapp/components/leave_team_modal.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,115 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
import * as GlobalActions from 'actions/global_actions.jsx';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {intlShape, injectIntl, FormattedMessage} from 'react-intl';
import {Modal} from 'react-bootstrap';
import React from 'react';
class LeaveTeamModal extends React.Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleHide = this.handleHide.bind(this);
this.state = {
show: false
};
}
componentDidMount() {
ModalStore.addModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle);
}
componentWillUnmount() {
ModalStore.removeModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle);
}
handleToggle(value) {
this.setState({
show: value
});
}
handleSubmit() {
GlobalActions.emitLeaveTeam();
this.setState({
show: false
});
}
handleHide() {
this.setState({
show: false
});
}
render() {
var currentUser = UserStore.getCurrentUser();
if (currentUser != null) {
return (
<Modal
className='modal-confirm'
show={this.state.show}
onHide={this.handleHide}
>
<Modal.Header closeButton={false}>
<Modal.Title>
<FormattedMessage
id='leave_team_modal.title'
defaultMessage='Leave the team?'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormattedMessage
id='leave_team_modal.desc'
defaultMessage='You will be removed from all public channels and private groups. If the team is private you will not be able to rejoin the team. Are you sure?'
/>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.handleHide}
>
<FormattedMessage
id='leave_team_modal.no'
defaultMessage='No'
/>
</button>
<button
type='button'
className='btn btn-danger'
onClick={this.handleSubmit}
>
<FormattedMessage
id='leave_team_modal.yes'
defaultMessage='Yes'
/>
</button>
</Modal.Footer>
</Modal>
);
}
return null;
}
}
LeaveTeamModal.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(LeaveTeamModal);

Просмотреть файл

@@ -236,6 +236,20 @@ export default class NavbarDropdown extends React.Component {
); );
} }
teams.push(
<li key='leaveTeam_li'>
<a
href='#'
onClick={GlobalActions.showLeaveTeamModal}
>
<FormattedMessage
id='navbar_dropdown.leave'
defaultMessage='Leave Team'
/>
</a>
</li>
);
if (this.state.teamMembers && this.state.teamMembers.length > 1) { if (this.state.teamMembers && this.state.teamMembers.length > 1) {
teams.push( teams.push(
<li <li

Просмотреть файл

@@ -34,6 +34,7 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
import RegisterAppModal from 'components/register_app_modal.jsx'; import RegisterAppModal from 'components/register_app_modal.jsx';
import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx'; import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
import InviteMemberModal from 'components/invite_member_modal.jsx'; import InviteMemberModal from 'components/invite_member_modal.jsx';
import LeaveTeamModal from 'components/leave_team_modal.jsx';
import SelectTeamModal from 'components/admin_console/select_team_modal.jsx'; import SelectTeamModal from 'components/admin_console/select_team_modal.jsx';
export default class NeedsTeam extends React.Component { export default class NeedsTeam extends React.Component {
@@ -129,6 +130,7 @@ export default class NeedsTeam extends React.Component {
<GetPublicLinkModal/> <GetPublicLinkModal/>
<GetTeamInviteLinkModal/> <GetTeamInviteLinkModal/>
<InviteMemberModal/> <InviteMemberModal/>
<LeaveTeamModal/>
<ImportThemeModal/> <ImportThemeModal/>
<TeamSettingsModal/> <TeamSettingsModal/>
<MoreChannelsModal/> <MoreChannelsModal/>

Просмотреть файл

@@ -19,6 +19,7 @@ export default class TeamMembersDropdown extends React.Component {
super(props); super(props);
this.handleMakeMember = this.handleMakeMember.bind(this); this.handleMakeMember = this.handleMakeMember.bind(this);
this.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this);
this.handleMakeActive = this.handleMakeActive.bind(this); this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this); this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
@@ -52,6 +53,19 @@ export default class TeamMembersDropdown extends React.Component {
); );
} }
} }
handleRemoveFromTeam() {
Client.removeUserFromTeam(
'',
this.props.user.id,
() => {
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfiles();
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeActive() { handleMakeActive() {
Client.updateActive(this.props.user.id, true, Client.updateActive(this.props.user.id, true,
() => { () => {
@@ -171,6 +185,7 @@ export default class TeamMembersDropdown extends React.Component {
); );
} }
const me = UserStore.getCurrentUser();
let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin';
let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
let showMakeActive = false; let showMakeActive = false;
@@ -225,6 +240,24 @@ export default class TeamMembersDropdown extends React.Component {
); );
} }
let removeFromTeam = null;
if (this.props.user.id !== me.id) {
removeFromTeam = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleRemoveFromTeam}
>
<FormattedMessage
id='team_members_dropdown.leave_team'
defaultMessage='Remove From Team'
/>
</a>
</li>
);
}
let makeActive = null; let makeActive = null;
if (showMakeActive) { if (showMakeActive) {
// makeActive = ( // makeActive = (
@@ -260,7 +293,7 @@ export default class TeamMembersDropdown extends React.Component {
// </li> // </li>
// ); // );
} }
const me = UserStore.getCurrentUser();
let makeDemoteModal = null; let makeDemoteModal = null;
if (this.props.user.id === me.id) { if (this.props.user.id === me.id) {
const title = ( const title = (
@@ -321,6 +354,7 @@ export default class TeamMembersDropdown extends React.Component {
className='dropdown-menu member-menu' className='dropdown-menu member-menu'
role='menu' role='menu'
> >
{removeFromTeam}
{makeAdmin} {makeAdmin}
{makeMember} {makeMember}
{makeActive} {makeActive}

Просмотреть файл

@@ -1202,6 +1202,7 @@
"navbar_dropdown.accountSettings": "Account Settings", "navbar_dropdown.accountSettings": "Account Settings",
"navbar_dropdown.console": "System Console", "navbar_dropdown.console": "System Console",
"navbar_dropdown.create": "Create a New Team", "navbar_dropdown.create": "Create a New Team",
"navbar_dropdown.leave": "Leave Team",
"navbar_dropdown.emoji": "Custom Emoji", "navbar_dropdown.emoji": "Custom Emoji",
"navbar_dropdown.help": "Help", "navbar_dropdown.help": "Help",
"navbar_dropdown.integrations": "Integrations", "navbar_dropdown.integrations": "Integrations",
@@ -1414,6 +1415,7 @@
"team_members_dropdown.makeAdmin": "Make Team Admin", "team_members_dropdown.makeAdmin": "Make Team Admin",
"team_members_dropdown.makeInactive": "Make Inactive", "team_members_dropdown.makeInactive": "Make Inactive",
"team_members_dropdown.makeMember": "Make Member", "team_members_dropdown.makeMember": "Make Member",
"team_members_dropdown.leave_team": "Remove From Team",
"team_members_dropdown.member": "Member", "team_members_dropdown.member": "Member",
"team_members_dropdown.systemAdmin": "System Admin", "team_members_dropdown.systemAdmin": "System Admin",
"team_members_dropdown.teamAdmin": "Team Admin", "team_members_dropdown.teamAdmin": "Team Admin",

Просмотреть файл

@@ -18,7 +18,7 @@
"keymirror": "0.1.1", "keymirror": "0.1.1",
"marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b", "marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b",
"match-at": "0.1.0", "match-at": "0.1.0",
"mattermost": "mattermost/mattermost-javascript#18527e6c4a9aea69aa7845a62d9618b357faa4e7", "mattermost": "mattermost/mattermost-javascript#c72a75ca4ac135e2d476fc048ef7adc450e6739f",
"object-assign": "4.1.0", "object-assign": "4.1.0",
"perfect-scrollbar": "0.6.11", "perfect-scrollbar": "0.6.11",
"react": "15.0.2", "react": "15.0.2",

Просмотреть файл

@@ -33,6 +33,7 @@ class ModalStoreClass extends EventEmitter {
switch (type) { switch (type) {
case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL: case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
case ActionTypes.TOGGLE_LEAVE_TEAM_MODAL:
case ActionTypes.TOGGLE_DELETE_POST_MODAL: case ActionTypes.TOGGLE_DELETE_POST_MODAL:
case ActionTypes.TOGGLE_GET_POST_LINK_MODAL: case ActionTypes.TOGGLE_GET_POST_LINK_MODAL:
case ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL: case ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL:

Просмотреть файл

@@ -139,6 +139,16 @@ class TeamStoreClass extends EventEmitter {
this.team_members.push(member); this.team_members.push(member);
} }
removeTeamMember(teamId) {
for (var index in this.team_members) {
if (this.team_members.hasOwnProperty(index)) {
if (this.team_members[index].team_id === teamId) {
Reflect.deleteProperty(this.team_members, index);
}
}
}
}
getTeamMembers() { getTeamMembers() {
return this.team_members; return this.team_members;
} }

Просмотреть файл

@@ -113,6 +113,7 @@ export default {
TOGGLE_IMPORT_THEME_MODAL: null, TOGGLE_IMPORT_THEME_MODAL: null,
TOGGLE_INVITE_MEMBER_MODAL: null, TOGGLE_INVITE_MEMBER_MODAL: null,
TOGGLE_LEAVE_TEAM_MODAL: null,
TOGGLE_DELETE_POST_MODAL: null, TOGGLE_DELETE_POST_MODAL: null,
TOGGLE_GET_POST_LINK_MODAL: null, TOGGLE_GET_POST_LINK_MODAL: null,
TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null, TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null,
@@ -160,6 +161,7 @@ export default {
CHANNEL_VIEWED: 'channel_viewed', CHANNEL_VIEWED: 'channel_viewed',
DIRECT_ADDED: 'direct_added', DIRECT_ADDED: 'direct_added',
NEW_USER: 'new_user', NEW_USER: 'new_user',
LEAVE_TEAM: 'leave_team',
USER_ADDED: 'user_added', USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed', USER_REMOVED: 'user_removed',
TYPING: 'typing', TYPING: 'typing',