[MM-20684] Slash Command Autocomplete (#14557)
* [MM-20684] Initial implementation of the Command Autocomplete (#13602) * Implement Autocomplete Data * Change CommandName to Trigger * Fix Autocomplete test * Make stylistic changes * Rename a bunch of fields and methods * Fix variable names, safer type assertions * [MM-20684] plugin autocomplete implementation (#14259) * Add an endpoint for command autocomplete suggestions * Add full Suggestion to the AutocompleteSugestion struct * Add Dynamic Argument support * Tidy up things * Fix missed test case * Add support of the named arguments * Update autocomplete API Fix review issues Implement dynamic args as a local request * Fix ineffassign * Add support of the uppercase letters in arguments * Add support of the optional arguments * Remove ineffectual assignment * Add support for icons (#14489) * Address couple of nits * Add comment to IconData * Add types to all consts Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e8daab6b84
Коммит
43e606173b
@@ -23,6 +23,7 @@ func (api *API) InitCommand() {
|
||||
api.BaseRoutes.Command.Handle("", api.ApiSessionRequired(deleteCommand)).Methods("DELETE")
|
||||
|
||||
api.BaseRoutes.Team.Handle("/commands/autocomplete", api.ApiSessionRequired(listAutocompleteCommands)).Methods("GET")
|
||||
api.BaseRoutes.Team.Handle("/commands/autocomplete_suggestions", api.ApiSessionRequired(listCommandAutocompleteSuggestions)).Methods("GET")
|
||||
api.BaseRoutes.Command.Handle("/regen_token", api.ApiSessionRequired(regenCommandToken)).Methods("PUT")
|
||||
}
|
||||
|
||||
@@ -369,6 +370,39 @@ func listAutocompleteCommands(c *Context, w http.ResponseWriter, r *http.Request
|
||||
w.Write([]byte(model.CommandListToJson(commands)))
|
||||
}
|
||||
|
||||
func listCommandAutocompleteSuggestions(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireTeamId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_VIEW_TEAM) {
|
||||
c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
|
||||
return
|
||||
}
|
||||
|
||||
roleId := model.SYSTEM_USER_ROLE_ID
|
||||
if c.IsSystemAdmin() {
|
||||
roleId = model.SYSTEM_ADMIN_ROLE_ID
|
||||
}
|
||||
|
||||
userInput := r.URL.Query().Get("user_input")
|
||||
if userInput == "" {
|
||||
c.SetInvalidParam("userInput")
|
||||
return
|
||||
}
|
||||
userInput = strings.TrimPrefix(userInput, "/")
|
||||
|
||||
commands, err := c.App.ListAutocompleteCommands(c.Params.TeamId, c.App.T)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
suggestions := c.App.GetSuggestions(commands, userInput, roleId)
|
||||
|
||||
w.Write(model.AutocompleteSuggestionsToJSON(suggestions))
|
||||
}
|
||||
|
||||
func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireCommandId()
|
||||
if c.Err != nil {
|
||||
|
||||
@@ -407,6 +407,96 @@ func TestListAutocompleteCommands(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestListCommandAutocompleteSuggestions(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
newCmd := &model.Command{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
URL: "http://nowhere.com",
|
||||
Method: model.COMMAND_METHOD_POST,
|
||||
Trigger: "custom_command"}
|
||||
|
||||
_, resp := th.SystemAdminClient.CreateCommand(newCmd)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
t.Run("ListAutocompleteSuggestionsOnly", func(t *testing.T) {
|
||||
suggestions, resp := th.SystemAdminClient.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
foundEcho := false
|
||||
foundShrug := false
|
||||
foundCustom := false
|
||||
for _, command := range suggestions {
|
||||
if command.Suggestion == "echo" {
|
||||
foundEcho = true
|
||||
}
|
||||
if command.Suggestion == "shrug" {
|
||||
foundShrug = true
|
||||
}
|
||||
if command.Suggestion == "custom_command" {
|
||||
foundCustom = true
|
||||
}
|
||||
}
|
||||
require.True(t, foundEcho, "Couldn't find echo command")
|
||||
require.True(t, foundShrug, "Couldn't find shrug command")
|
||||
require.False(t, foundCustom, "Should not list the custom command")
|
||||
})
|
||||
|
||||
t.Run("ListAutocompleteSuggestionsOnlyWithInput", func(t *testing.T) {
|
||||
suggestions, resp := th.SystemAdminClient.ListCommandAutocompleteSuggestions("/e", th.BasicTeam.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
foundEcho := false
|
||||
foundShrug := false
|
||||
for _, command := range suggestions {
|
||||
if command.Suggestion == "echo" {
|
||||
foundEcho = true
|
||||
}
|
||||
if command.Suggestion == "shrug" {
|
||||
foundShrug = true
|
||||
}
|
||||
}
|
||||
require.True(t, foundEcho, "Couldn't find echo command")
|
||||
require.False(t, foundShrug, "Should not list the shrug command")
|
||||
})
|
||||
|
||||
t.Run("RegularUserCanListOnlySystemCommands", func(t *testing.T) {
|
||||
suggestions, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
foundEcho := false
|
||||
foundCustom := false
|
||||
for _, suggestion := range suggestions {
|
||||
if suggestion.Suggestion == "echo" {
|
||||
foundEcho = true
|
||||
}
|
||||
if suggestion.Suggestion == "custom_command" {
|
||||
foundCustom = true
|
||||
}
|
||||
}
|
||||
require.True(t, foundEcho, "Couldn't find echo command")
|
||||
require.False(t, foundCustom, "Should not list the custom command")
|
||||
})
|
||||
|
||||
t.Run("NoMember", func(t *testing.T) {
|
||||
Client.Logout()
|
||||
user := th.CreateUser()
|
||||
th.SystemAdminClient.RemoveTeamMember(th.BasicTeam.Id, user.Id)
|
||||
Client.Login(user.Email, user.Password)
|
||||
_, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("NotLoggedIn", func(t *testing.T) {
|
||||
Client.Logout()
|
||||
_, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetCommand(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -190,6 +190,8 @@ type AppIface interface {
|
||||
// GetSessionLengthInMillis returns the session length, in milliseconds,
|
||||
// based on the type of session (Mobile, SSO, Web/LDAP).
|
||||
GetSessionLengthInMillis(session *model.Session) int64
|
||||
// GetSuggestions returns suggestions for user input.
|
||||
GetSuggestions(commands []*model.Command, userInput, roleID string) []model.AutocompleteSuggestion
|
||||
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
|
||||
GetTeamGroupUsers(teamID string) ([]*model.User, *model.AppError)
|
||||
// GetTeamSchemeChannelRoles Checks if a team has an override scheme and returns the scheme channel role names or default channel role names.
|
||||
|
||||
253
app/command_autocomplete.go
Обычный файл
253
app/command_autocomplete.go
Обычный файл
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
// GetSuggestions returns suggestions for user input.
|
||||
func (a *App) GetSuggestions(commands []*model.Command, userInput, roleID string) []model.AutocompleteSuggestion {
|
||||
sort.Slice(commands, func(i, j int) bool {
|
||||
return strings.Compare(strings.ToLower(commands[i].Trigger), strings.ToLower(commands[j].Trigger)) < 0
|
||||
})
|
||||
|
||||
autocompleteData := []*model.AutocompleteData{}
|
||||
for _, command := range commands {
|
||||
if command.AutocompleteData == nil {
|
||||
command.AutocompleteData = model.NewAutocompleteData(command.Trigger, command.AutoCompleteHint, command.AutoCompleteDesc)
|
||||
}
|
||||
autocompleteData = append(autocompleteData, command.AutocompleteData)
|
||||
}
|
||||
|
||||
suggestions := a.getSuggestions(autocompleteData, "", userInput, roleID)
|
||||
for i, suggestion := range suggestions {
|
||||
for _, command := range commands {
|
||||
if strings.HasPrefix(suggestion.Complete, command.Trigger) {
|
||||
suggestions[i].IconData = command.AutocompleteIconData
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func (a *App) getSuggestions(commands []*model.AutocompleteData, inputParsed, inputToBeParsed, roleID string) []model.AutocompleteSuggestion {
|
||||
suggestions := []model.AutocompleteSuggestion{}
|
||||
index := strings.Index(inputToBeParsed, " ")
|
||||
if index == -1 { // no space in input
|
||||
for _, command := range commands {
|
||||
if strings.HasPrefix(command.Trigger, strings.ToLower(inputToBeParsed)) && (command.RoleID == roleID || roleID == model.SYSTEM_ADMIN_ROLE_ID || roleID == "") {
|
||||
s := model.AutocompleteSuggestion{
|
||||
Complete: inputParsed + command.Trigger,
|
||||
Suggestion: command.Trigger,
|
||||
Description: command.HelpText,
|
||||
Hint: command.Hint,
|
||||
}
|
||||
suggestions = append(suggestions, s)
|
||||
}
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
for _, command := range commands {
|
||||
if command.Trigger != strings.ToLower(inputToBeParsed[:index]) {
|
||||
continue
|
||||
}
|
||||
if roleID != "" && roleID != model.SYSTEM_ADMIN_ROLE_ID && roleID != command.RoleID {
|
||||
continue
|
||||
}
|
||||
toBeParsed := inputToBeParsed[index+1:]
|
||||
parsed := inputParsed + inputToBeParsed[:index+1]
|
||||
if len(command.Arguments) == 0 {
|
||||
// Seek recursively in subcommands
|
||||
subSuggestions := a.getSuggestions(command.SubCommands, parsed, toBeParsed, roleID)
|
||||
suggestions = append(suggestions, subSuggestions...)
|
||||
continue
|
||||
}
|
||||
found, _, _, suggestion := a.parseArguments(command.Arguments, parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, suggestion...)
|
||||
}
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func (a *App) parseArguments(args []*model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
||||
if len(args) == 0 {
|
||||
return false, parsed, toBeParsed, suggestions
|
||||
}
|
||||
if args[0].Required {
|
||||
found, changedParsed, changedToBeParsed, suggestion := a.parseArgument(args[0], parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, suggestion...)
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
return a.parseArguments(args[1:], changedParsed, changedToBeParsed)
|
||||
}
|
||||
// Handling optional arguments. Optional argument can be inputted or not,
|
||||
// so we have to pase both cases recursively and output combined suggestions.
|
||||
foundWithOptional, changedParsedWithOptional, changedToBeParsedWithOptional, suggestionsWithOptional := a.parseArgument(args[0], parsed, toBeParsed)
|
||||
if foundWithOptional {
|
||||
suggestions = append(suggestions, suggestionsWithOptional...)
|
||||
} else {
|
||||
foundWithOptionalRest, changedParsedWithOptionalRest, changedToBeParsedWithOptionalRest, suggestionsWithOptionalRest := a.parseArguments(args[1:], changedParsedWithOptional, changedToBeParsedWithOptional)
|
||||
if foundWithOptionalRest {
|
||||
suggestions = append(suggestions, suggestionsWithOptionalRest...)
|
||||
}
|
||||
foundWithOptional = foundWithOptionalRest
|
||||
changedParsedWithOptional = changedParsedWithOptionalRest
|
||||
changedToBeParsedWithOptional = changedToBeParsedWithOptionalRest
|
||||
}
|
||||
|
||||
foundWithoutOptional, changedParsedWithoutOptional, changedToBeParsedWithoutOptional, suggestionsWithoutOptional := a.parseArguments(args[1:], parsed, toBeParsed)
|
||||
if foundWithoutOptional {
|
||||
suggestions = append(suggestions, suggestionsWithoutOptional...)
|
||||
}
|
||||
|
||||
// if suggestions were found we can return them
|
||||
if foundWithOptional || foundWithoutOptional {
|
||||
return true, parsed + toBeParsed, "", suggestions
|
||||
}
|
||||
// no suggestions found yet, check if optional argument was inputted
|
||||
if changedParsedWithOptional != parsed && changedToBeParsedWithOptional != toBeParsed {
|
||||
return false, changedParsedWithOptional, changedToBeParsedWithOptional, suggestions
|
||||
}
|
||||
// no suggestions and optional argument was not inputted
|
||||
return foundWithoutOptional, changedParsedWithoutOptional, changedToBeParsedWithoutOptional, suggestions
|
||||
}
|
||||
|
||||
func (a *App) parseArgument(arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
||||
if arg.Name != "" { //Parse the --name first
|
||||
found, changedParsed, changedToBeParsed, suggestion := parseNamedArgument(arg, parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, suggestion)
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
if changedToBeParsed == "" {
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
if changedToBeParsed == " " {
|
||||
changedToBeParsed = ""
|
||||
}
|
||||
parsed = changedParsed
|
||||
toBeParsed = changedToBeParsed
|
||||
}
|
||||
if arg.Type == model.AutocompleteArgTypeText {
|
||||
found, changedParsed, changedToBeParsed, suggestion := parseInputTextArgument(arg, parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, suggestion)
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
parsed = changedParsed
|
||||
toBeParsed = changedToBeParsed
|
||||
} else if arg.Type == model.AutocompleteArgTypeStaticList {
|
||||
found, changedParsed, changedToBeParsed, staticListsuggestions := parseStaticListArgument(arg, parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, staticListsuggestions...)
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
parsed = changedParsed
|
||||
toBeParsed = changedToBeParsed
|
||||
} else if arg.Type == model.AutocompleteArgTypeDynamicList {
|
||||
found, changedParsed, changedToBeParsed, dynamicListsuggestions := a.getDynamicListArgument(arg, parsed, toBeParsed)
|
||||
if found {
|
||||
suggestions = append(suggestions, dynamicListsuggestions...)
|
||||
return true, changedParsed, changedToBeParsed, suggestions
|
||||
}
|
||||
parsed = changedParsed
|
||||
toBeParsed = changedToBeParsed
|
||||
}
|
||||
return false, parsed, toBeParsed, suggestions
|
||||
}
|
||||
|
||||
func parseNamedArgument(arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestion model.AutocompleteSuggestion) {
|
||||
in := strings.TrimPrefix(toBeParsed, " ")
|
||||
namedArg := "--" + arg.Name
|
||||
if in == "" { //The user has not started typing the argument.
|
||||
return true, parsed + toBeParsed, "", model.AutocompleteSuggestion{Complete: parsed + toBeParsed + namedArg + " ", Suggestion: namedArg, Hint: "", Description: arg.HelpText}
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(namedArg), strings.ToLower(in)) {
|
||||
return true, parsed + toBeParsed, "", model.AutocompleteSuggestion{Complete: parsed + toBeParsed + namedArg[len(in):] + " ", Suggestion: namedArg, Hint: "", Description: arg.HelpText}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(strings.ToLower(in), strings.ToLower(namedArg)+" ") {
|
||||
return false, parsed + toBeParsed, "", model.AutocompleteSuggestion{}
|
||||
}
|
||||
if strings.ToLower(in) == strings.ToLower(namedArg)+" " {
|
||||
return false, parsed + namedArg + " ", " ", model.AutocompleteSuggestion{}
|
||||
}
|
||||
return false, parsed + namedArg + " ", in[len(namedArg)+1:], model.AutocompleteSuggestion{}
|
||||
}
|
||||
|
||||
func parseInputTextArgument(arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestion model.AutocompleteSuggestion) {
|
||||
in := strings.TrimPrefix(toBeParsed, " ")
|
||||
a := arg.Data.(*model.AutocompleteTextArg)
|
||||
if in == "" { //The user has not started typing the argument.
|
||||
return true, parsed + toBeParsed, "", model.AutocompleteSuggestion{Complete: parsed + toBeParsed, Suggestion: "", Hint: a.Hint, Description: arg.HelpText}
|
||||
}
|
||||
if in[0] == '"' { //input with multiple words
|
||||
indexOfSecondQuote := strings.Index(in[1:], `"`)
|
||||
if indexOfSecondQuote == -1 { //typing of the multiple word argument is not finished
|
||||
return true, parsed + toBeParsed, "", model.AutocompleteSuggestion{Complete: parsed + toBeParsed, Suggestion: "", Hint: a.Hint, Description: arg.HelpText}
|
||||
}
|
||||
// this argument is typed already
|
||||
offset := 2
|
||||
if len(in) > indexOfSecondQuote+2 && in[indexOfSecondQuote+2] == ' ' {
|
||||
offset++
|
||||
}
|
||||
return false, parsed + in[:indexOfSecondQuote+offset], in[indexOfSecondQuote+offset:], model.AutocompleteSuggestion{}
|
||||
}
|
||||
// input with a single word
|
||||
index := strings.Index(in, " ")
|
||||
if index == -1 { // typing of the single word argument is not finished
|
||||
return true, parsed + toBeParsed, "", model.AutocompleteSuggestion{Complete: parsed + toBeParsed, Suggestion: "", Hint: a.Hint, Description: arg.HelpText}
|
||||
}
|
||||
// single word argument already typed
|
||||
return false, parsed + in[:index+1], in[index+1:], model.AutocompleteSuggestion{}
|
||||
}
|
||||
|
||||
func parseStaticListArgument(arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
||||
a := arg.Data.(*model.AutocompleteStaticListArg)
|
||||
return parseListItems(a.PossibleArguments, parsed, toBeParsed)
|
||||
}
|
||||
|
||||
func (a *App) getDynamicListArgument(arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
||||
dynamicArg := arg.Data.(*model.AutocompleteDynamicListArg)
|
||||
params := url.Values{}
|
||||
params.Add("user_input", parsed+toBeParsed)
|
||||
params.Add("parsed", parsed)
|
||||
resp, err := a.doPluginRequest("GET", dynamicArg.FetchURL, params, nil)
|
||||
if err != nil {
|
||||
a.Log().Error("Can't fetch dynamic list arguments for", mlog.String("url", dynamicArg.FetchURL), mlog.Err(err))
|
||||
return false, parsed, toBeParsed, []model.AutocompleteSuggestion{}
|
||||
}
|
||||
listItems := model.AutocompleteStaticListItemsFromJSON(resp.Body)
|
||||
return parseListItems(listItems, parsed, toBeParsed)
|
||||
}
|
||||
|
||||
func parseListItems(items []model.AutocompleteListItem, parsed, toBeParsed string) (bool, string, string, []model.AutocompleteSuggestion) {
|
||||
in := strings.TrimPrefix(toBeParsed, " ")
|
||||
suggestions := []model.AutocompleteSuggestion{}
|
||||
maxPrefix := ""
|
||||
for _, arg := range items {
|
||||
if strings.HasPrefix(strings.ToLower(in), strings.ToLower(arg.Item)+" ") && len(maxPrefix) < len(arg.Item)+1 {
|
||||
maxPrefix = arg.Item + " "
|
||||
}
|
||||
}
|
||||
if maxPrefix != "" { //typing of an argument finished
|
||||
return false, parsed + in[:len(maxPrefix)], in[len(maxPrefix):], []model.AutocompleteSuggestion{}
|
||||
}
|
||||
// user has not finished typing static argument
|
||||
for _, arg := range items {
|
||||
if strings.HasPrefix(strings.ToLower(arg.Item), strings.ToLower(in)) {
|
||||
suggestions = append(suggestions, model.AutocompleteSuggestion{Complete: parsed + arg.Item, Suggestion: arg.Item, Hint: arg.Hint, Description: arg.HelpText})
|
||||
}
|
||||
}
|
||||
return true, parsed + toBeParsed, "", suggestions
|
||||
}
|
||||
609
app/command_autocomplete_test.go
Обычный файл
609
app/command_autocomplete_test.go
Обычный файл
@@ -0,0 +1,609 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseStaticListArgument(t *testing.T) {
|
||||
items := []model.AutocompleteListItem{
|
||||
{
|
||||
Hint: "[hint]",
|
||||
Item: "on",
|
||||
HelpText: "help",
|
||||
},
|
||||
}
|
||||
fixedArgs := &model.AutocompleteStaticListArg{PossibleArguments: items}
|
||||
|
||||
argument := &model.AutocompleteArg{
|
||||
Name: "", //positional
|
||||
HelpText: "some_help",
|
||||
Type: model.AutocompleteArgTypeStaticList,
|
||||
Data: fixedArgs,
|
||||
}
|
||||
found, _, _, suggestions := parseStaticListArgument(argument, "", "") //TODO understand this!
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "on", Suggestion: "on", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "", "o")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "on", Suggestion: "on", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, parsed, toBeParsed, _ := parseStaticListArgument(argument, "", "on ")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "on ", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseStaticListArgument(argument, "", "on some")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "on ", parsed)
|
||||
assert.Equal(t, "some", toBeParsed)
|
||||
|
||||
fixedArgs.PossibleArguments = append(fixedArgs.PossibleArguments,
|
||||
model.AutocompleteListItem{Hint: "[hint]", Item: "off", HelpText: "help"})
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "", "o")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "on", Suggestion: "on", Hint: "[hint]", Description: "help"}, {Complete: "off", Suggestion: "off", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "", "of")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "off", Suggestion: "off", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "", "o some")
|
||||
assert.True(t, found)
|
||||
assert.Len(t, suggestions, 0)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseStaticListArgument(argument, "", "off some")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "off ", parsed)
|
||||
assert.Equal(t, "some", toBeParsed)
|
||||
|
||||
fixedArgs.PossibleArguments = append(fixedArgs.PossibleArguments,
|
||||
model.AutocompleteListItem{Hint: "[hint]", Item: "onon", HelpText: "help"})
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "", "on")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "on", Suggestion: "on", Hint: "[hint]", Description: "help"}, {Complete: "onon", Suggestion: "onon", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, _, _, suggestions = parseStaticListArgument(argument, "bla ", "ono")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, []model.AutocompleteSuggestion{{Complete: "bla onon", Suggestion: "onon", Hint: "[hint]", Description: "help"}}, suggestions)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseStaticListArgument(argument, "", "on some")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "on ", parsed)
|
||||
assert.Equal(t, "some", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseStaticListArgument(argument, "", "onon some")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "onon ", parsed)
|
||||
assert.Equal(t, "some", toBeParsed)
|
||||
}
|
||||
|
||||
func TestParseInputTextArgument(t *testing.T) {
|
||||
argument := &model.AutocompleteArg{
|
||||
Name: "", //positional
|
||||
HelpText: "some_help",
|
||||
Type: model.AutocompleteArgTypeText,
|
||||
Data: &model.AutocompleteTextArg{Hint: "hint", Pattern: "pat"},
|
||||
}
|
||||
|
||||
found, _, _, suggestion := parseInputTextArgument(argument, "", "")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "", Suggestion: "", Hint: "hint", Description: "some_help"}, suggestion)
|
||||
|
||||
found, _, _, suggestion = parseInputTextArgument(argument, "", " ")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: " ", Suggestion: "", Hint: "hint", Description: "some_help"}, suggestion)
|
||||
|
||||
found, _, _, suggestion = parseInputTextArgument(argument, "", "abc")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "abc", Suggestion: "", Hint: "hint", Description: "some_help"}, suggestion)
|
||||
|
||||
found, _, _, suggestion = parseInputTextArgument(argument, "", "\"abc dfd df ")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "\"abc dfd df ", Suggestion: "", Hint: "hint", Description: "some_help"}, suggestion)
|
||||
|
||||
found, parsed, toBeParsed, _ := parseInputTextArgument(argument, "", "abc efg ")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "abc ", parsed)
|
||||
assert.Equal(t, "efg ", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseInputTextArgument(argument, "", "abc ")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "abc ", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseInputTextArgument(argument, "", "\"abc def\" abc")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "\"abc def\" ", parsed)
|
||||
assert.Equal(t, "abc", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseInputTextArgument(argument, "", "\"abc def\"")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "\"abc def\"", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
}
|
||||
|
||||
func TestParseNamedArguments(t *testing.T) {
|
||||
argument := &model.AutocompleteArg{
|
||||
Name: "name", //named
|
||||
HelpText: "some_help",
|
||||
Type: model.AutocompleteArgTypeText,
|
||||
Data: &model.AutocompleteTextArg{Hint: "hint", Pattern: "pat"},
|
||||
}
|
||||
|
||||
found, _, _, suggestion := parseNamedArgument(argument, "", "")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "--name ", Suggestion: "--name", Hint: "", Description: "some_help"}, suggestion)
|
||||
|
||||
found, _, _, suggestion = parseNamedArgument(argument, "", " ")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: " --name ", Suggestion: "--name", Hint: "", Description: "some_help"}, suggestion)
|
||||
|
||||
found, parsed, toBeParsed, _ := parseNamedArgument(argument, "", "abc")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "abc", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, suggestion = parseNamedArgument(argument, "", "-")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, "-", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "--name ", Suggestion: "--name", Hint: "", Description: "some_help"}, suggestion)
|
||||
|
||||
found, parsed, toBeParsed, suggestion = parseNamedArgument(argument, "", " -")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, " -", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: " --name ", Suggestion: "--name", Hint: "", Description: "some_help"}, suggestion)
|
||||
|
||||
found, parsed, toBeParsed, suggestion = parseNamedArgument(argument, "", "--name")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, "--name", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
assert.Equal(t, model.AutocompleteSuggestion{Complete: "--name ", Suggestion: "--name", Hint: "", Description: "some_help"}, suggestion)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseNamedArgument(argument, "", "--name bla")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "--name ", parsed)
|
||||
assert.Equal(t, "bla", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseNamedArgument(argument, "", "--name bla gla")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "--name ", parsed)
|
||||
assert.Equal(t, "bla gla", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseNamedArgument(argument, "", "--name \"bla gla\"")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "--name ", parsed)
|
||||
assert.Equal(t, "\"bla gla\"", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseNamedArgument(argument, "", "--name \"bla gla\" ")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "--name ", parsed)
|
||||
assert.Equal(t, "\"bla gla\" ", toBeParsed)
|
||||
|
||||
found, parsed, toBeParsed, _ = parseNamedArgument(argument, "", "bla")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "bla", parsed)
|
||||
assert.Equal(t, "", toBeParsed)
|
||||
|
||||
}
|
||||
|
||||
func TestSuggestions(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
jira := createJiraAutocompleteData()
|
||||
|
||||
suggestions := th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "ji", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, jira.Trigger, suggestions[0].Complete)
|
||||
assert.Equal(t, jira.Trigger, suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[command]", suggestions[0].Hint)
|
||||
assert.Equal(t, jira.HelpText, suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira crea", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira create", suggestions[0].Complete)
|
||||
assert.Equal(t, "create", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[issue text]", suggestions[0].Hint)
|
||||
assert.Equal(t, "Create a new Issue", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira c", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "jira create", suggestions[1].Complete)
|
||||
assert.Equal(t, "create", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "[issue text]", suggestions[1].Hint)
|
||||
assert.Equal(t, "Create a new Issue", suggestions[1].Description)
|
||||
assert.Equal(t, "jira connect", suggestions[0].Complete)
|
||||
assert.Equal(t, "connect", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[url]", suggestions[0].Hint)
|
||||
assert.Equal(t, "Connect your Mattermost account to your Jira account", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira create ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira create ", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[text]", suggestions[0].Hint)
|
||||
assert.Equal(t, "This text is optional, will be inserted into the description field", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira create some", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira create some", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[text]", suggestions[0].Hint)
|
||||
assert.Equal(t, "This text is optional, will be inserted into the description field", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira create some text ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 0)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "invalid command", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 0)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira settings notifications o", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "jira settings notifications On", suggestions[0].Complete)
|
||||
assert.Equal(t, "On", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "Turn notifications on", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "jira settings notifications Off", suggestions[1].Complete)
|
||||
assert.Equal(t, "Off", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "Turn notifications off", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 11)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira ", model.SYSTEM_USER_ROLE_ID)
|
||||
assert.Len(t, suggestions, 9)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira create \"some issue text", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira create \"some issue text", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[text]", suggestions[0].Hint)
|
||||
assert.Equal(t, "This text is optional, will be inserted into the description field", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira timezone ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira timezone --zone ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--zone", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "Set timezone", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira timezone --", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira timezone --zone ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--zone", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "Set timezone", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira timezone --zone ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira timezone --zone ", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[UTC+07:00]", suggestions[0].Hint)
|
||||
assert.Equal(t, "Set timezone", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira timezone --zone bla", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "jira timezone --zone bla", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "[UTC+07:00]", suggestions[0].Hint)
|
||||
assert.Equal(t, "Set timezone", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{jira}, "", "jira timezone bla", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 0)
|
||||
|
||||
commandA := &model.Command{
|
||||
Trigger: "alice",
|
||||
AutocompleteData: model.NewAutocompleteData("alice", "", ""),
|
||||
}
|
||||
commandB := &model.Command{
|
||||
Trigger: "bob",
|
||||
AutocompleteData: model.NewAutocompleteData("bob", "", ""),
|
||||
}
|
||||
commandC := &model.Command{
|
||||
Trigger: "charles",
|
||||
AutocompleteData: model.NewAutocompleteData("charles", "", ""),
|
||||
}
|
||||
suggestions = th.App.GetSuggestions([]*model.Command{commandB, commandC, commandA}, "", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 3)
|
||||
assert.Equal(t, "alice", suggestions[0].Complete)
|
||||
assert.Equal(t, "bob", suggestions[1].Complete)
|
||||
assert.Equal(t, "charles", suggestions[2].Complete)
|
||||
}
|
||||
|
||||
func TestCommandWithOptionalArgs(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
command := createCommandWithOptionalArgs()
|
||||
|
||||
suggestions := th.App.getSuggestions([]*model.AutocompleteData{command}, "", "comm", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, command.Trigger, suggestions[0].Complete)
|
||||
assert.Equal(t, command.Trigger, suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, command.HelpText, suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 4)
|
||||
assert.Equal(t, "command subcommand1", suggestions[0].Complete)
|
||||
assert.Equal(t, "subcommand1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand2", suggestions[1].Complete)
|
||||
assert.Equal(t, "subcommand2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
assert.Equal(t, "command subcommand3", suggestions[2].Complete)
|
||||
assert.Equal(t, "subcommand3", suggestions[2].Suggestion)
|
||||
assert.Equal(t, "", suggestions[2].Hint)
|
||||
assert.Equal(t, "", suggestions[2].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "command subcommand1 item1", suggestions[0].Complete)
|
||||
assert.Equal(t, "item1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand1 item2", suggestions[1].Complete)
|
||||
assert.Equal(t, "item2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand1 item1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "command subcommand1 item1 --name2 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--name2", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand1 item1 --name2 bla", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "command subcommand1 item1 --name2 bla", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "command subcommand2 --name1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--name1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg1", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand2 ", suggestions[1].Complete)
|
||||
assert.Equal(t, "", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 -", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "command subcommand2 --name1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--name1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg1", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand2 -", suggestions[1].Complete)
|
||||
assert.Equal(t, "", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 --name1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 3)
|
||||
assert.Equal(t, "command subcommand2 --name1 item1", suggestions[0].Complete)
|
||||
assert.Equal(t, "item1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand2 --name1 item2", suggestions[1].Complete)
|
||||
assert.Equal(t, "item2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
assert.Equal(t, "command subcommand2 --name1 ", suggestions[2].Complete)
|
||||
assert.Equal(t, "", suggestions[2].Suggestion)
|
||||
assert.Equal(t, "", suggestions[2].Hint)
|
||||
assert.Equal(t, "arg3", suggestions[2].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 --name1 item", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 3)
|
||||
assert.Equal(t, "command subcommand2 --name1 item1", suggestions[0].Complete)
|
||||
assert.Equal(t, "item1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand2 --name1 item2", suggestions[1].Complete)
|
||||
assert.Equal(t, "item2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
assert.Equal(t, "command subcommand2 --name1 item", suggestions[2].Complete)
|
||||
assert.Equal(t, "", suggestions[2].Suggestion)
|
||||
assert.Equal(t, "", suggestions[2].Hint)
|
||||
assert.Equal(t, "arg3", suggestions[2].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 --name1 item1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "command subcommand2 --name1 item1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 --name1 item1 bla ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "command subcommand2 --name1 item1 bla ", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg3", suggestions[0].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand2 --name1 item1 bla bla ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 0)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand3 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 3)
|
||||
assert.Equal(t, "command subcommand3 --name1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--name1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg1", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand3 --name2 ", suggestions[1].Complete)
|
||||
assert.Equal(t, "--name2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[1].Description)
|
||||
assert.Equal(t, "command subcommand3 --name3 ", suggestions[2].Complete)
|
||||
assert.Equal(t, "--name3", suggestions[2].Suggestion)
|
||||
assert.Equal(t, "", suggestions[2].Hint)
|
||||
assert.Equal(t, "arg3", suggestions[2].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand3 --name", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 3)
|
||||
assert.Equal(t, "command subcommand3 --name1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "--name1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "arg1", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand3 --name2 ", suggestions[1].Complete)
|
||||
assert.Equal(t, "--name2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "arg2", suggestions[1].Description)
|
||||
assert.Equal(t, "command subcommand3 --name3 ", suggestions[2].Complete)
|
||||
assert.Equal(t, "--name3", suggestions[2].Suggestion)
|
||||
assert.Equal(t, "", suggestions[2].Hint)
|
||||
assert.Equal(t, "arg3", suggestions[2].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand3 --name1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "command subcommand3 --name1 item1", suggestions[0].Complete)
|
||||
assert.Equal(t, "item1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "", suggestions[0].Hint)
|
||||
assert.Equal(t, "", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand3 --name1 item2", suggestions[1].Complete)
|
||||
assert.Equal(t, "item2", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "", suggestions[1].Hint)
|
||||
assert.Equal(t, "", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand4 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 2)
|
||||
assert.Equal(t, "command subcommand4 item1", suggestions[0].Complete)
|
||||
assert.Equal(t, "item1", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "(optional)", suggestions[0].Hint)
|
||||
assert.Equal(t, "help3", suggestions[0].Description)
|
||||
assert.Equal(t, "command subcommand4 ", suggestions[1].Complete)
|
||||
assert.Equal(t, "", suggestions[1].Suggestion)
|
||||
assert.Equal(t, "message", suggestions[1].Hint)
|
||||
assert.Equal(t, "help4", suggestions[1].Description)
|
||||
|
||||
suggestions = th.App.getSuggestions([]*model.AutocompleteData{command}, "", "command subcommand4 item1 ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||
assert.Len(t, suggestions, 1)
|
||||
assert.Equal(t, "command subcommand4 item1 ", suggestions[0].Complete)
|
||||
assert.Equal(t, "", suggestions[0].Suggestion)
|
||||
assert.Equal(t, "message", suggestions[0].Hint)
|
||||
assert.Equal(t, "help4", suggestions[0].Description)
|
||||
}
|
||||
|
||||
func createCommandWithOptionalArgs() *model.AutocompleteData {
|
||||
command := model.NewAutocompleteData("command", "", "")
|
||||
subCommand1 := model.NewAutocompleteData("subcommand1", "", "")
|
||||
subCommand1.AddStaticListArgument("arg1", true, []model.AutocompleteListItem{{Item: "item1"}, {Item: "item2"}})
|
||||
subCommand1.AddNamedTextArgument("name2", "arg2", "", "", false)
|
||||
command.AddCommand(subCommand1)
|
||||
subCommand2 := model.NewAutocompleteData("subcommand2", "", "")
|
||||
subCommand2.AddNamedStaticListArgument("name1", "arg1", false, []model.AutocompleteListItem{{Item: "item1"}, {Item: "item2"}})
|
||||
subCommand2.AddTextArgument("arg2", "", "")
|
||||
subCommand2.AddTextArgument("arg3", "", "")
|
||||
command.AddCommand(subCommand2)
|
||||
subCommand3 := model.NewAutocompleteData("subcommand3", "", "")
|
||||
subCommand3.AddNamedStaticListArgument("name1", "arg1", false, []model.AutocompleteListItem{{Item: "item1"}, {Item: "item2"}})
|
||||
subCommand3.AddNamedTextArgument("name2", "arg2", "", "", false)
|
||||
subCommand3.AddNamedTextArgument("name3", "arg3", "", "", false)
|
||||
command.AddCommand(subCommand3)
|
||||
subcommand4 := model.NewAutocompleteData("subcommand4", "", "help1")
|
||||
subcommand4.AddStaticListArgument("help2", false, []model.AutocompleteListItem{{
|
||||
HelpText: "help3",
|
||||
Hint: "(optional)",
|
||||
Item: "item1",
|
||||
}})
|
||||
subcommand4.AddTextArgument("help4", "message", "")
|
||||
command.AddCommand(subcommand4)
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// createJiraAutocompleteData will create autocomplete data for jira plugin. For testing purposes only.
|
||||
func createJiraAutocompleteData() *model.AutocompleteData {
|
||||
jira := model.NewAutocompleteData("jira", "[command]", "Available commands: connect, assign, disconnect, create, transition, view, subscribe, settings, install cloud/server, uninstall cloud/server, help")
|
||||
|
||||
connect := model.NewAutocompleteData("connect", "[url]", "Connect your Mattermost account to your Jira account")
|
||||
jira.AddCommand(connect)
|
||||
|
||||
disconnect := model.NewAutocompleteData("disconnect", "", "Disconnect your Mattermost account from your Jira account")
|
||||
jira.AddCommand(disconnect)
|
||||
|
||||
assign := model.NewAutocompleteData("assign", "[issue]", "Change the assignee of a Jira issue")
|
||||
assign.AddDynamicListArgument("List of issues is downloading from your Jira account", "/url/issue-key", true)
|
||||
assign.AddDynamicListArgument("List of assignees is downloading from your Jira account", "/url/assignee", true)
|
||||
jira.AddCommand(assign)
|
||||
|
||||
create := model.NewAutocompleteData("create", "[issue text]", "Create a new Issue")
|
||||
create.AddTextArgument("This text is optional, will be inserted into the description field", "[text]", "")
|
||||
jira.AddCommand(create)
|
||||
|
||||
transition := model.NewAutocompleteData("transition", "[issue]", "Change the state of a Jira issue")
|
||||
assign.AddDynamicListArgument("List of issues is downloading from your Jira account", "/url/issue-key", true)
|
||||
assign.AddDynamicListArgument("List of states is downloading from your Jira account", "/url/states", true)
|
||||
jira.AddCommand(transition)
|
||||
|
||||
subscribe := model.NewAutocompleteData("subscribe", "", "Configure the Jira notifications sent to this channel")
|
||||
jira.AddCommand(subscribe)
|
||||
|
||||
view := model.NewAutocompleteData("view", "[issue]", "View the details of a specific Jira issue")
|
||||
assign.AddDynamicListArgument("List of issues is downloading from your Jira account", "/url/issue-key", true)
|
||||
jira.AddCommand(view)
|
||||
|
||||
settings := model.NewAutocompleteData("settings", "", "Update your user settings")
|
||||
notifications := model.NewAutocompleteData("notifications", "[on/off]", "Turn notifications on or off")
|
||||
|
||||
items := []model.AutocompleteListItem{
|
||||
{
|
||||
Hint: "Turn notifications on",
|
||||
Item: "On",
|
||||
},
|
||||
{
|
||||
Hint: "Turn notifications off",
|
||||
Item: "Off",
|
||||
},
|
||||
}
|
||||
notifications.AddStaticListArgument("Turn notifications on or off", true, items)
|
||||
settings.AddCommand(notifications)
|
||||
jira.AddCommand(settings)
|
||||
|
||||
timezone := model.NewAutocompleteData("timezone", "", "Update your timezone")
|
||||
timezone.AddNamedTextArgument("zone", "Set timezone", "[UTC+07:00]", "", true)
|
||||
jira.AddCommand(timezone)
|
||||
|
||||
install := model.NewAutocompleteData("install", "", "Connect Mattermost to a Jira instance")
|
||||
install.RoleID = model.SYSTEM_ADMIN_ROLE_ID
|
||||
cloud := model.NewAutocompleteData("cloud", "", "Connect to a Jira Cloud instance")
|
||||
urlPattern := "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)"
|
||||
cloud.AddTextArgument("input URL of the Jira Cloud instance", "[URL]", urlPattern)
|
||||
install.AddCommand(cloud)
|
||||
server := model.NewAutocompleteData("server", "", "Connect to a Jira Server or Data Center instance")
|
||||
server.AddTextArgument("input URL of the Jira Server or Data Center instance", "[URL]", urlPattern)
|
||||
install.AddCommand(server)
|
||||
jira.AddCommand(install)
|
||||
|
||||
uninstall := model.NewAutocompleteData("uninstall", "", "Disconnect Mattermost from a Jira instance")
|
||||
uninstall.RoleID = model.SYSTEM_ADMIN_ROLE_ID
|
||||
cloud = model.NewAutocompleteData("cloud", "", "Disconnect from a Jira Cloud instance")
|
||||
cloud.AddTextArgument("input URL of the Jira Cloud instance", "[URL]", urlPattern)
|
||||
uninstall.AddCommand(cloud)
|
||||
server = model.NewAutocompleteData("server", "", "Disconnect from a Jira Server or Data Center instance")
|
||||
server.AddTextArgument("input URL of the Jira Server or Data Center instance", "[URL]", urlPattern)
|
||||
uninstall.AddCommand(server)
|
||||
jira.AddCommand(uninstall)
|
||||
|
||||
return jira
|
||||
}
|
||||
@@ -25,12 +25,15 @@ func (me *RenameProvider) GetTrigger() string {
|
||||
}
|
||||
|
||||
func (me *RenameProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
|
||||
renameAutocompleteData := model.NewAutocompleteData(CMD_RENAME, T("api.command_channel_rename.hint"), T("api.command_channel_rename.desc"))
|
||||
renameAutocompleteData.AddTextArgument(T("api.command_channel_rename.hint"), "[text]", "")
|
||||
return &model.Command{
|
||||
Trigger: CMD_RENAME,
|
||||
AutoComplete: true,
|
||||
AutoCompleteDesc: T("api.command_channel_rename.desc"),
|
||||
AutoCompleteHint: T("api.command_channel_rename.hint"),
|
||||
DisplayName: T("api.command_channel_rename.name"),
|
||||
AutocompleteData: renameAutocompleteData,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -325,27 +325,34 @@ func (w *LocalResponseWriter) WriteHeader(statusCode int) {
|
||||
w.status = statusCode
|
||||
}
|
||||
|
||||
func (a *App) DoLocalRequest(rawURL string, body []byte) (*http.Response, *model.AppError) {
|
||||
func (a *App) doPluginRequest(method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) {
|
||||
rawURL = strings.TrimPrefix(rawURL, "/")
|
||||
inURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DoActionRequest", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
|
||||
return nil, model.NewAppError("doPluginRequest", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
result := strings.Split(inURL.Path, "/")
|
||||
if len(result) < 2 {
|
||||
return nil, model.NewAppError("DoActionRequest", "api.post.do_action.action_integration.app_error", nil, "err=Unable to find pluginId", http.StatusBadRequest)
|
||||
return nil, model.NewAppError("doPluginRequest", "api.post.do_action.action_integration.app_error", nil, "err=Unable to find pluginId", http.StatusBadRequest)
|
||||
}
|
||||
if result[0] != "plugins" {
|
||||
return nil, model.NewAppError("DoActionRequest", "api.post.do_action.action_integration.app_error", nil, "err=plugins not in path", http.StatusBadRequest)
|
||||
return nil, model.NewAppError("doPluginRequest", "api.post.do_action.action_integration.app_error", nil, "err=plugins not in path", http.StatusBadRequest)
|
||||
}
|
||||
pluginId := result[1]
|
||||
|
||||
path := strings.TrimPrefix(inURL.Path, "plugins/"+pluginId)
|
||||
|
||||
w := &LocalResponseWriter{}
|
||||
r, err := http.NewRequest("POST", path, bytes.NewReader(body))
|
||||
base, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DoActionRequest", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
|
||||
return nil, model.NewAppError("doPluginRequest", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
if values != nil {
|
||||
base.RawQuery = values.Encode()
|
||||
}
|
||||
w := &LocalResponseWriter{}
|
||||
r, err := http.NewRequest(method, base.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("doPluginRequest", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
r.Header.Set("Mattermost-User-Id", a.Session().UserId)
|
||||
r.Header.Set(model.HEADER_AUTH, "Bearer "+a.Session().Token)
|
||||
@@ -370,6 +377,10 @@ func (a *App) DoLocalRequest(rawURL string, body []byte) (*http.Response, *model
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *App) DoLocalRequest(rawURL string, body []byte) (*http.Response, *model.AppError) {
|
||||
return a.doPluginRequest("POST", rawURL, nil, body)
|
||||
}
|
||||
|
||||
func (a *App) OpenInteractiveDialog(request model.OpenDialogRequest) *model.AppError {
|
||||
clientTriggerId, userId, err := request.DecodeAndVerifyTriggerId(a.AsymmetricSigningKey())
|
||||
if err != nil {
|
||||
|
||||
@@ -7647,6 +7647,23 @@ func (a *OpenTracingAppLayer) GetStatusesByIds(userIds []string) (map[string]int
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetSuggestions(commands []*model.Command, userInput string, roleID string) []model.AutocompleteSuggestion {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSuggestions")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.GetSuggestions(commands, userInput, roleID)
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetTeam(teamId string) (*model.Team, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTeam")
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PluginCommand struct {
|
||||
@@ -18,16 +19,36 @@ type PluginCommand struct {
|
||||
|
||||
func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) error {
|
||||
if command.Trigger == "" {
|
||||
return fmt.Errorf("invalid command")
|
||||
return errors.New("invalid command")
|
||||
}
|
||||
if command.AutocompleteData != nil {
|
||||
if err := command.AutocompleteData.IsValid(); err != nil {
|
||||
return errors.Wrap(err, "invalid autocomplete data in command")
|
||||
}
|
||||
}
|
||||
|
||||
if command.AutocompleteData == nil {
|
||||
command.AutocompleteData = model.NewAutocompleteData(command.Trigger, command.AutoCompleteHint, command.AutoCompleteDesc)
|
||||
} else {
|
||||
baseURL, err := url.Parse("/plugins/" + pluginId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Can't parse url %s", "/plugins/"+pluginId)
|
||||
}
|
||||
err = command.AutocompleteData.UpdateRelativeURLsForPluginCommands(baseURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Can't update relative urls for plugin commands")
|
||||
}
|
||||
}
|
||||
|
||||
command = &model.Command{
|
||||
Trigger: strings.ToLower(command.Trigger),
|
||||
TeamId: command.TeamId,
|
||||
AutoComplete: command.AutoComplete,
|
||||
AutoCompleteDesc: command.AutoCompleteDesc,
|
||||
AutoCompleteHint: command.AutoCompleteHint,
|
||||
DisplayName: command.DisplayName,
|
||||
Trigger: strings.ToLower(command.Trigger),
|
||||
TeamId: command.TeamId,
|
||||
AutoComplete: command.AutoComplete,
|
||||
AutoCompleteDesc: command.AutoCompleteDesc,
|
||||
AutoCompleteHint: command.AutoCompleteHint,
|
||||
DisplayName: command.DisplayName,
|
||||
AutocompleteData: command.AutocompleteData,
|
||||
AutocompleteIconData: command.AutocompleteIconData,
|
||||
}
|
||||
|
||||
a.Srv().pluginCommandsLock.Lock()
|
||||
|
||||
@@ -47,7 +47,7 @@ func structToMap(t interface{}) map[string]interface{} {
|
||||
|
||||
if indirectType.Kind() == reflect.Struct {
|
||||
value = structToMap(indirectType.Interface())
|
||||
} else {
|
||||
} else if indirectType.Kind() != reflect.Invalid {
|
||||
value = indirectType.Interface()
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -4762,6 +4762,10 @@
|
||||
"id": "model.cluster.is_valid.type.app_error",
|
||||
"translation": "Type must be set."
|
||||
},
|
||||
{
|
||||
"id": "model.command.is_valid.autocomplete_data.app_error",
|
||||
"translation": "Invalid AutocompleteData"
|
||||
},
|
||||
{
|
||||
"id": "model.command.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time."
|
||||
|
||||
@@ -4157,6 +4157,17 @@ func (c *Client4) ListCommands(teamId string, customOnly bool) ([]*Command, *Res
|
||||
return CommandListFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// ListCommandAutocompleteSuggestions will retrieve a list of suggestions for a userInput.
|
||||
func (c *Client4) ListCommandAutocompleteSuggestions(userInput, teamId string) ([]AutocompleteSuggestion, *Response) {
|
||||
query := fmt.Sprintf("/commands/autocomplete_suggestions?user_input=%v", userInput)
|
||||
r, err := c.DoApiGet(c.GetTeamRoute(teamId)+query, "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return AutocompleteSuggestionsFromJSON(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetCommandById will retrieve a command by id.
|
||||
func (c *Client4) GetCommandById(cmdId string) (*Command, *Response) {
|
||||
url := fmt.Sprintf("%s/%s", c.GetCommandsRoute(), cmdId)
|
||||
|
||||
@@ -18,23 +18,26 @@ const (
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Id string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
CreatorId string `json:"creator_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
Trigger string `json:"trigger"`
|
||||
Method string `json:"method"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
AutoComplete bool `json:"auto_complete"`
|
||||
AutoCompleteDesc string `json:"auto_complete_desc"`
|
||||
AutoCompleteHint string `json:"auto_complete_hint"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Description string `json:"description"`
|
||||
URL string `json:"url"`
|
||||
Id string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
CreatorId string `json:"creator_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
Trigger string `json:"trigger"`
|
||||
Method string `json:"method"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
AutoComplete bool `json:"auto_complete"`
|
||||
AutoCompleteDesc string `json:"auto_complete_desc"`
|
||||
AutoCompleteHint string `json:"auto_complete_hint"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Description string `json:"description"`
|
||||
URL string `json:"url"`
|
||||
AutocompleteData *AutocompleteData `db:"-" json:"autocomplete_data,omitempty"`
|
||||
// AutocompleteIconData is a base64 encoded svg
|
||||
AutocompleteIconData string `db:"-" json:"autocomplete_icon_data,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Command) ToJson() string {
|
||||
@@ -109,6 +112,12 @@ func (o *Command) IsValid() *AppError {
|
||||
return NewAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.AutocompleteData != nil {
|
||||
if err := o.AutocompleteData.IsValid(); err != nil {
|
||||
return NewAppError("Command.IsValid", "model.command.is_valid.autocomplete_data.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
455
model/command_autocomplete.go
Обычный файл
455
model/command_autocomplete.go
Обычный файл
@@ -0,0 +1,455 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AutocompleteArgType describes autocomplete argument type
|
||||
type AutocompleteArgType string
|
||||
|
||||
// Argument types
|
||||
const (
|
||||
AutocompleteArgTypeText AutocompleteArgType = "TextInput"
|
||||
AutocompleteArgTypeStaticList AutocompleteArgType = "StaticList"
|
||||
AutocompleteArgTypeDynamicList AutocompleteArgType = "DynamicList"
|
||||
)
|
||||
|
||||
// AutocompleteData describes slash command autocomplete information.
|
||||
type AutocompleteData struct {
|
||||
// Trigger of the command
|
||||
Trigger string
|
||||
// Hint of a command
|
||||
Hint string
|
||||
// Text displayed to the user to help with the autocomplete description
|
||||
HelpText string
|
||||
// Role of the user who should be able to see the autocomplete info of this command
|
||||
RoleID string
|
||||
// Arguments of the command. Arguments can be named or positional.
|
||||
// If they are positional order in the list matters, if they are named order does not matter.
|
||||
// All arguments should be either named or positional, no mixing allowed.
|
||||
Arguments []*AutocompleteArg
|
||||
// Subcommands of the command
|
||||
SubCommands []*AutocompleteData
|
||||
}
|
||||
|
||||
// AutocompleteArg describes an argument of the command. Arguments can be named or positional.
|
||||
// If Name is empty string Argument is positional otherwise it is named argument.
|
||||
// Named arguments are passed as --Name Argument_Value.
|
||||
type AutocompleteArg struct {
|
||||
// Name of the argument
|
||||
Name string
|
||||
// Text displayed to the user to help with the autocomplete
|
||||
HelpText string
|
||||
// Type of the argument
|
||||
Type AutocompleteArgType
|
||||
// Required determins if argument is optional or not.
|
||||
Required bool
|
||||
// Actual data of the argument (depends on the Type)
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// AutocompleteTextArg describes text user can input as an argument.
|
||||
type AutocompleteTextArg struct {
|
||||
// Hint of the input text
|
||||
Hint string
|
||||
// Regex pattern to match
|
||||
Pattern string
|
||||
}
|
||||
|
||||
// AutocompleteListItem describes an item in the AutocompleteStaticListArg.
|
||||
type AutocompleteListItem struct {
|
||||
Item string
|
||||
Hint string
|
||||
HelpText string
|
||||
}
|
||||
|
||||
// AutocompleteStaticListArg is used to input one of the arguments from the list,
|
||||
// for example [yes, no], [on, off], and so on.
|
||||
type AutocompleteStaticListArg struct {
|
||||
PossibleArguments []AutocompleteListItem
|
||||
}
|
||||
|
||||
// AutocompleteDynamicListArg is used when user wants to download possible argument list from the URL.
|
||||
type AutocompleteDynamicListArg struct {
|
||||
FetchURL string
|
||||
}
|
||||
|
||||
// AutocompleteSuggestion describes a single suggestion item sent to the front-end
|
||||
// Example: for user input `/jira cre` -
|
||||
// Complete might be `/jira create`
|
||||
// Suggestion might be `create`,
|
||||
// Hint might be `[issue text]`,
|
||||
// Description might be `Create a new Issue`
|
||||
type AutocompleteSuggestion struct {
|
||||
// Complete describes completed suggestion
|
||||
Complete string
|
||||
// Suggestion describes what user might want to input next
|
||||
Suggestion string
|
||||
// Hint describes a hint about the suggested input
|
||||
Hint string
|
||||
// Description of the command or a suggestion
|
||||
Description string
|
||||
// IconData is base64 encoded svg image
|
||||
IconData string
|
||||
}
|
||||
|
||||
// NewAutocompleteData returns new Autocomplete data.
|
||||
func NewAutocompleteData(trigger, hint, helpText string) *AutocompleteData {
|
||||
return &AutocompleteData{
|
||||
Trigger: trigger,
|
||||
Hint: hint,
|
||||
HelpText: helpText,
|
||||
RoleID: SYSTEM_USER_ROLE_ID,
|
||||
Arguments: []*AutocompleteArg{},
|
||||
SubCommands: []*AutocompleteData{},
|
||||
}
|
||||
}
|
||||
|
||||
// AddCommand adds a subcommand to the autocomplete data.
|
||||
func (ad *AutocompleteData) AddCommand(command *AutocompleteData) {
|
||||
ad.SubCommands = append(ad.SubCommands, command)
|
||||
}
|
||||
|
||||
// AddTextArgument adds positional AutocompleteArgTypeText argument to the command.
|
||||
func (ad *AutocompleteData) AddTextArgument(helpText, hint, pattern string) {
|
||||
ad.AddNamedTextArgument("", helpText, hint, pattern, true)
|
||||
}
|
||||
|
||||
// AddNamedTextArgument adds named AutocompleteArgTypeText argument to the command.
|
||||
func (ad *AutocompleteData) AddNamedTextArgument(name, helpText, hint, pattern string, required bool) {
|
||||
argument := AutocompleteArg{
|
||||
Name: name,
|
||||
HelpText: helpText,
|
||||
Type: AutocompleteArgTypeText,
|
||||
Required: required,
|
||||
Data: &AutocompleteTextArg{Hint: hint, Pattern: pattern},
|
||||
}
|
||||
ad.Arguments = append(ad.Arguments, &argument)
|
||||
}
|
||||
|
||||
// AddStaticListArgument adds positional AutocompleteArgTypeStaticList argument to the command.
|
||||
func (ad *AutocompleteData) AddStaticListArgument(helpText string, required bool, items []AutocompleteListItem) {
|
||||
ad.AddNamedStaticListArgument("", helpText, required, items)
|
||||
}
|
||||
|
||||
// AddNamedStaticListArgument adds named AutocompleteArgTypeStaticList argument to the command.
|
||||
func (ad *AutocompleteData) AddNamedStaticListArgument(name, helpText string, required bool, items []AutocompleteListItem) {
|
||||
argument := AutocompleteArg{
|
||||
Name: name,
|
||||
HelpText: helpText,
|
||||
Type: AutocompleteArgTypeStaticList,
|
||||
Required: required,
|
||||
Data: &AutocompleteStaticListArg{PossibleArguments: items},
|
||||
}
|
||||
ad.Arguments = append(ad.Arguments, &argument)
|
||||
}
|
||||
|
||||
// AddDynamicListArgument adds positional AutocompleteArgTypeDynamicList argument to the command.
|
||||
func (ad *AutocompleteData) AddDynamicListArgument(helpText, url string, required bool) {
|
||||
ad.AddNamedDynamicListArgument("", helpText, url, required)
|
||||
}
|
||||
|
||||
// AddNamedDynamicListArgument adds named AutocompleteArgTypeDynamicList argument to the command.
|
||||
func (ad *AutocompleteData) AddNamedDynamicListArgument(name, helpText, url string, required bool) {
|
||||
argument := AutocompleteArg{
|
||||
Name: name,
|
||||
HelpText: helpText,
|
||||
Type: AutocompleteArgTypeDynamicList,
|
||||
Required: required,
|
||||
Data: &AutocompleteDynamicListArg{FetchURL: url},
|
||||
}
|
||||
ad.Arguments = append(ad.Arguments, &argument)
|
||||
}
|
||||
|
||||
// Equals method checks if command is the same.
|
||||
func (ad *AutocompleteData) Equals(command *AutocompleteData) bool {
|
||||
if !(ad.Trigger == command.Trigger && ad.HelpText == command.HelpText && ad.RoleID == command.RoleID && ad.Hint == command.Hint) {
|
||||
return false
|
||||
}
|
||||
if len(ad.Arguments) != len(command.Arguments) || len(ad.SubCommands) != len(command.SubCommands) {
|
||||
return false
|
||||
}
|
||||
for i := range ad.Arguments {
|
||||
if !ad.Arguments[i].Equals(command.Arguments[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i := range ad.SubCommands {
|
||||
if !ad.SubCommands[i].Equals(command.SubCommands[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateRelativeURLsForPluginCommands method updates relative urls for plugin commands
|
||||
func (ad *AutocompleteData) UpdateRelativeURLsForPluginCommands(baseURL *url.URL) error {
|
||||
for _, arg := range ad.Arguments {
|
||||
if arg.Type != AutocompleteArgTypeDynamicList {
|
||||
continue
|
||||
}
|
||||
dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
|
||||
if !ok {
|
||||
return errors.New("Not a proper DynamicList type argument")
|
||||
}
|
||||
dynamicListURL, err := url.Parse(dynamicList.FetchURL)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "FetchURL is not a proper url")
|
||||
}
|
||||
if !dynamicListURL.IsAbs() {
|
||||
absURL := &url.URL{}
|
||||
*absURL = *baseURL
|
||||
absURL.Path = path.Join(absURL.Path, dynamicList.FetchURL)
|
||||
dynamicList.FetchURL = absURL.String()
|
||||
}
|
||||
|
||||
}
|
||||
for _, command := range ad.SubCommands {
|
||||
err := command.UpdateRelativeURLsForPluginCommands(baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid method checks if autocomplete data is valid.
|
||||
func (ad *AutocompleteData) IsValid() error {
|
||||
if ad == nil {
|
||||
return errors.New("No nil commands are allowed in AutocompleteData")
|
||||
}
|
||||
if ad.Trigger == "" {
|
||||
return errors.New("An empty command name in the autocomplete data")
|
||||
}
|
||||
if strings.ToLower(ad.Trigger) != ad.Trigger {
|
||||
return errors.New("Command should be lowercase")
|
||||
}
|
||||
roles := []string{SYSTEM_ADMIN_ROLE_ID, SYSTEM_USER_ROLE_ID, ""}
|
||||
if stringNotInSlice(ad.RoleID, roles) {
|
||||
return errors.New("Wrong role in the autocomplete data")
|
||||
}
|
||||
if len(ad.Arguments) > 0 && len(ad.SubCommands) > 0 {
|
||||
return errors.New("Command can't have arguments and subcommands")
|
||||
}
|
||||
if len(ad.Arguments) > 0 {
|
||||
namedArgumentIndex := -1
|
||||
for i, arg := range ad.Arguments {
|
||||
if arg.Name != "" { // it's a named argument
|
||||
if namedArgumentIndex == -1 { // first named argument
|
||||
namedArgumentIndex = i
|
||||
}
|
||||
} else { // it's a positional argument
|
||||
if namedArgumentIndex != -1 {
|
||||
return errors.New("Named argument should not be before positional argument")
|
||||
}
|
||||
}
|
||||
if arg.Type == AutocompleteArgTypeDynamicList {
|
||||
dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
|
||||
if !ok {
|
||||
return errors.New("Not a proper DynamicList type argument")
|
||||
}
|
||||
_, err := url.Parse(dynamicList.FetchURL)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "FetchURL is not a proper url")
|
||||
}
|
||||
} else if arg.Type == AutocompleteArgTypeStaticList {
|
||||
staticList, ok := arg.Data.(*AutocompleteStaticListArg)
|
||||
if !ok {
|
||||
return errors.New("Not a proper StaticList type argument")
|
||||
}
|
||||
for _, arg := range staticList.PossibleArguments {
|
||||
if arg.Item == "" {
|
||||
return errors.New("Possible argument name not set in StaticList argument")
|
||||
}
|
||||
}
|
||||
} else if arg.Type == AutocompleteArgTypeText {
|
||||
if _, ok := arg.Data.(*AutocompleteTextArg); !ok {
|
||||
return errors.New("Not a proper TextInput type argument")
|
||||
}
|
||||
if arg.Name == "" && !arg.Required {
|
||||
return errors.New("Positional argument can not be optional")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, command := range ad.SubCommands {
|
||||
err := command.IsValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToJSON encodes AutocompleteData struct to the json
|
||||
func (ad *AutocompleteData) ToJSON() ([]byte, error) {
|
||||
b, err := json.Marshal(ad)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't marshal slash command %s", ad.Trigger)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// AutocompleteDataFromJSON decodes AutocompleteData struct from the json
|
||||
func AutocompleteDataFromJSON(data []byte) (*AutocompleteData, error) {
|
||||
var ad AutocompleteData
|
||||
if err := json.Unmarshal(data, &ad); err != nil {
|
||||
return nil, errors.Wrap(err, "can't unmarshal AutocompleteData")
|
||||
}
|
||||
return &ad, nil
|
||||
}
|
||||
|
||||
// Equals method checks if argument is the same.
|
||||
func (a *AutocompleteArg) Equals(arg *AutocompleteArg) bool {
|
||||
if a.Name != arg.Name ||
|
||||
a.HelpText != arg.HelpText ||
|
||||
a.Type != arg.Type ||
|
||||
a.Required != arg.Required ||
|
||||
!reflect.DeepEqual(a.Data, arg.Data) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON will unmarshal argument
|
||||
func (a *AutocompleteArg) UnmarshalJSON(b []byte) error {
|
||||
var arg map[string]interface{}
|
||||
if err := json.Unmarshal(b, &arg); err != nil {
|
||||
return errors.Wrapf(err, "Can't unmarshal argument %s", string(b))
|
||||
}
|
||||
var ok bool
|
||||
a.Name, ok = arg["Name"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Name in the argument %s", string(b))
|
||||
}
|
||||
|
||||
a.HelpText, ok = arg["HelpText"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field HelpText in the argument %s", string(b))
|
||||
}
|
||||
|
||||
t, ok := arg["Type"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Type in the argument %s", string(b))
|
||||
}
|
||||
a.Type = AutocompleteArgType(t)
|
||||
|
||||
a.Required, ok = arg["Required"].(bool)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Required in the argument %s", string(b))
|
||||
}
|
||||
|
||||
data, ok := arg["Data"]
|
||||
if !ok {
|
||||
return errors.Errorf("No field Data in the argument %s", string(b))
|
||||
}
|
||||
|
||||
if a.Type == AutocompleteArgTypeText {
|
||||
m, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.Errorf("Wrong Data type in the TextInput argument %s", string(b))
|
||||
}
|
||||
pattern, ok := m["Pattern"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Pattern in the TextInput argument %s", string(b))
|
||||
}
|
||||
hint, ok := m["Hint"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Hint in the TextInput argument %s", string(b))
|
||||
}
|
||||
a.Data = &AutocompleteTextArg{Hint: hint, Pattern: pattern}
|
||||
} else if a.Type == AutocompleteArgTypeStaticList {
|
||||
m, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.Errorf("Wrong Data type in the StaticList argument %s", string(b))
|
||||
}
|
||||
list, ok := m["PossibleArguments"].([]interface{})
|
||||
if !ok {
|
||||
return errors.Errorf("No field PossibleArguments in the StaticList argument %s", string(b))
|
||||
}
|
||||
|
||||
possibleArguments := []AutocompleteListItem{}
|
||||
for i := range list {
|
||||
args, ok := list[i].(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.Errorf("Wrong AutocompleteStaticListItem type in the StaticList argument %s", string(b))
|
||||
}
|
||||
item, ok := args["Item"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Item in the StaticList's possible arguments %s", string(b))
|
||||
}
|
||||
|
||||
hint, ok := args["Hint"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
|
||||
}
|
||||
helpText, ok := args["HelpText"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
|
||||
}
|
||||
|
||||
possibleArguments = append(possibleArguments, AutocompleteListItem{
|
||||
Item: item,
|
||||
Hint: hint,
|
||||
HelpText: helpText,
|
||||
})
|
||||
}
|
||||
a.Data = &AutocompleteStaticListArg{PossibleArguments: possibleArguments}
|
||||
} else if a.Type == AutocompleteArgTypeDynamicList {
|
||||
m, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.Errorf("Wrong type in the DynamicList argument %s", string(b))
|
||||
}
|
||||
url, ok := m["FetchURL"].(string)
|
||||
if !ok {
|
||||
return errors.Errorf("No field FetchURL in the DynamicList's argument %s", string(b))
|
||||
}
|
||||
a.Data = &AutocompleteDynamicListArg{FetchURL: url}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutocompleteSuggestionsToJSON returns json for a list of AutocompleteSuggestion objects
|
||||
func AutocompleteSuggestionsToJSON(suggestions []AutocompleteSuggestion) []byte {
|
||||
b, _ := json.Marshal(suggestions)
|
||||
return b
|
||||
}
|
||||
|
||||
// AutocompleteSuggestionsFromJSON returns list of AutocompleteSuggestions from json.
|
||||
func AutocompleteSuggestionsFromJSON(data io.Reader) []AutocompleteSuggestion {
|
||||
var o []AutocompleteSuggestion
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
// AutocompleteStaticListItemsToJSON returns json for a list of AutocompleteStaticListItem objects
|
||||
func AutocompleteStaticListItemsToJSON(items []AutocompleteListItem) []byte {
|
||||
b, _ := json.Marshal(items)
|
||||
return b
|
||||
}
|
||||
|
||||
// AutocompleteStaticListItemsFromJSON returns list of AutocompleteStaticListItem from json.
|
||||
func AutocompleteStaticListItemsFromJSON(data io.Reader) []AutocompleteListItem {
|
||||
var o []AutocompleteListItem
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
func stringNotInSlice(a string, slice []string) bool {
|
||||
for _, b := range slice {
|
||||
if b == a {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
108
model/command_autocomplete_test.go
Обычный файл
108
model/command_autocomplete_test.go
Обычный файл
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAutocompleteData(t *testing.T) {
|
||||
ad := NewAutocompleteData("jira", "", "Avaliable commands:")
|
||||
assert.Nil(t, ad.IsValid())
|
||||
ad.RoleID = "some_id"
|
||||
assert.NotNil(t, ad.IsValid())
|
||||
ad.RoleID = SYSTEM_ADMIN_ROLE_ID
|
||||
assert.Nil(t, ad.IsValid())
|
||||
ad.AddDynamicListArgument("help", "/some/url", true)
|
||||
assert.Nil(t, ad.IsValid())
|
||||
ad.AddNamedTextArgument("name", "help", "[text]", "", true)
|
||||
assert.Nil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
assert.Nil(t, ad.IsValid())
|
||||
command := NewAutocompleteData("", "", "")
|
||||
ad.AddCommand(command)
|
||||
assert.NotNil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
command = NewAutocompleteData("disconnect", "", "disconnect")
|
||||
command.AddTextArgument("help", "[text]", "")
|
||||
command.AddNamedTextArgument("some", "help", "[text]", "", true)
|
||||
ad.AddCommand(command)
|
||||
assert.Nil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
command = NewAutocompleteData("disconnect", "", "disconnect")
|
||||
command.AddDynamicListArgument("help", "valid_url", true)
|
||||
ad.AddCommand(command)
|
||||
assert.Nil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
command = NewAutocompleteData("disconnect", "", "disconnect")
|
||||
command.AddDynamicListArgument("help", "/valid/url", true)
|
||||
items := []AutocompleteListItem{
|
||||
{
|
||||
Hint: "help",
|
||||
Item: "",
|
||||
HelpText: "text",
|
||||
},
|
||||
}
|
||||
command.AddStaticListArgument("help", true, items)
|
||||
ad.AddCommand(command)
|
||||
assert.NotNil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
ad.AddCommand(nil)
|
||||
assert.NotNil(t, ad.IsValid())
|
||||
|
||||
ad = getAutocompleteData()
|
||||
command = NewAutocompleteData("Disconnect", "", "")
|
||||
ad.AddCommand(command)
|
||||
assert.NotNil(t, ad.IsValid())
|
||||
}
|
||||
|
||||
func TestAutocompleteDataJSON(t *testing.T) {
|
||||
ad := getAutocompleteData()
|
||||
b, err := ad.ToJSON()
|
||||
assert.Nil(t, err)
|
||||
ad2, err := AutocompleteDataFromJSON(b)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ad2.Equals(ad))
|
||||
}
|
||||
|
||||
func getAutocompleteData() *AutocompleteData {
|
||||
ad := NewAutocompleteData("jira", "", "Avaliable commands:")
|
||||
ad.RoleID = SYSTEM_USER_ROLE_ID
|
||||
command := NewAutocompleteData("connect", "", "Connect to mattermost")
|
||||
command.RoleID = SYSTEM_ADMIN_ROLE_ID
|
||||
items := []AutocompleteListItem{
|
||||
{
|
||||
Hint: "arg1",
|
||||
Item: "help1",
|
||||
HelpText: "text1",
|
||||
}, {
|
||||
Hint: "arg2",
|
||||
Item: "help2",
|
||||
HelpText: "text2",
|
||||
},
|
||||
}
|
||||
command.AddStaticListArgument("help", true, items)
|
||||
command.AddNamedTextArgument("some", "help", "[text]", "", true)
|
||||
command.AddNamedDynamicListArgument("other", "help", "/other/url", true)
|
||||
ad.AddCommand(command)
|
||||
return ad
|
||||
}
|
||||
|
||||
func TestUpdateRelativeURLsForPluginCommands(t *testing.T) {
|
||||
ad := getAutocompleteData()
|
||||
baseURL, _ := url.Parse("http://localhost:8065/plugins/com.mattermost.demo-plugin")
|
||||
err := ad.UpdateRelativeURLsForPluginCommands(baseURL)
|
||||
assert.Nil(t, err)
|
||||
arg, ok := ad.SubCommands[0].Arguments[2].Data.(*AutocompleteDynamicListArg)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "http://localhost:8065/plugins/com.mattermost.demo-plugin/other/url", arg.FetchURL)
|
||||
}
|
||||
@@ -99,6 +99,9 @@ func init() {
|
||||
gob.Register(&model.AppError{})
|
||||
gob.Register(&ErrorString{})
|
||||
gob.Register(&opengraph.OpenGraph{})
|
||||
gob.Register(&model.AutocompleteDynamicListArg{})
|
||||
gob.Register(&model.AutocompleteStaticListArg{})
|
||||
gob.Register(&model.AutocompleteTextArg{})
|
||||
}
|
||||
|
||||
// These enforce compile time checks to make sure types implement the interface
|
||||
|
||||
Ссылка в новой задаче
Block a user