MM-30212 Support dynamic autocomplete for built-in slash commands (#16196)
Support added for dynamic auto-completion lists for built-in slash commands. * a moniker within the fetch url determines whether the request should be sent via HTTP(S) or routed internally to a built-in command. The moniker is of the form builtin: followed by the name of the built-in command to use. e.g. builtin:share would be become a call to the share command to fetch auto-completion items. * command providers implement the optional DynamicListProvider interface to indicate they can provide dynamic auto-completion.
Этот коммит содержится в:
@@ -4,6 +4,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,6 +14,11 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AutocompleteDynamicArgProvider dynamically provides auto-completion args for built-in commands.
|
||||||
|
type AutocompleteDynamicArgProvider interface {
|
||||||
|
GetAutoCompleteListItems(a *App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
// GetSuggestions returns suggestions for user input.
|
// GetSuggestions returns suggestions for user input.
|
||||||
func (a *App) GetSuggestions(commandArgs *model.CommandArgs, commands []*model.Command, roleID string) []model.AutocompleteSuggestion {
|
func (a *App) GetSuggestions(commandArgs *model.CommandArgs, commands []*model.Command, roleID string) []model.AutocompleteSuggestion {
|
||||||
sort.Slice(commands, func(i, j int) bool {
|
sort.Slice(commands, func(i, j int) bool {
|
||||||
@@ -233,6 +240,15 @@ func parseStaticListArgument(arg *model.AutocompleteArg, parsed, toBeParsed stri
|
|||||||
func (a *App) getDynamicListArgument(commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
func (a *App) getDynamicListArgument(commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) (found bool, alreadyParsed string, yetToBeParsed string, suggestions []model.AutocompleteSuggestion) {
|
||||||
dynamicArg := arg.Data.(*model.AutocompleteDynamicListArg)
|
dynamicArg := arg.Data.(*model.AutocompleteDynamicListArg)
|
||||||
|
|
||||||
|
if strings.HasPrefix(dynamicArg.FetchURL, "builtin:") {
|
||||||
|
listItems, err := a.getBuiltinDynamicListArgument(commandArgs, arg, parsed, toBeParsed)
|
||||||
|
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{}
|
||||||
|
}
|
||||||
|
return parseListItems(listItems, parsed, toBeParsed)
|
||||||
|
}
|
||||||
|
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("user_input", parsed+toBeParsed)
|
params.Add("user_input", parsed+toBeParsed)
|
||||||
params.Add("parsed", parsed)
|
params.Add("parsed", parsed)
|
||||||
@@ -286,3 +302,24 @@ func parseListItems(items []model.AutocompleteListItem, parsed, toBeParsed strin
|
|||||||
}
|
}
|
||||||
return true, parsed + toBeParsed, "", suggestions
|
return true, parsed + toBeParsed, "", suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) getBuiltinDynamicListArgument(commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
|
||||||
|
dynamicArg := arg.Data.(*model.AutocompleteDynamicListArg)
|
||||||
|
arr := strings.Split(dynamicArg.FetchURL, ":")
|
||||||
|
if len(arr) < 2 {
|
||||||
|
return nil, errors.New("Dynamic list URL missing built-in command name")
|
||||||
|
}
|
||||||
|
cmdName := arr[1]
|
||||||
|
|
||||||
|
provider := GetCommandProvider(cmdName)
|
||||||
|
if provider == nil {
|
||||||
|
return nil, fmt.Errorf("No command provider for %s", cmdName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dp, ok := provider.(AutocompleteDynamicArgProvider)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Auto-completion not available for built-in command %s", cmdName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp.GetAutoCompleteListItems(a, commandArgs, arg, parsed, toBeParsed)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
goi18n "github.com/mattermost/go-i18n/i18n"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -609,3 +611,66 @@ func createJiraAutocompleteData() *model.AutocompleteData {
|
|||||||
|
|
||||||
return jira
|
return jira
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDynamicListArgsForBuiltin(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
provider := &testProvider{}
|
||||||
|
RegisterCommandProvider(provider)
|
||||||
|
|
||||||
|
command := provider.GetCommand(th.App, nil)
|
||||||
|
emptyCmdArgs := &model.CommandArgs{}
|
||||||
|
|
||||||
|
t.Run("GetAutoCompleteListItems", func(t *testing.T) {
|
||||||
|
suggestions := th.App.getSuggestions(emptyCmdArgs, []*model.AutocompleteData{command.AutocompleteData}, "", "bogus --dynaArg ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||||
|
assert.Len(t, suggestions, 3)
|
||||||
|
assert.Equal(t, "this is hint 1", suggestions[0].Hint)
|
||||||
|
assert.Equal(t, "this is hint 2", suggestions[1].Hint)
|
||||||
|
assert.Equal(t, "this is hint 3", suggestions[2].Hint)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetAutoCompleteListItems bad arg", func(t *testing.T) {
|
||||||
|
suggestions := th.App.getSuggestions(emptyCmdArgs, []*model.AutocompleteData{command.AutocompleteData}, "", "bogus --badArg ", model.SYSTEM_ADMIN_ROLE_ID)
|
||||||
|
assert.Len(t, suggestions, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testProvider) GetTrigger() string {
|
||||||
|
return "bogus"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
|
||||||
|
top := model.NewAutocompleteData(p.GetTrigger(), "[command]", "Just a test.")
|
||||||
|
top.AddNamedDynamicListArgument("dynaArg", "A dynamic list", "builtin:bogus", true)
|
||||||
|
|
||||||
|
return &model.Command{
|
||||||
|
Trigger: p.GetTrigger(),
|
||||||
|
AutoComplete: true,
|
||||||
|
AutoCompleteDesc: "Test description",
|
||||||
|
AutoCompleteHint: "Test hint.",
|
||||||
|
DisplayName: "test display name",
|
||||||
|
AutocompleteData: top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
Text: "I do nothing!",
|
||||||
|
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testProvider) GetAutoCompleteListItems(a *App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
|
||||||
|
if arg.Name == "dynaArg" {
|
||||||
|
return []model.AutocompleteListItem{
|
||||||
|
{Item: "item1", Hint: "this is hint 1", HelpText: "This is help text 1."},
|
||||||
|
{Item: "item2", Hint: "this is hint 2", HelpText: "This is help text 2."},
|
||||||
|
{Item: "item3", Hint: "this is hint 3", HelpText: "This is help text 3."},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user