add external slashcommands management
Этот коммит содержится в:
@@ -44,7 +44,7 @@ func InitCommand(r *mux.Router) {
|
|||||||
sr := r.PathPrefix("/commands").Subrouter()
|
sr := r.PathPrefix("/commands").Subrouter()
|
||||||
|
|
||||||
sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST")
|
sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST")
|
||||||
sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET")
|
sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST")
|
||||||
|
|
||||||
sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
|
sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
|
||||||
sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
|
sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
|
||||||
@@ -76,7 +76,9 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
teamCmds := result.Data.([]*model.Command)
|
teamCmds := result.Data.([]*model.Command)
|
||||||
for _, cmd := range teamCmds {
|
for _, cmd := range teamCmds {
|
||||||
if cmd.AutoComplete && !seen[cmd.Id] {
|
if cmd.ExternalManagement {
|
||||||
|
commands = append(commands, autocompleteCommands(c, cmd, r)...)
|
||||||
|
} else if cmd.AutoComplete && !seen[cmd.Id] {
|
||||||
cmd.Sanitize()
|
cmd.Sanitize()
|
||||||
seen[cmd.Trigger] = true
|
seen[cmd.Trigger] = true
|
||||||
commands = append(commands, cmd)
|
commands = append(commands, cmd)
|
||||||
@@ -88,6 +90,92 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.CommandListToJson(commands)))
|
w.Write([]byte(model.CommandListToJson(commands)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autocompleteCommands(c *Context, cmd *model.Command, r *http.Request) []*model.Command {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
command := strings.TrimSpace(props["command"])
|
||||||
|
channelId := strings.TrimSpace(props["channelId"])
|
||||||
|
parts := strings.Split(command, " ")
|
||||||
|
trigger := parts[0][1:]
|
||||||
|
message := strings.Join(parts[1:], " ")
|
||||||
|
|
||||||
|
chanChan := Srv.Store.Channel().Get(channelId)
|
||||||
|
teamChan := Srv.Store.Team().Get(c.Session.TeamId)
|
||||||
|
userChan := Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if tr := <-teamChan; tr.Err != nil {
|
||||||
|
c.Err = tr.Err
|
||||||
|
return make([]*model.Command, 0, 32)
|
||||||
|
} else {
|
||||||
|
team = tr.Data.(*model.Team)
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if ur := <-userChan; ur.Err != nil {
|
||||||
|
c.Err = ur.Err
|
||||||
|
return make([]*model.Command, 0, 32)
|
||||||
|
} else {
|
||||||
|
user = ur.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel *model.Channel
|
||||||
|
if cr := <-chanChan; cr.Err != nil {
|
||||||
|
c.Err = cr.Err
|
||||||
|
return make([]*model.Command, 0, 32)
|
||||||
|
} else {
|
||||||
|
channel = cr.Data.(*model.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, c.Session.UserId))
|
||||||
|
p := url.Values{}
|
||||||
|
p.Set("token", cmd.Token)
|
||||||
|
|
||||||
|
p.Set("team_id", cmd.TeamId)
|
||||||
|
p.Set("team_domain", team.Name)
|
||||||
|
|
||||||
|
p.Set("channel_id", channelId)
|
||||||
|
p.Set("channel_name", channel.Name)
|
||||||
|
|
||||||
|
p.Set("user_id", c.Session.UserId)
|
||||||
|
p.Set("user_name", user.Username)
|
||||||
|
|
||||||
|
p.Set("command", "/"+trigger)
|
||||||
|
p.Set("text", message)
|
||||||
|
p.Set("response_url", "not supported yet")
|
||||||
|
p.Set("suggest", "true")
|
||||||
|
|
||||||
|
method := "POST"
|
||||||
|
if cmd.Method == model.COMMAND_METHOD_GET {
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode()))
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
if cmd.Method == model.COMMAND_METHOD_POST {
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp, err := client.Do(req); err != nil {
|
||||||
|
c.Err = model.NewLocAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error())
|
||||||
|
} else {
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
response := model.CommandListFromJson(resp.Body)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
} else {
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return make([]*model.Command, 0, 32)
|
||||||
|
}
|
||||||
|
|
||||||
func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
|
func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
command := strings.TrimSpace(props["command"])
|
command := strings.TrimSpace(props["command"])
|
||||||
@@ -159,7 +247,7 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
teamCmds := result.Data.([]*model.Command)
|
teamCmds := result.Data.([]*model.Command)
|
||||||
for _, cmd := range teamCmds {
|
for _, cmd := range teamCmds {
|
||||||
if trigger == cmd.Trigger {
|
if trigger == cmd.Trigger || cmd.ExternalManagement {
|
||||||
l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, c.Session.UserId))
|
l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, c.Session.UserId))
|
||||||
|
|
||||||
p := url.Values{}
|
p := url.Values{}
|
||||||
|
|||||||
@@ -14,22 +14,23 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
UpdateAt int64 `json:"update_at"`
|
UpdateAt int64 `json:"update_at"`
|
||||||
DeleteAt int64 `json:"delete_at"`
|
DeleteAt int64 `json:"delete_at"`
|
||||||
CreatorId string `json:"creator_id"`
|
CreatorId string `json:"creator_id"`
|
||||||
TeamId string `json:"team_id"`
|
TeamId string `json:"team_id"`
|
||||||
Trigger string `json:"trigger"`
|
ExternalManagement bool `json:"external_management"`
|
||||||
Method string `json:"method"`
|
Trigger string `json:"trigger"`
|
||||||
Username string `json:"username"`
|
Method string `json:"method"`
|
||||||
IconURL string `json:"icon_url"`
|
Username string `json:"username"`
|
||||||
AutoComplete bool `json:"auto_complete"`
|
IconURL string `json:"icon_url"`
|
||||||
AutoCompleteDesc string `json:"auto_complete_desc"`
|
AutoComplete bool `json:"auto_complete"`
|
||||||
AutoCompleteHint string `json:"auto_complete_hint"`
|
AutoCompleteDesc string `json:"auto_complete_desc"`
|
||||||
DisplayName string `json:"display_name"`
|
AutoCompleteHint string `json:"auto_complete_hint"`
|
||||||
URL string `json:"url"`
|
DisplayName string `json:"display_name"`
|
||||||
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Command) ToJson() string {
|
func (o *Command) ToJson() string {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ func NewSqlCommandStore(sqlStore *SqlStore) CommandStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlCommandStore) UpgradeSchemaIfNeeded() {
|
func (s SqlCommandStore) UpgradeSchemaIfNeeded() {
|
||||||
|
s.CreateColumnIfNotExists("Commands", "ExternalManagement", "tinyint(1)", "boolean", "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlCommandStore) CreateIndexesIfNotExists() {
|
func (s SqlCommandStore) CreateIndexesIfNotExists() {
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ CommandSuggestion.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class CommandProvider {
|
export default class CommandProvider {
|
||||||
handlePretextChanged(suggestionId, pretext) {
|
handlePretextChanged(suggestionId, pretext, channelId) {
|
||||||
if (pretext.startsWith('/')) {
|
if (pretext.startsWith('/')) {
|
||||||
AsyncClient.getSuggestedCommands(pretext, suggestionId, CommandSuggestion);
|
AsyncClient.getSuggestedCommands(pretext, channelId, suggestionId, CommandSuggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default class SuggestionBox extends React.Component {
|
|||||||
|
|
||||||
handlePretextChanged(pretext) {
|
handlePretextChanged(pretext) {
|
||||||
for (const provider of this.props.providers) {
|
for (const provider of this.props.providers) {
|
||||||
provider.handlePretextChanged(this.suggestionId, pretext);
|
provider.handlePretextChanged(this.suggestionId, pretext, this.props.channelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export default class Textbox extends React.Component {
|
|||||||
style={{visibility: this.state.preview ? 'hidden' : 'visible'}}
|
style={{visibility: this.state.preview ? 'hidden' : 'visible'}}
|
||||||
listComponent={SuggestionList}
|
listComponent={SuggestionList}
|
||||||
providers={this.suggestionProviders}
|
providers={this.suggestionProviders}
|
||||||
|
channelId={this.props.channelId}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
ref='preview'
|
ref='preview'
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
this.getCmds = this.getCmds.bind(this);
|
this.getCmds = this.getCmds.bind(this);
|
||||||
this.addNewCmd = this.addNewCmd.bind(this);
|
this.addNewCmd = this.addNewCmd.bind(this);
|
||||||
this.emptyCmd = this.emptyCmd.bind(this);
|
this.emptyCmd = this.emptyCmd.bind(this);
|
||||||
|
this.updateExternalManagement = this.updateExternalManagement.bind(this);
|
||||||
this.updateTrigger = this.updateTrigger.bind(this);
|
this.updateTrigger = this.updateTrigger.bind(this);
|
||||||
this.updateURL = this.updateURL.bind(this);
|
this.updateURL = this.updateURL.bind(this);
|
||||||
this.updateMethod = this.updateMethod.bind(this);
|
this.updateMethod = this.updateMethod.bind(this);
|
||||||
@@ -99,7 +100,7 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
addNewCmd(e) {
|
addNewCmd(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.state.cmd.trigger === '' || this.state.cmd.url === '') {
|
if (this.state.cmd.url === '' || (this.state.cmd.trigger === '' && !this.state.external_management)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,12 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateExternalManagement(e) {
|
||||||
|
var cmd = this.state.cmd;
|
||||||
|
cmd.external_management = e.target.checked;
|
||||||
|
this.setState(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
updateTrigger(e) {
|
updateTrigger(e) {
|
||||||
var cmd = this.state.cmd;
|
var cmd = this.state.cmd;
|
||||||
cmd.trigger = e.target.value;
|
cmd.trigger = e.target.value;
|
||||||
@@ -275,6 +282,14 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
key={cmd.id}
|
key={cmd.id}
|
||||||
className='webhook__item webcmd__item'
|
className='webhook__item webcmd__item'
|
||||||
>
|
>
|
||||||
|
<div className='padding-top x2'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.cmds.external_management'
|
||||||
|
defaultMessage='External management: '
|
||||||
|
/>
|
||||||
|
</strong><span className='word-break--all'>{cmd.external_management ? this.props.intl.formatMessage(holders.autocompleteYes) : this.props.intl.formatMessage(holders.autocompleteNo)}</span>
|
||||||
|
</div>
|
||||||
{triggerDiv}
|
{triggerDiv}
|
||||||
<div className='padding-top x2 webcmd__url'>
|
<div className='padding-top x2 webcmd__url'>
|
||||||
<strong>
|
<strong>
|
||||||
@@ -416,7 +431,7 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const disableButton = this.state.cmd.trigger === '' || this.state.cmd.url === '';
|
const disableButton = this.state.cmd.url === '' || (this.state.cmd.trigger === '' && !this.state.external_management);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key='addCommandCmd'>
|
<div key='addCommandCmd'>
|
||||||
@@ -433,6 +448,30 @@ export default class ManageCommandCmds extends React.Component {
|
|||||||
<div className='padding-top divider-light'></div>
|
<div className='padding-top divider-light'></div>
|
||||||
<div className='padding-top'>
|
<div className='padding-top'>
|
||||||
|
|
||||||
|
<div className='padding-top x2'>
|
||||||
|
<label className='control-label'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.cmds.external_management'
|
||||||
|
defaultMessage='External management: '
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div className='padding-top'>
|
||||||
|
<div className='checkbox'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.cmd.external_management}
|
||||||
|
onChange={this.updateExternalManagement}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.cmds.external_management_help'
|
||||||
|
defaultMessage=' Let an external integration manage commands.'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='padding-top x2'>
|
<div className='padding-top x2'>
|
||||||
<label className='control-label'>
|
<label className='control-label'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@@ -755,12 +755,15 @@ export function savePreferences(preferences, success, error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSuggestedCommands(command, suggestionId, component) {
|
export function getSuggestedCommands(command, channelId, suggestionId, component) {
|
||||||
client.listCommands(
|
client.listCommands({
|
||||||
|
command: command,
|
||||||
|
channelId: channelId
|
||||||
|
},
|
||||||
(data) => {
|
(data) => {
|
||||||
var matches = [];
|
var matches = [];
|
||||||
data.forEach((cmd) => {
|
data.forEach((cmd) => {
|
||||||
if (('/' + cmd.trigger).indexOf(command) === 0) {
|
if (('/' + cmd.trigger).indexOf(command) === 0 || cmd.external_management) {
|
||||||
let s = '/' + cmd.trigger;
|
let s = '/' + cmd.trigger;
|
||||||
let hint = '';
|
let hint = '';
|
||||||
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
|
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
|
||||||
|
|||||||
@@ -1002,12 +1002,13 @@ export function regenCommandToken(data, success, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listCommands(success, error) {
|
export function listCommands(data, success, error) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/v1/commands/list',
|
url: '/api/v1/commands/list',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
type: 'GET',
|
type: 'POST',
|
||||||
|
data: JSON.stringify(data),
|
||||||
success,
|
success,
|
||||||
error: function onError(xhr, status, err) {
|
error: function onError(xhr, status, err) {
|
||||||
var e = handleError('listCommands', xhr, status, err);
|
var e = handleError('listCommands', xhr, status, err);
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user