add external slashcommands management

Этот коммит содержится в:
Nicolas Clerc
2016-02-15 09:11:35 +01:00
родитель 809779a87f
Коммит 5e2596598f
9 изменённых файлов: 163 добавлений и 29 удалений

Просмотреть файл

@@ -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);