summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Clerc <kernicPanel@nclerc.fr>2016-02-15 09:11:35 +0100
committerNicolas Clerc <kernicPanel@nclerc.fr>2016-03-17 01:45:37 +0100
commit5e2596598f97e318f1e4e8bd835b08a011fa0b60 (patch)
tree68b3a9200e9858a7239d45b1869c38c1216d6bda
parent809779a87f4380b6802314271b06540a31b83f53 (diff)
downloadchat-5e2596598f97e318f1e4e8bd835b08a011fa0b60.tar.gz
chat-5e2596598f97e318f1e4e8bd835b08a011fa0b60.tar.bz2
chat-5e2596598f97e318f1e4e8bd835b08a011fa0b60.zip
add external slashcommands management
-rw-r--r--api/command.go94
-rw-r--r--model/command.go33
-rw-r--r--store/sql_command_store.go1
-rw-r--r--webapp/components/suggestion/command_provider.jsx4
-rw-r--r--webapp/components/suggestion/suggestion_box.jsx2
-rw-r--r--webapp/components/textbox.jsx1
-rw-r--r--webapp/components/user_settings/manage_command_hooks.jsx43
-rw-r--r--webapp/utils/async_client.jsx9
-rw-r--r--webapp/utils/client.jsx5
9 files changed, 163 insertions, 29 deletions
diff --git a/api/command.go b/api/command.go
index 99fd05d7a..29cee070e 100644
--- a/api/command.go
+++ b/api/command.go
@@ -44,7 +44,7 @@ func InitCommand(r *mux.Router) {
sr := r.PathPrefix("/commands").Subrouter()
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("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
@@ -76,7 +76,9 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
teamCmds := result.Data.([]*model.Command)
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()
seen[cmd.Trigger] = true
commands = append(commands, cmd)
@@ -88,6 +90,92 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
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) {
props := model.MapFromJson(r.Body)
command := strings.TrimSpace(props["command"])
@@ -159,7 +247,7 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) {
teamCmds := result.Data.([]*model.Command)
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))
p := url.Values{}
diff --git a/model/command.go b/model/command.go
index 56d88f13c..8e0b31583 100644
--- a/model/command.go
+++ b/model/command.go
@@ -14,22 +14,23 @@ 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"`
- 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"`
+ ExternalManagement bool `json:"external_management"`
+ 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"`
+ URL string `json:"url"`
}
func (o *Command) ToJson() string {
diff --git a/store/sql_command_store.go b/store/sql_command_store.go
index 074a6e588..a35737bd7 100644
--- a/store/sql_command_store.go
+++ b/store/sql_command_store.go
@@ -34,6 +34,7 @@ func NewSqlCommandStore(sqlStore *SqlStore) CommandStore {
}
func (s SqlCommandStore) UpgradeSchemaIfNeeded() {
+ s.CreateColumnIfNotExists("Commands", "ExternalManagement", "tinyint(1)", "boolean", "0")
}
func (s SqlCommandStore) CreateIndexesIfNotExists() {
diff --git a/webapp/components/suggestion/command_provider.jsx b/webapp/components/suggestion/command_provider.jsx
index 36860fa66..204f52483 100644
--- a/webapp/components/suggestion/command_provider.jsx
+++ b/webapp/components/suggestion/command_provider.jsx
@@ -37,9 +37,9 @@ CommandSuggestion.propTypes = {
};
export default class CommandProvider {
- handlePretextChanged(suggestionId, pretext) {
+ handlePretextChanged(suggestionId, pretext, channelId) {
if (pretext.startsWith('/')) {
- AsyncClient.getSuggestedCommands(pretext, suggestionId, CommandSuggestion);
+ AsyncClient.getSuggestedCommands(pretext, channelId, suggestionId, CommandSuggestion);
}
}
}
diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx
index e3ec63194..04e4006f1 100644
--- a/webapp/components/suggestion/suggestion_box.jsx
+++ b/webapp/components/suggestion/suggestion_box.jsx
@@ -111,7 +111,7 @@ export default class SuggestionBox extends React.Component {
handlePretextChanged(pretext) {
for (const provider of this.props.providers) {
- provider.handlePretextChanged(this.suggestionId, pretext);
+ provider.handlePretextChanged(this.suggestionId, pretext, this.props.channelId);
}
}
diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx
index 1a395072e..952026ed5 100644
--- a/webapp/components/textbox.jsx
+++ b/webapp/components/textbox.jsx
@@ -224,6 +224,7 @@ export default class Textbox extends React.Component {
style={{visibility: this.state.preview ? 'hidden' : 'visible'}}
listComponent={SuggestionList}
providers={this.suggestionProviders}
+ channelId={this.props.channelId}
/>
<div
ref='preview'
diff --git a/webapp/components/user_settings/manage_command_hooks.jsx b/webapp/components/user_settings/manage_command_hooks.jsx
index ce353ad64..4053a62aa 100644
--- a/webapp/components/user_settings/manage_command_hooks.jsx
+++ b/webapp/components/user_settings/manage_command_hooks.jsx
@@ -59,6 +59,7 @@ export default class ManageCommandCmds extends React.Component {
this.getCmds = this.getCmds.bind(this);
this.addNewCmd = this.addNewCmd.bind(this);
this.emptyCmd = this.emptyCmd.bind(this);
+ this.updateExternalManagement = this.updateExternalManagement.bind(this);
this.updateTrigger = this.updateTrigger.bind(this);
this.updateURL = this.updateURL.bind(this);
this.updateMethod = this.updateMethod.bind(this);
@@ -99,7 +100,7 @@ export default class ManageCommandCmds extends React.Component {
addNewCmd(e) {
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;
}
@@ -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) {
var cmd = this.state.cmd;
cmd.trigger = e.target.value;
@@ -275,6 +282,14 @@ export default class ManageCommandCmds extends React.Component {
key={cmd.id}
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}
<div className='padding-top x2 webcmd__url'>
<strong>
@@ -416,7 +431,7 @@ export default class ManageCommandCmds extends React.Component {
</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 (
<div key='addCommandCmd'>
@@ -436,6 +451,30 @@ export default class ManageCommandCmds extends React.Component {
<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'>
+ <label className='control-label'>
+ <FormattedMessage
id='user.settings.cmds.trigger'
defaultMessage='Command Trigger Word: '
/>
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index 9c40311cf..3e2a706d4 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -755,12 +755,15 @@ export function savePreferences(preferences, success, error) {
);
}
-export function getSuggestedCommands(command, suggestionId, component) {
- client.listCommands(
+export function getSuggestedCommands(command, channelId, suggestionId, component) {
+ client.listCommands({
+ command: command,
+ channelId: channelId
+ },
(data) => {
var matches = [];
data.forEach((cmd) => {
- if (('/' + cmd.trigger).indexOf(command) === 0) {
+ if (('/' + cmd.trigger).indexOf(command) === 0 || cmd.external_management) {
let s = '/' + cmd.trigger;
let hint = '';
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
diff --git a/webapp/utils/client.jsx b/webapp/utils/client.jsx
index 9bd62e22d..ef6d496a2 100644
--- a/webapp/utils/client.jsx
+++ b/webapp/utils/client.jsx
@@ -1002,12 +1002,13 @@ export function regenCommandToken(data, success, error) {
});
}
-export function listCommands(success, error) {
+export function listCommands(data, success, error) {
$.ajax({
url: '/api/v1/commands/list',
dataType: 'json',
contentType: 'application/json',
- type: 'GET',
+ type: 'POST',
+ data: JSON.stringify(data),
success,
error: function onError(xhr, status, err) {
var e = handleError('listCommands', xhr, status, err);