PLT-7218: CLI to move slash commands between teams. (#7574)

This commit is contained in:
George Goldberg
2017-10-04 19:08:59 +01:00
committed by Chris
parent e16bdf8d1d
commit f3fc6d11fa
9 changed files with 252 additions and 1 deletions

View File

@@ -336,6 +336,16 @@ func (a *App) UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command,
}
}
func (a *App) MoveCommand(team *model.Team, command *model.Command) *model.AppError {
command.TeamId = team.Id
if result := <-a.Srv.Store.Command().Update(command); result.Err != nil {
return result.Err
}
return nil
}
func (a *App) RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
return nil, model.NewAppError("RegenCommandToken", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)

46
app/command_test.go Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (c) 2017-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 TestMoveCommand(t *testing.T) {
th := Setup().InitBasic()
sourceTeam := th.CreateTeam()
targetTeam := th.CreateTeam()
command := &model.Command{}
command.CreatorId = model.NewId()
command.Method = model.COMMAND_METHOD_POST
command.TeamId = sourceTeam.Id
command.URL = "http://nowhere.com/"
command.Trigger = "trigger1"
command, err := th.App.CreateCommand(command)
assert.Nil(t, err)
defer func() {
th.App.PermanentDeleteTeam(sourceTeam)
th.App.PermanentDeleteTeam(targetTeam)
}()
// Move a command and check the team is updated.
assert.Nil(t, th.App.MoveCommand(targetTeam, command))
retrievedCommand, err := th.App.GetCommand(command.Id)
assert.Nil(t, err)
assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
// Move it to the team it's already in. Nothing should change.
assert.Nil(t, th.App.MoveCommand(targetTeam, command))
retrievedCommand, err = th.App.GetCommand(command.Id)
assert.Nil(t, err)
assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
}

69
cmd/platform/command.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var commandCmd = &cobra.Command{
Use: "command",
Short: "Management of slash commands",
}
var commandMoveCmd = &cobra.Command{
Use: "move",
Short: "Move a slash command to a different team",
Long: `Move a slash command to a different team. Commands can be specified by [team]:[command-trigger-word]. ie. myteam:trigger or by command ID.`,
Example: ` command move newteam oldteam:command`,
RunE: moveCommandCmdF,
}
func init() {
commandCmd.AddCommand(
commandMoveCmd,
)
}
func moveCommandCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Enter the destination team and at least one comamnd to move.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find destination team '" + args[0] + "'")
}
commands := getCommandsFromCommandArgs(a, args[1:])
CommandPrintErrorln(commands)
for i, command := range commands {
if command == nil {
CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
continue
}
if err := moveCommand(a, team, command); err != nil {
CommandPrintErrorln("Unable to move command '" + command.Trigger + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
}
}
return nil
}
func moveCommand(a *app.App, team *model.Team, command *model.Command) *model.AppError {
if err := a.MoveCommand(team, command); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
const COMMAND_ARGS_SEPARATOR = ":"
func getCommandsFromCommandArgs(a *app.App, commandArgs []string) []*model.Command {
commands := make([]*model.Command, 0, len(commandArgs))
for _, commandArg := range commandArgs {
command := getCommandFromCommandArg(a, commandArg)
commands = append(commands, command)
}
return commands
}
func parseCommandArg(commandArg string) (string, string) {
result := strings.SplitN(commandArg, COMMAND_ARGS_SEPARATOR, 2)
if len(result) == 1 {
return "", commandArg
}
return result[0], result[1]
}
func getCommandFromCommandArg(a *app.App, commandArg string) *model.Command {
teamArg, commandPart := parseCommandArg(commandArg)
if teamArg == "" && commandPart == "" {
return nil
}
var command *model.Command
if teamArg != "" {
team := getTeamFromTeamArg(a, teamArg)
if team == nil {
return nil
}
if result := <-a.Srv.Store.Command().GetByTrigger(team.Id, commandPart); result.Err == nil {
command = result.Data.(*model.Command)
} else {
fmt.Println(result.Err.Error())
}
}
if command == nil {
if result := <-a.Srv.Store.Command().Get(commandPart); result.Err == nil {
command = result.Data.(*model.Command)
}
}
return command
}

View File

@@ -40,7 +40,7 @@ func init() {
resetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
rootCmd.AddCommand(serverCmd, versionCmd, userCmd, teamCmd, licenseCmd, importCmd, resetCmd, channelCmd, rolesCmd, testCmd, ldapCmd, configCmd, jobserverCmd)
rootCmd.AddCommand(serverCmd, versionCmd, userCmd, teamCmd, licenseCmd, importCmd, resetCmd, channelCmd, rolesCmd, testCmd, ldapCmd, configCmd, jobserverCmd, commandCmd)
}
var rootCmd = &cobra.Command{

View File

@@ -5579,6 +5579,10 @@
"id": "store.sql_command.save.update.app_error",
"translation": "We couldn't update the command"
},
{
"id": "store.sql_command.get_by_trigger.app_error",
"translation": "We couldn't get the command"
},
{
"id": "store.sql_command_webhooks.get.app_error",
"translation": "We couldn't get the webhook"

View File

@@ -119,6 +119,27 @@ func (s SqlCommandStore) GetByTeam(teamId string) store.StoreChannel {
return storeChannel
}
func (s SqlCommandStore) GetByTrigger(teamId string, trigger string) store.StoreChannel {
storeChannel := make(store.StoreChannel, 1)
go func() {
result := store.StoreResult{}
var command model.Command
if err := s.GetReplica().SelectOne(&command, "SELECT * FROM Commands WHERE TeamId = :TeamId AND `Trigger` = :Trigger AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId, "Trigger": trigger}); err != nil {
result.Err = model.NewAppError("SqlCommandStore.GetByTrigger", "store.sql_command.get_by_trigger.app_error", nil, "teamId="+teamId+", trigger="+trigger+", err="+err.Error(), http.StatusInternalServerError)
}
result.Data = &command
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlCommandStore) Delete(commandId string, time int64) store.StoreChannel {
storeChannel := make(store.StoreChannel, 1)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
func TestCommandStoreSave(t *testing.T) {
@@ -82,6 +83,41 @@ func TestCommandStoreGetByTeam(t *testing.T) {
}
}
func TestCommandStoreGetByTrigger(t *testing.T) {
ss := Setup()
o1 := &model.Command{}
o1.CreatorId = model.NewId()
o1.Method = model.COMMAND_METHOD_POST
o1.TeamId = model.NewId()
o1.URL = "http://nowhere.com/"
o1.Trigger = "trigger1"
o2 := &model.Command{}
o2.CreatorId = model.NewId()
o2.Method = model.COMMAND_METHOD_POST
o2.TeamId = model.NewId()
o2.URL = "http://nowhere.com/"
o2.Trigger = "trigger1"
o1 = (<-ss.Command().Save(o1)).Data.(*model.Command)
o2 = (<-ss.Command().Save(o2)).Data.(*model.Command)
if r1 := <-ss.Command().GetByTrigger(o1.TeamId, o1.Trigger); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.Command).Id != o1.Id {
t.Fatal("invalid returned command")
}
}
store.Must(ss.Command().Delete(o1.Id, model.GetMillis()))
if result := <-ss.Command().GetByTrigger(o1.TeamId, o1.Trigger); result.Err == nil {
t.Fatal("no commands should have returned")
}
}
func TestCommandStoreDelete(t *testing.T) {
ss := Setup()

View File

@@ -7,6 +7,7 @@ import (
"time"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
)
@@ -322,6 +323,7 @@ type CommandStore interface {
Save(webhook *model.Command) StoreChannel
Get(id string) StoreChannel
GetByTeam(teamId string) StoreChannel
GetByTrigger(teamId string, trigger string) StoreChannel
Delete(commandId string, time int64) StoreChannel
PermanentDeleteByTeam(teamId string) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel