Merge pull request #2584 from hmhealey/plt2054

PLT-2054 Add DisplayName and Description fields for integrations
Этот коммит содержится в:
Corey Hulen
2016-04-04 08:15:13 -07:00
родитель ead8e64ccb 74daec08ce
Коммит 578749d083
16 изменённых файлов: 204 добавлений и 40 удалений

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

@@ -2059,6 +2059,14 @@
"id": "model.command.is_valid.create_at.app_error", "id": "model.command.is_valid.create_at.app_error",
"translation": "Create at must be a valid time" "translation": "Create at must be a valid time"
}, },
{
"id": "model.command.is_valid.description.app_error",
"translation": "Invalid description"
},
{
"id": "model.command.is_valid.display_name.app_error",
"translation": "Invalid display name"
},
{ {
"id": "model.command.is_valid.id.app_error", "id": "model.command.is_valid.id.app_error",
"translation": "Invalid Id" "translation": "Invalid Id"
@@ -2215,6 +2223,14 @@
"id": "model.incoming_hook.create_at.app_error", "id": "model.incoming_hook.create_at.app_error",
"translation": "Create at must be a valid time" "translation": "Create at must be a valid time"
}, },
{
"id": "model.incoming_hook.description.app_error",
"translation": "Invalid description"
},
{
"id": "model.incoming_hook.display_name.app_error",
"translation": "Invalid display name"
},
{ {
"id": "model.incoming_hook.id.app_error", "id": "model.incoming_hook.id.app_error",
"translation": "Invalid Id" "translation": "Invalid Id"
@@ -2279,6 +2295,14 @@
"id": "model.outgoing_hook.is_valid.create_at.app_error", "id": "model.outgoing_hook.is_valid.create_at.app_error",
"translation": "Create at must be a valid time" "translation": "Create at must be a valid time"
}, },
{
"id": "model.outgoing_hook.is_valid.description.app_error",
"translation": "Invalid description"
},
{
"id": "model.outgoing_hook.is_valid.display_name.app_error",
"translation": "Invalid display name"
},
{ {
"id": "model.outgoing_hook.is_valid.id.app_error", "id": "model.outgoing_hook.is_valid.id.app_error",
"translation": "Invalid Id" "translation": "Invalid Id"

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

@@ -29,6 +29,7 @@ type Command struct {
AutoCompleteDesc string `json:"auto_complete_desc"` AutoCompleteDesc string `json:"auto_complete_desc"`
AutoCompleteHint string `json:"auto_complete_hint"` AutoCompleteHint string `json:"auto_complete_hint"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
Description string `json:"description"`
URL string `json:"url"` URL string `json:"url"`
} }
@@ -114,6 +115,14 @@ func (o *Command) IsValid() *AppError {
return NewLocAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "") return NewLocAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "")
} }
if len(o.DisplayName) > 64 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.display_name.app_error", nil, "")
}
if len(o.Description) > 128 {
return NewLocAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "")
}
return nil return nil
} }

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

@@ -84,6 +84,26 @@ func TestCommandIsValid(t *testing.T) {
if err := o.IsValid(); err != nil { if err := o.IsValid(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
o.DisplayName = strings.Repeat("1", 65)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.DisplayName = strings.Repeat("1", 64)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
o.Description = strings.Repeat("1", 129)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Description = strings.Repeat("1", 128)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
} }
func TestCommandPreSave(t *testing.T) { func TestCommandPreSave(t *testing.T) {

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

@@ -14,13 +14,15 @@ const (
) )
type IncomingWebhook struct { type IncomingWebhook struct {
Id string `json:"id"` Id string `json:"id"`
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"`
UserId string `json:"user_id"` UserId string `json:"user_id"`
ChannelId string `json:"channel_id"` ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"` TeamId string `json:"team_id"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
} }
type IncomingWebhookRequest struct { type IncomingWebhookRequest struct {
@@ -99,6 +101,14 @@ func (o *IncomingWebhook) IsValid() *AppError {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "") return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "")
} }
if len(o.DisplayName) > 64 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "")
}
if len(o.Description) > 128 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "")
}
return nil return nil
} }

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

@@ -69,6 +69,26 @@ func TestIncomingWebhookIsValid(t *testing.T) {
if err := o.IsValid(); err != nil { if err := o.IsValid(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
o.DisplayName = strings.Repeat("1", 65)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.DisplayName = strings.Repeat("1", 64)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
o.Description = strings.Repeat("1", 129)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Description = strings.Repeat("1", 128)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
} }
func TestIncomingWebhookPreSave(t *testing.T) { func TestIncomingWebhookPreSave(t *testing.T) {

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

@@ -20,6 +20,8 @@ type OutgoingWebhook struct {
TeamId string `json:"team_id"` TeamId string `json:"team_id"`
TriggerWords StringArray `json:"trigger_words"` TriggerWords StringArray `json:"trigger_words"`
CallbackURLs StringArray `json:"callback_urls"` CallbackURLs StringArray `json:"callback_urls"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
} }
func (o *OutgoingWebhook) ToJson() string { func (o *OutgoingWebhook) ToJson() string {
@@ -106,6 +108,14 @@ func (o *OutgoingWebhook) IsValid() *AppError {
} }
} }
if len(o.DisplayName) > 64 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "")
}
if len(o.Description) > 128 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "")
}
return nil return nil
} }

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

@@ -89,6 +89,26 @@ func TestOutgoingWebhookIsValid(t *testing.T) {
if err := o.IsValid(); err != nil { if err := o.IsValid(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
o.DisplayName = strings.Repeat("1", 65)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.DisplayName = strings.Repeat("1", 64)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
o.Description = strings.Repeat("1", 129)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Description = strings.Repeat("1", 128)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
} }
func TestOutgoingWebhookPreSave(t *testing.T) { func TestOutgoingWebhookPreSave(t *testing.T) {

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

@@ -28,12 +28,14 @@ func NewSqlCommandStore(sqlStore *SqlStore) CommandStore {
tableo.ColMap("AutoCompleteDesc").SetMaxSize(1024) tableo.ColMap("AutoCompleteDesc").SetMaxSize(1024)
tableo.ColMap("AutoCompleteHint").SetMaxSize(1024) tableo.ColMap("AutoCompleteHint").SetMaxSize(1024)
tableo.ColMap("DisplayName").SetMaxSize(64) tableo.ColMap("DisplayName").SetMaxSize(64)
tableo.ColMap("Description").SetMaxSize(128)
} }
return s return s
} }
func (s SqlCommandStore) UpgradeSchemaIfNeeded() { func (s SqlCommandStore) UpgradeSchemaIfNeeded() {
s.CreateColumnIfNotExists("Commands", "Description", "varchar(128)", "varchar(128)", "")
} }
func (s SqlCommandStore) CreateIndexesIfNotExists() { func (s SqlCommandStore) CreateIndexesIfNotExists() {

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

@@ -20,6 +20,8 @@ func NewSqlWebhookStore(sqlStore *SqlStore) WebhookStore {
table.ColMap("UserId").SetMaxSize(26) table.ColMap("UserId").SetMaxSize(26)
table.ColMap("ChannelId").SetMaxSize(26) table.ColMap("ChannelId").SetMaxSize(26)
table.ColMap("TeamId").SetMaxSize(26) table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("DisplayName").SetMaxSize(64)
table.ColMap("Description").SetMaxSize(128)
tableo := db.AddTableWithName(model.OutgoingWebhook{}, "OutgoingWebhooks").SetKeys(false, "Id") tableo := db.AddTableWithName(model.OutgoingWebhook{}, "OutgoingWebhooks").SetKeys(false, "Id")
tableo.ColMap("Id").SetMaxSize(26) tableo.ColMap("Id").SetMaxSize(26)
@@ -29,12 +31,19 @@ func NewSqlWebhookStore(sqlStore *SqlStore) WebhookStore {
tableo.ColMap("TeamId").SetMaxSize(26) tableo.ColMap("TeamId").SetMaxSize(26)
tableo.ColMap("TriggerWords").SetMaxSize(1024) tableo.ColMap("TriggerWords").SetMaxSize(1024)
tableo.ColMap("CallbackURLs").SetMaxSize(1024) tableo.ColMap("CallbackURLs").SetMaxSize(1024)
tableo.ColMap("DisplayName").SetMaxSize(64)
tableo.ColMap("Description").SetMaxSize(128)
} }
return s return s
} }
func (s SqlWebhookStore) UpgradeSchemaIfNeeded() { func (s SqlWebhookStore) UpgradeSchemaIfNeeded() {
s.CreateColumnIfNotExists("IncomingWebhooks", "DisplayName", "varchar(64)", "varchar(64)", "")
s.CreateColumnIfNotExists("IncomingWebhooks", "Description", "varchar(128)", "varchar(128)", "")
s.CreateColumnIfNotExists("OutgoingWebhooks", "DisplayName", "varchar(64)", "varchar(64)", "")
s.CreateColumnIfNotExists("OutgoingWebhooks", "Description", "varchar(128)", "varchar(128)", "")
} }
func (s SqlWebhookStore) CreateIndexesIfNotExists() { func (s SqlWebhookStore) CreateIndexesIfNotExists() {

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

@@ -18,12 +18,12 @@ export default class AddIncomingWebhook extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.updateName = this.updateName.bind(this); this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this); this.updateDescription = this.updateDescription.bind(this);
this.updateChannelId = this.updateChannelId.bind(this); this.updateChannelId = this.updateChannelId.bind(this);
this.state = { this.state = {
name: '', displayName: '',
description: '', description: '',
channelId: '', channelId: '',
saving: false, saving: false,
@@ -60,7 +60,9 @@ export default class AddIncomingWebhook extends React.Component {
} }
const hook = { const hook = {
channel_id: this.state.channelId channel_id: this.state.channelId,
display_name: this.state.displayName,
description: this.state.description
}; };
AsyncClient.addIncomingHook( AsyncClient.addIncomingHook(
@@ -70,15 +72,16 @@ export default class AddIncomingWebhook extends React.Component {
}, },
(err) => { (err) => {
this.setState({ this.setState({
saving: false,
serverError: err.message serverError: err.message
}); });
} }
); );
} }
updateName(e) { updateDisplayName(e) {
this.setState({ this.setState({
name: e.target.value displayName: e.target.value
}); });
} }
@@ -112,20 +115,21 @@ export default class AddIncomingWebhook extends React.Component {
<div className='form-group'> <div className='form-group'>
<label <label
className='control-label col-sm-3' className='control-label col-sm-3'
htmlFor='name' htmlFor='displayName'
> >
<FormattedMessage <FormattedMessage
id='add_incoming_webhook.name' id='add_incoming_webhook.displayName'
defaultMessage='Name' defaultMessage='Display Name'
/> />
</label> </label>
<div className='col-md-5 col-sm-9'> <div className='col-md-5 col-sm-9'>
<input <input
id='name' id='displayName'
type='text' type='text'
maxLength='64'
className='form-control' className='form-control'
value={this.state.name} value={this.state.displayName}
onChange={this.updateName} onChange={this.updateDisplayName}
/> />
</div> </div>
</div> </div>
@@ -143,6 +147,7 @@ export default class AddIncomingWebhook extends React.Component {
<input <input
id='description' id='description'
type='text' type='text'
maxLength='128'
className='form-control' className='form-control'
value={this.state.description} value={this.state.description}
onChange={this.updateDescription} onChange={this.updateDescription}

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

@@ -18,14 +18,14 @@ export default class AddOutgoingWebhook extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.updateName = this.updateName.bind(this); this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this); this.updateDescription = this.updateDescription.bind(this);
this.updateChannelId = this.updateChannelId.bind(this); this.updateChannelId = this.updateChannelId.bind(this);
this.updateTriggerWords = this.updateTriggerWords.bind(this); this.updateTriggerWords = this.updateTriggerWords.bind(this);
this.updateCallbackUrls = this.updateCallbackUrls.bind(this); this.updateCallbackUrls = this.updateCallbackUrls.bind(this);
this.state = { this.state = {
name: '', displayName: '',
description: '', description: '',
channelId: '', channelId: '',
triggerWords: '', triggerWords: '',
@@ -80,7 +80,9 @@ export default class AddOutgoingWebhook extends React.Component {
const hook = { const hook = {
channel_id: this.state.channelId, channel_id: this.state.channelId,
trigger_words: this.state.triggerWords.split('\n').map((word) => word.trim()), trigger_words: this.state.triggerWords.split('\n').map((word) => word.trim()),
callback_urls: this.state.callbackUrls.split('\n').map((url) => url.trim()) callback_urls: this.state.callbackUrls.split('\n').map((url) => url.trim()),
display_name: this.state.displayName,
description: this.state.description
}; };
AsyncClient.addOutgoingHook( AsyncClient.addOutgoingHook(
@@ -90,15 +92,16 @@ export default class AddOutgoingWebhook extends React.Component {
}, },
(err) => { (err) => {
this.setState({ this.setState({
saving: false,
serverError: err.message serverError: err.message
}); });
} }
); );
} }
updateName(e) { updateDisplayName(e) {
this.setState({ this.setState({
name: e.target.value displayName: e.target.value
}); });
} }
@@ -144,20 +147,21 @@ export default class AddOutgoingWebhook extends React.Component {
<div className='form-group'> <div className='form-group'>
<label <label
className='control-label col-sm-3' className='control-label col-sm-3'
htmlFor='name' htmlFor='displayName'
> >
<FormattedMessage <FormattedMessage
id='add_outgoing_webhook.name' id='add_outgoing_webhook.displayName'
defaultMessage='Name' defaultMessage='Display Name'
/> />
</label> </label>
<div className='col-md-5 col-sm-9'> <div className='col-md-5 col-sm-9'>
<input <input
id='name' id='displayName'
type='text' type='text'
maxLength='64'
className='form-control' className='form-control'
value={this.state.name} value={this.state.displayName}
onChange={this.updateName} onChange={this.updateDisplayName}
/> />
</div> </div>
</div> </div>
@@ -175,6 +179,7 @@ export default class AddOutgoingWebhook extends React.Component {
<input <input
id='description' id='description'
type='text' type='text'
maxLength='128'
className='form-control' className='form-control'
value={this.state.description} value={this.state.description}
onChange={this.updateDescription} onChange={this.updateDescription}
@@ -213,6 +218,7 @@ export default class AddOutgoingWebhook extends React.Component {
<textarea <textarea
id='triggerWords' id='triggerWords'
rows='3' rows='3'
maxLength='1000'
className='form-control' className='form-control'
value={this.state.triggerWords} value={this.state.triggerWords}
onChange={this.updateTriggerWords} onChange={this.updateTriggerWords}
@@ -233,6 +239,7 @@ export default class AddOutgoingWebhook extends React.Component {
<textarea <textarea
id='callbackUrls' id='callbackUrls'
rows='3' rows='3'
maxLength='1000'
className='form-control' className='form-control'
value={this.state.callbackUrls} value={this.state.callbackUrls}
onChange={this.updateCallbackUrls} onChange={this.updateCallbackUrls}

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

@@ -39,7 +39,7 @@ export default class InstalledIncomingWebhook extends React.Component {
<div className='item-details'> <div className='item-details'>
<div className='item-details__row'> <div className='item-details__row'>
<span className='item-details__name'> <span className='item-details__name'>
{channelName} {incomingWebhook.display_name || channelName}
</span> </span>
<span className='item-details__type'> <span className='item-details__type'>
<FormattedMessage <FormattedMessage
@@ -50,7 +50,19 @@ export default class InstalledIncomingWebhook extends React.Component {
</div> </div>
<div className='item-details__row'> <div className='item-details__row'>
<span className='item-details__description'> <span className='item-details__description'>
{Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id} {incomingWebhook.description}
</span>
</div>
<div className='tem-details__row'>
<span className='item-details__creation'>
<FormattedMessage
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
creator: Utils.displayUsername(incomingWebhook.user_id),
createAt: incomingWebhook.create_at
}}
/>
</span> </span>
</div> </div>
</div> </div>

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

@@ -47,7 +47,7 @@ export default class InstalledOutgoingWebhook extends React.Component {
<div className='item-details'> <div className='item-details'>
<div className='item-details__row'> <div className='item-details__row'>
<span className='item-details__name'> <span className='item-details__name'>
{channelName} {outgoingWebhook.display_name || channelName}
</span> </span>
<span className='item-details__type'> <span className='item-details__type'>
<FormattedMessage <FormattedMessage
@@ -58,9 +58,19 @@ export default class InstalledOutgoingWebhook extends React.Component {
</div> </div>
<div className='item-details__row'> <div className='item-details__row'>
<span className='item-details__description'> <span className='item-details__description'>
{Utils.getWindowLocationOrigin() + '/hooks/' + outgoingWebhook.id} {outgoingWebhook.description}
{' - '} </span>
{outgoingWebhook.token} </div>
<div className='item-details__row'>
<span className='item-details__creation'>
<FormattedMessage
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
creator: Utils.displayUsername(outgoingWebhook.creator_id),
createAt: outgoingWebhook.create_at
}}
/>
</span> </span>
</div> </div>
</div> </div>

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

@@ -814,6 +814,7 @@
"get_team_invite_link_modal.title": "Team Invite Link", "get_team_invite_link_modal.title": "Team Invite Link",
"installed_integrations.add": "Add Integration", "installed_integrations.add": "Add Integration",
"installed_integrations.allFilter": "All ({count})", "installed_integrations.allFilter": "All ({count})",
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
"installed_integrations.delete": "Delete", "installed_integrations.delete": "Delete",
"installed_integrations.header": "Installed Integrations", "installed_integrations.header": "Installed Integrations",
"installed_integrations.incomingWebhookType": "(Incoming Webhook)", "installed_integrations.incomingWebhookType": "(Incoming Webhook)",

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

@@ -196,7 +196,12 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
.list-item__actions { .item-details__creation {
color: $dark-gray;
margin-bottom: 1em;
}
.item-actions {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
padding-left: 20px; padding-left: 20px;

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

@@ -1182,10 +1182,10 @@ export function addIncomingHook(hook, success, error) {
} }
}, },
(err) => { (err) => {
dispatchError(err, 'addIncomingHook');
if (error) { if (error) {
error(err); error(err);
} else {
dispatchError(err, 'addIncomingHook');
} }
} }
); );
@@ -1205,10 +1205,10 @@ export function addOutgoingHook(hook, success, error) {
} }
}, },
(err) => { (err) => {
dispatchError(err, 'addOutgoingHook');
if (error) { if (error) {
error(err); error(err);
} else {
dispatchError(err, 'addOutgoingHook');
} }
} }
); );