[MM-9904] Add /invite slash command to invite users to a channel (#8482)

* [MM-9904] Add /invite slash command to invite users to a channel

* Update en.json
This commit is contained in:
Carlos Tadeu Panato Junior
2018-04-16 14:23:58 +02:00
committed by Jesús Espino
parent 0759cf639d
commit bf24f51c4e
4 changed files with 272 additions and 0 deletions

View File

@@ -218,6 +218,20 @@ func (me *TestHelper) createChannel(team *model.Team, channelType string) *model
return channel
}
func (me *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError
var channel *model.Channel
if channel, err = me.App.CreateDirectChannel(me.BasicUser.Id, user.Id); err != nil {
l4g.Error(err.Error())
l4g.Close()
time.Sleep(time.Second)
panic(err)
}
utils.EnableDebugLogForTest()
return channel
}
func (me *TestHelper) CreatePost(channel *model.Channel) *model.Post {
id := model.NewId()

102
app/command_invite.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"strings"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
goi18n "github.com/nicksnyder/go-i18n/i18n"
)
type InviteProvider struct {
}
const (
CMD_INVITE = "invite"
)
func init() {
RegisterCommandProvider(&InviteProvider{})
}
func (me *InviteProvider) GetTrigger() string {
return CMD_INVITE
}
func (me *InviteProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
return &model.Command{
Trigger: CMD_INVITE,
AutoComplete: true,
AutoCompleteDesc: T("api.command_invite.desc"),
AutoCompleteHint: T("api.command_invite.hint"),
DisplayName: T("api.command_invite.name"),
}
}
func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
if message == "" {
return &model.CommandResponse{Text: args.T("api.command_invite.missing_message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
l4g.Debug(message)
splitMessage := strings.SplitN(message, " ", 2)
targetUsername := splitMessage[0]
targetUsername = strings.TrimPrefix(targetUsername, "@")
var userProfile *model.User
if result := <-a.Srv.Store.User().GetByUsername(targetUsername); result.Err != nil {
l4g.Error(result.Err.Error())
return &model.CommandResponse{Text: args.T("api.command_invite.missing_user.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
userProfile = result.Data.(*model.User)
}
var channelToJoin *model.Channel
var err *model.AppError
// User set a channel to add the invited user
if len(splitMessage) > 1 && splitMessage[1] != "" {
targetChannelName := strings.TrimPrefix(strings.TrimSpace(splitMessage[1]), "~")
if channelToJoin, err = a.GetChannelByName(targetChannelName, args.TeamId); err != nil {
return &model.CommandResponse{Text: args.T("api.command_invite.channel.error", map[string]interface{}{"Channel": targetChannelName}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
} else {
channelToJoin, err = a.GetChannel(args.ChannelId)
if err != nil {
return &model.CommandResponse{Text: args.T("api.command_invite.channel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
}
// Check if is a Direct Channel
if channelToJoin.Type == model.CHANNEL_DIRECT || channelToJoin.Type == model.CHANNEL_GROUP {
return &model.CommandResponse{Text: args.T("api.command_invite.directchannel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
// Check if user is already in the channel
_, err = a.GetChannelMember(channelToJoin.Id, userProfile.Id)
if err == nil {
return &model.CommandResponse{Text: args.T("api.command_invite.user_already_in_channel.app_error", map[string]interface{}{"User": userProfile.Username}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if channelToJoin.Type == model.CHANNEL_OPEN && !a.SessionHasPermissionToChannel(args.Session, channelToJoin.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) {
return &model.CommandResponse{Text: args.T("api.command_invite.permission.app_error", map[string]interface{}{"User": userProfile.Username, "Channel": channelToJoin.Name}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if channelToJoin.Type == model.CHANNEL_PRIVATE && !a.SessionHasPermissionToChannel(args.Session, channelToJoin.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) {
return &model.CommandResponse{Text: args.T("api.command_invite.permission.app_error", map[string]interface{}{"User": userProfile.Username, "Channel": channelToJoin.Name}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if _, err := a.AddChannelMember(userProfile.Id, channelToJoin, args.Session.UserId, ""); err != nil {
return &model.CommandResponse{Text: args.T("api.command_invite.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if args.ChannelId != channelToJoin.Id {
return &model.CommandResponse{Text: args.T("api.command_invite.success", map[string]interface{}{"User": userProfile.Username, "Channel": channelToJoin.Name}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{}
}

108
app/command_invite_test.go Normal file
View File

@@ -0,0 +1,108 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model"
)
func TestInviteProvider(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
channel := th.createChannel(th.BasicTeam, model.CHANNEL_OPEN)
privateChannel := th.createChannel(th.BasicTeam, model.CHANNEL_PRIVATE)
dmChannel := th.CreateDmChannel(th.BasicUser2)
basicUser3 := th.CreateUser()
th.LinkUserToTeam(basicUser3, th.BasicTeam)
basicUser4 := th.CreateUser()
InviteP := InviteProvider{}
args := &model.CommandArgs{
T: func(s string, args ...interface{}) string { return s },
ChannelId: th.BasicChannel.Id,
TeamId: th.BasicTeam.Id,
Session: model.Session{UserId: th.BasicUser.Id, TeamMembers: []*model.TeamMember{{TeamId: th.BasicTeam.Id, Roles: model.TEAM_USER_ROLE_ID}}},
}
userAndWrongChannel := "@" + th.BasicUser2.Username + " wrongchannel1"
userAndChannel := "@" + th.BasicUser2.Username + " ~" + channel.Name + " "
userAndDisplayChannel := "@" + th.BasicUser2.Username + " ~" + channel.DisplayName + " "
userAndPrivateChannel := "@" + th.BasicUser2.Username + " ~" + privateChannel.Name
userAndDMChannel := "@" + basicUser3.Username + " ~" + dmChannel.Name
tests := []struct {
desc string
expected string
msg string
}{
{
desc: "Missing user and channel in the command",
expected: "api.command_invite.missing_message.app_error",
msg: "",
},
{
desc: "User added in the current channel",
expected: "",
msg: th.BasicUser2.Username,
},
{
desc: "Add user to another channel not the current",
expected: "api.command_invite.success",
msg: userAndChannel,
},
{
desc: "try to add a user to a direct channel",
expected: "api.command_invite.directchannel.app_error",
msg: userAndDMChannel,
},
{
desc: "Try to add a user to a invalid channel",
expected: "api.command_invite.channel.error",
msg: userAndWrongChannel,
},
{
desc: "Try to add a user to an private channel",
expected: "api.command_invite.success",
msg: userAndPrivateChannel,
},
{
desc: "Using display channel name which is different form Channel name",
expected: "api.command_invite.channel.error",
msg: userAndDisplayChannel,
},
{
desc: "Invalid user to current channel",
expected: "api.command_invite.missing_user.app_error",
msg: "@invalidUser123",
},
{
desc: "Invalid user to current channel without @",
expected: "api.command_invite.missing_user.app_error",
msg: "invalidUser321",
},
{
desc: "try to add a user which is not part of the team",
expected: "api.command_invite.fail.app_error",
msg: basicUser4.Username,
},
{
desc: "try to add a user to a direct channel",
expected: "api.command_invite.directchannel.app_error",
msg: userAndDMChannel,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
actual := InviteP.DoCommand(th.App, args, test.msg).Text
assert.Equal(t, test.expected, actual)
})
}
}

View File

@@ -814,6 +814,54 @@
"id": "api.command_join.success",
"translation": "Joined channel."
},
{
"id": "api.command_invite.hint",
"translation": "@[username] ~[channel]"
},
{
"id": "api.command_invite.name",
"translation": "invite"
},
{
"id": "api.command_invite.desc",
"translation": "Invite a user to a channel"
},
{
"id": "api.command_invite.missing_message.app_error",
"translation": "Missing Username and Channel."
},
{
"id": "api.command_invite.missing_user.app_error",
"translation": "We couldn't find the user."
},
{
"id": "api.command_invite.channel.app_error",
"translation": "Error to retrieve the current channel."
},
{
"id": "api.command_invite.channel.error",
"translation": "Could not find the channel {{.Channel}}. Please use the [channel handle](https://about.mattermost.com/default-channel-handle-documentation) to identify channels."
},
{
"id": "api.command_invite.fail.app_error",
"translation": "An error occurred while joining the channel."
},
{
"id": "api.command_invite.permission.app_error",
"translation": "You don't have enough permissions to add {{.User}} in {{.Channel}}."
},
{
"id": "api.command_invite.directchannel.app_error",
"translation": "You can't add someone to a direct message channel."
},
{
"id": "api.command_invite.user_already_in_channel.app_error",
"translation": "{{.User}} is already in the channel."
},
{
"id": "api.command_invite.success",
"translation": "{{.User}} added to {{.Channel}} channel."
},
{
"id": "api.command_kick.name",
"translation": "kick"