summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-04-08 11:51:28 -0400
committerChristopher Speller <crspeller@gmail.com>2016-04-08 11:51:28 -0400
commit77ee1ce7fee698847e211dc15d4673300901aa48 (patch)
tree115391ae591f7e008cf357238be612e7482742fc /webapp
parent742d611ba4c08dbc4d30d3ef7a40a872186bd9eb (diff)
downloadchat-77ee1ce7fee698847e211dc15d4673300901aa48.tar.gz
chat-77ee1ce7fee698847e211dc15d4673300901aa48.tar.bz2
chat-77ee1ce7fee698847e211dc15d4673300901aa48.zip
PLT-2553 Updated backstage page navigation (#2661)
* Updated integrations list based on feedback * Reorganized Integrations pages * Repurposed AddIntegration page as a landing page for Integrations * Moved backstage breadcrumb header into its own component * Removed unnecessary prop * Fixed Save links on AddIntegration pages
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/backstage/add_command.jsx27
-rw-r--r--webapp/components/backstage/add_incoming_webhook.jsx29
-rw-r--r--webapp/components/backstage/add_outgoing_webhook.jsx29
-rw-r--r--webapp/components/backstage/backstage_header.jsx39
-rw-r--r--webapp/components/backstage/backstage_section.jsx1
-rw-r--r--webapp/components/backstage/backstage_sidebar.jsx51
-rw-r--r--webapp/components/backstage/installed_command.jsx64
-rw-r--r--webapp/components/backstage/installed_commands.jsx93
-rw-r--r--webapp/components/backstage/installed_incoming_webhook.jsx67
-rw-r--r--webapp/components/backstage/installed_incoming_webhooks.jsx85
-rw-r--r--webapp/components/backstage/installed_integrations.jsx335
-rw-r--r--webapp/components/backstage/installed_outgoing_webhook.jsx95
-rw-r--r--webapp/components/backstage/installed_outgoing_webhooks.jsx91
-rw-r--r--webapp/components/backstage/integration_option.jsx (renamed from webapp/components/backstage/add_integration_option.jsx)10
-rw-r--r--webapp/components/backstage/integrations.jsx (renamed from webapp/components/backstage/add_integration.jsx)38
-rw-r--r--webapp/i18n/en.json44
-rw-r--r--webapp/root.jsx40
-rw-r--r--webapp/sass/routes/_backstage.scss11
18 files changed, 656 insertions, 493 deletions
diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx
index b6f01b4d8..2eb7bdb21 100644
--- a/webapp/components/backstage/add_command.jsx
+++ b/webapp/components/backstage/add_command.jsx
@@ -7,6 +7,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router';
import * as Utils from 'utils/utils.jsx';
+import BackstageHeader from './backstage_header.jsx';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
import {Link} from 'react-router';
@@ -105,7 +106,7 @@ export default class AddCommand extends React.Component {
AsyncClient.addCommand(
command,
() => {
- browserHistory.push('/settings/integrations/installed');
+ browserHistory.push('/settings/integrations/commands');
},
(err) => {
this.setState({
@@ -249,16 +250,18 @@ export default class AddCommand extends React.Component {
return (
<div className='backstage-content row'>
- <div className='add-command'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='add_command.header'
- defaultMessage='Add Slash Command'
- />
- </h1>
- </div>
- </div>
+ <BackstageHeader>
+ <Link to={'/settings/integrations/commands'}>
+ <FormattedMessage
+ id='installed_command.header'
+ defaultMessage='Slash Commands'
+ />
+ </Link>
+ <FormattedMessage
+ id='add_command.header'
+ defaultMessage='Add'
+ />
+ </BackstageHeader>
<div className='backstage-form'>
<form className='form-horizontal'>
<div className='form-group'>
@@ -479,7 +482,7 @@ export default class AddCommand extends React.Component {
<FormError errors={[this.state.serverError, this.state.clientError]}/>
<Link
className='btn btn-sm'
- to={'/settings/integrations/add'}
+ to={'/settings/integrations/commands'}
>
<FormattedMessage
id='add_command.cancel'
diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx
index b0c16b9ff..f68a263be 100644
--- a/webapp/components/backstage/add_incoming_webhook.jsx
+++ b/webapp/components/backstage/add_incoming_webhook.jsx
@@ -6,6 +6,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router';
+import BackstageHeader from './backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
@@ -68,7 +69,7 @@ export default class AddIncomingWebhook extends React.Component {
AsyncClient.addIncomingHook(
hook,
() => {
- browserHistory.push('/settings/integrations/installed');
+ browserHistory.push('/settings/integrations/incoming_webhooks');
},
(err) => {
this.setState({
@@ -99,17 +100,19 @@ export default class AddIncomingWebhook extends React.Component {
render() {
return (
- <div className='backstage-content row'>
- <div className='add-incoming-webhook'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='add_incoming_webhook.header'
- defaultMessage='Add Incoming Webhook'
- />
- </h1>
- </div>
- </div>
+ <div className='backstage-content'>
+ <BackstageHeader>
+ <Link to={'/settings/integrations/incoming_webhooks'}>
+ <FormattedMessage
+ id='installed_incoming_webhooks.header'
+ defaultMessage='Incoming Webhooks'
+ />
+ </Link>
+ <FormattedMessage
+ id='add_incoming_webhook.header'
+ defaultMessage='Add'
+ />
+ </BackstageHeader>
<div className='backstage-form'>
<form className='form-horizontal'>
<div className='form-group'>
@@ -176,7 +179,7 @@ export default class AddIncomingWebhook extends React.Component {
<FormError errors={[this.state.serverError, this.state.clientError]}/>
<Link
className='btn btn-sm'
- to={'/settings/integrations/add'}
+ to={'/settings/integrations/incoming_webhooks'}
>
<FormattedMessage
id='add_incoming_webhook.cancel'
diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx
index 9d1f79e5d..acdd98ba8 100644
--- a/webapp/components/backstage/add_outgoing_webhook.jsx
+++ b/webapp/components/backstage/add_outgoing_webhook.jsx
@@ -6,6 +6,7 @@ import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router';
+import BackstageHeader from './backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
@@ -88,7 +89,7 @@ export default class AddOutgoingWebhook extends React.Component {
AsyncClient.addOutgoingHook(
hook,
() => {
- browserHistory.push('/settings/integrations/installed');
+ browserHistory.push('/settings/integrations/outgoing_webhooks');
},
(err) => {
this.setState({
@@ -131,17 +132,19 @@ export default class AddOutgoingWebhook extends React.Component {
render() {
return (
- <div className='backstage-content row'>
- <div className='add-outgoing-webhook'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='add_outgoing_webhook.header'
- defaultMessage='Add Outgoing Webhook'
- />
- </h1>
- </div>
- </div>
+ <div className='backstage-content'>
+ <BackstageHeader>
+ <Link to={'/settings/integrations/outgoing_webhooks'}>
+ <FormattedMessage
+ id='installed_outgoing_webhooks.header'
+ defaultMessage='Outgoing Webhooks'
+ />
+ </Link>
+ <FormattedMessage
+ id='add_outgoing_webhook.header'
+ defaultMessage='Add'
+ />
+ </BackstageHeader>
<div className='backstage-form'>
<form className='form-horizontal'>
<div className='form-group'>
@@ -250,7 +253,7 @@ export default class AddOutgoingWebhook extends React.Component {
<FormError errors={[this.state.serverError, this.state.clientError]}/>
<Link
className='btn btn-sm'
- to={'/settings/integrations/add'}
+ to={'/settings/integrations/outgoing_webhooks'}
>
<FormattedMessage
id='add_outgoing_webhook.cancel'
diff --git a/webapp/components/backstage/backstage_header.jsx b/webapp/components/backstage/backstage_header.jsx
new file mode 100644
index 000000000..95b35d7a9
--- /dev/null
+++ b/webapp/components/backstage/backstage_header.jsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+export default class BackstageHeader extends React.Component {
+ static get propTypes() {
+ return {
+ children: React.PropTypes.node
+ };
+ }
+
+ render() {
+ const children = [];
+
+ React.Children.forEach(this.props.children, (child, index) => {
+ if (index !== 0) {
+ children.push(
+ <span
+ key={'divider' + index}
+ className='backstage-header__divider'
+ >
+ {'>'}
+ </span>
+ );
+ }
+
+ children.push(child);
+ });
+
+ return (
+ <div className='backstage-header'>
+ <h1>
+ {children}
+ </h1>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/backstage/backstage_section.jsx b/webapp/components/backstage/backstage_section.jsx
index d6ce2b258..120e956b0 100644
--- a/webapp/components/backstage/backstage_section.jsx
+++ b/webapp/components/backstage/backstage_section.jsx
@@ -65,7 +65,6 @@ export default class BackstageSection extends React.Component {
<Link
className={`${className}-title`}
activeClassName={`${className}-title--active`}
- onlyActiveOnIndex={true}
onClick={this.handleClick}
to={link}
>
diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx
index 172119b32..eb84709a3 100644
--- a/webapp/components/backstage/backstage_sidebar.jsx
+++ b/webapp/components/backstage/backstage_sidebar.jsx
@@ -24,51 +24,32 @@ export default class BackstageSidebar extends React.Component {
}
>
<BackstageSection
- name='installed'
+ name='incoming_webhooks'
title={(
<FormattedMessage
- id='backstage_sidebar.integrations.installed'
- defaultMessage='Installed Integrations'
+ id='backstage_sidebar.integrations.incoming_webhooks'
+ defaultMessage='Incoming Webhooks'
/>
)}
/>
<BackstageSection
- name='add'
+ name='outgoing_webhooks'
title={(
<FormattedMessage
- id='backstage_sidebar.integrations.add'
- defaultMessage='Add Integration'
+ id='backstage_sidebar.integrations.outgoing_webhooks'
+ defaultMessage='Outgoing Webhooks'
/>
)}
- >
- <BackstageSection
- name='incoming_webhook'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.add.incomingWebhook'
- defaultMessage='Incoming Webhook'
- />
- )}
- />
- <BackstageSection
- name='outgoing_webhook'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.add.outgoingWebhook'
- defaultMessage='Outgoing Webhook'
- />
- )}
- />
- <BackstageSection
- name='command'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.add.command'
- defaultMessage='Slash Command'
- />
- )}
- />
- </BackstageSection>
+ />
+ <BackstageSection
+ name='commands'
+ title={(
+ <FormattedMessage
+ id='backstage_sidebar.integrations.commands'
+ defaultMessage='Slash Commands'
+ />
+ )}
+ />
</BackstageCategory>
</ul>
</div>
diff --git a/webapp/components/backstage/installed_command.jsx b/webapp/components/backstage/installed_command.jsx
index 51adce160..8b56ed595 100644
--- a/webapp/components/backstage/installed_command.jsx
+++ b/webapp/components/backstage/installed_command.jsx
@@ -12,7 +12,8 @@ export default class InstalledCommand extends React.Component {
return {
command: React.PropTypes.object.isRequired,
onRegenToken: React.PropTypes.func.isRequired,
- onDelete: React.PropTypes.func.isRequired
+ onDelete: React.PropTypes.func.isRequired,
+ filter: React.PropTypes.string
};
}
@@ -21,6 +22,8 @@ export default class InstalledCommand extends React.Component {
this.handleRegenToken = this.handleRegenToken.bind(this);
this.handleDelete = this.handleDelete.bind(this);
+
+ this.matchesFilter = this.matchesFilter.bind(this);
}
handleRegenToken(e) {
@@ -35,26 +38,67 @@ export default class InstalledCommand extends React.Component {
this.props.onDelete(this.props.command);
}
+ matchesFilter(command, filter) {
+ if (!filter) {
+ return true;
+ }
+
+ return command.display_name.toLowerCase().indexOf(filter) !== -1 ||
+ command.description.toLowerCase().indexOf(filter) !== -1 ||
+ command.trigger.toLowerCase().indexOf(filter) !== -1;
+ }
+
render() {
const command = this.props.command;
+ if (!this.matchesFilter(command, this.props.filter)) {
+ return null;
+ }
+
+ let name;
+ if (command.display_name) {
+ name = command.display_name;
+ } else {
+ name = (
+ <FormattedMessage
+ id='installed_integraions.unnamed_command'
+ defaultMessage='Unnamed Slash Command'
+ />
+ );
+ }
+
+ let description = null;
+ if (command.description) {
+ description = (
+ <div className='item-details__row'>
+ <span className='item-details__description'>
+ {command.description}
+ </span>
+ </div>
+ );
+ }
+
return (
<div className='backstage-list__item'>
<div className='item-details'>
<div className='item-details__row'>
<span className='item-details__name'>
- {command.display_name}
+ {name}
</span>
- <span className='item-details__type'>
- <FormattedMessage
- id='installed_integrations.commandType'
- defaultMessage='(Slash Command)'
- />
+ <span className='item-details__trigger'>
+ {'- /' + command.trigger}
</span>
</div>
+ {description}
<div className='item-details__row'>
- <span className='item-details__description'>
- {command.description}
+ <span className='item-details__token'>
+ <FormattedMessage
+ id='installed_integrations.token'
+ defaultMessage='Token: {token}'
+ values={{
+ token: command.token
+ }}
+ />
</span>
</div>
<div className='item-details__row'>
@@ -63,7 +107,7 @@ export default class InstalledCommand extends React.Component {
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
- creator: Utils.displayUsername(command.creator_Id),
+ creator: Utils.displayUsername(command.creator_id),
createAt: command.create_at
}}
/>
diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/backstage/installed_commands.jsx
new file mode 100644
index 000000000..ead2f9850
--- /dev/null
+++ b/webapp/components/backstage/installed_commands.jsx
@@ -0,0 +1,93 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import IntegrationStore from 'stores/integration_store.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import InstalledCommand from './installed_command.jsx';
+import InstalledIntegrations from './installed_integrations.jsx';
+
+export default class InstalledCommands extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
+
+ this.regenCommandToken = this.regenCommandToken.bind(this);
+ this.deleteCommand = this.deleteCommand.bind(this);
+
+ this.state = {
+ commands: []
+ };
+ }
+
+ componentWillMount() {
+ IntegrationStore.addChangeListener(this.handleIntegrationChange);
+
+ if (window.mm_config.EnableCommands === 'true') {
+ if (IntegrationStore.hasReceivedCommands()) {
+ this.setState({
+ commands: IntegrationStore.getCommands()
+ });
+ } else {
+ AsyncClient.listTeamCommands();
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ }
+
+ handleIntegrationChange() {
+ const commands = IntegrationStore.getCommands();
+
+ this.setState({
+ commands
+ });
+ }
+
+ regenCommandToken(command) {
+ AsyncClient.regenCommandToken(command.id);
+ }
+
+ deleteCommand(command) {
+ AsyncClient.deleteCommand(command.id);
+ }
+
+ render() {
+ const commands = this.state.commands.map((command) => {
+ return (
+ <InstalledCommand
+ key={command.id}
+ command={command}
+ onRegenToken={this.regenCommandToken}
+ onDelete={this.deleteCommand}
+ />
+ );
+ });
+
+ return (
+ <InstalledIntegrations
+ header={
+ <FormattedMessage
+ id='installed_integrations.commands'
+ defaultMessage='Installed Commands'
+ />
+ }
+ addText={
+ <FormattedMessage
+ id='installed_integrations.add_command'
+ defaultMessage='Add Command'
+ />
+ }
+ addLink='/settings/integrations/commands/add'
+ >
+ {commands}
+ </InstalledIntegrations>
+ );
+ }
+}
diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx
index cd9a6d761..58d318310 100644
--- a/webapp/components/backstage/installed_incoming_webhook.jsx
+++ b/webapp/components/backstage/installed_incoming_webhook.jsx
@@ -12,7 +12,8 @@ export default class InstalledIncomingWebhook extends React.Component {
static get propTypes() {
return {
incomingWebhook: React.PropTypes.object.isRequired,
- onDelete: React.PropTypes.func.isRequired
+ onDelete: React.PropTypes.func.isRequired,
+ filter: React.PropTypes.string
};
}
@@ -28,31 +29,67 @@ export default class InstalledIncomingWebhook extends React.Component {
this.props.onDelete(this.props.incomingWebhook);
}
+ matchesFilter(incomingWebhook, channel, filter) {
+ if (!filter) {
+ return true;
+ }
+
+ if (incomingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
+ incomingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+
+ if (incomingWebhook.channel_id) {
+ if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
render() {
const incomingWebhook = this.props.incomingWebhook;
-
const channel = ChannelStore.get(incomingWebhook.channel_id);
- const channelName = channel ? channel.display_name : 'cannot find channel';
+
+ if (!this.matchesFilter(incomingWebhook, channel, this.props.filter)) {
+ return null;
+ }
+
+ let displayName;
+ if (incomingWebhook.display_name) {
+ displayName = incomingWebhook.display_name;
+ } else if (channel) {
+ displayName = channel.display_name;
+ } else {
+ displayName = (
+ <FormattedMessage
+ id='installed_incoming_webhooks.unknown_channel'
+ defaultMessage='A Private Webhook'
+ />
+ );
+ }
+
+ let description = null;
+ if (incomingWebhook.description) {
+ description = (
+ <div className='item-details__row'>
+ <span className='item-details__description'>
+ {incomingWebhook.description}
+ </span>
+ </div>
+ );
+ }
return (
<div className='backstage-list__item'>
<div className='item-details'>
<div className='item-details__row'>
<span className='item-details__name'>
- {incomingWebhook.display_name || channelName}
- </span>
- <span className='item-details__type'>
- <FormattedMessage
- id='installed_integrations.incomingWebhookType'
- defaultMessage='(Incoming Webhook)'
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__description'>
- {incomingWebhook.description}
+ {displayName}
</span>
</div>
+ {description}
<div className='tem-details__row'>
<span className='item-details__creation'>
<FormattedMessage
diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/backstage/installed_incoming_webhooks.jsx
new file mode 100644
index 000000000..de7154afe
--- /dev/null
+++ b/webapp/components/backstage/installed_incoming_webhooks.jsx
@@ -0,0 +1,85 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import IntegrationStore from 'stores/integration_store.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
+import InstalledIntegrations from './installed_integrations.jsx';
+
+export default class InstalledIncomingWebhooks extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
+
+ this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
+
+ this.state = {
+ incomingWebhooks: []
+ };
+ }
+
+ componentWillMount() {
+ IntegrationStore.addChangeListener(this.handleIntegrationChange);
+
+ if (window.mm_config.EnableIncomingWebhooks === 'true') {
+ if (IntegrationStore.hasReceivedIncomingWebhooks()) {
+ this.setState({
+ incomingWebhooks: IntegrationStore.getIncomingWebhooks()
+ });
+ } else {
+ AsyncClient.listIncomingHooks();
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ }
+
+ handleIntegrationChange() {
+ this.setState({
+ incomingWebhooks: IntegrationStore.getIncomingWebhooks()
+ });
+ }
+
+ deleteIncomingWebhook(incomingWebhook) {
+ AsyncClient.deleteIncomingHook(incomingWebhook.id);
+ }
+
+ render() {
+ const incomingWebhooks = this.state.incomingWebhooks.map((incomingWebhook) => {
+ return (
+ <InstalledIncomingWebhook
+ key={incomingWebhook.id}
+ incomingWebhook={incomingWebhook}
+ onDelete={this.deleteIncomingWebhook}
+ />
+ );
+ });
+
+ return (
+ <InstalledIntegrations
+ header={
+ <FormattedMessage
+ id='installed_incoming_webhooks.header'
+ defaultMessage='Installed Incoming Webhooks'
+ />
+ }
+ addText={
+ <FormattedMessage
+ id='installed_incoming_webhooks.add'
+ defaultMessage='Add Incoming Webhook'
+ />
+ }
+ addLink='/settings/integrations/incoming_webhooks/add'
+ >
+ {incomingWebhooks}
+ </InstalledIntegrations>
+ );
+ }
+}
diff --git a/webapp/components/backstage/installed_integrations.jsx b/webapp/components/backstage/installed_integrations.jsx
index e353b7f29..baf74447f 100644
--- a/webapp/components/backstage/installed_integrations.jsx
+++ b/webapp/components/backstage/installed_integrations.jsx
@@ -3,366 +3,65 @@
import React from 'react';
-import * as AsyncClient from 'utils/async_client.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import {FormattedMessage} from 'react-intl';
-import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
-import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
-import InstalledCommand from './installed_command.jsx';
import {Link} from 'react-router';
export default class InstalledIntegrations extends React.Component {
+ static get propTypes() {
+ return {
+ children: React.PropTypes.node,
+ header: React.PropTypes.node.isRequired,
+ addLink: React.PropTypes.string.isRequired,
+ addText: React.PropTypes.node.isRequired
+ };
+ }
+
constructor(props) {
super(props);
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
this.updateFilter = this.updateFilter.bind(this);
- this.updateTypeFilter = this.updateTypeFilter.bind(this);
-
- this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
- this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
- this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
- this.regenCommandToken = this.regenCommandToken.bind(this);
- this.deleteCommand = this.deleteCommand.bind(this);
this.state = {
- incomingWebhooks: [],
- outgoingWebhooks: [],
- commands: [],
- typeFilter: '',
filter: ''
};
}
- componentWillMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
-
- if (window.mm_config.EnableIncomingWebhooks === 'true') {
- if (IntegrationStore.hasReceivedIncomingWebhooks()) {
- this.setState({
- incomingWebhooks: IntegrationStore.getIncomingWebhooks()
- });
- } else {
- AsyncClient.listIncomingHooks();
- }
- }
-
- if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- if (IntegrationStore.hasReceivedOutgoingWebhooks()) {
- this.setState({
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
- });
- } else {
- AsyncClient.listOutgoingHooks();
- }
- }
-
- if (window.mm_config.EnableCommands === 'true') {
- if (IntegrationStore.hasReceivedCommands()) {
- this.setState({
- commands: IntegrationStore.getCommands()
- });
- } else {
- AsyncClient.listTeamCommands();
- }
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- }
-
- handleIntegrationChange() {
- const incomingWebhooks = IntegrationStore.getIncomingWebhooks();
- const outgoingWebhooks = IntegrationStore.getOutgoingWebhooks();
- const commands = IntegrationStore.getCommands();
-
- this.setState({
- incomingWebhooks,
- outgoingWebhooks,
- commands
- });
-
- // reset the type filter if we were viewing a category that is now empty
- if ((this.state.typeFilter === 'incomingWebhooks' && incomingWebhooks.length === 0) ||
- (this.state.typeFilter === 'outgoingWebhooks' && outgoingWebhooks.length === 0) ||
- (this.state.typeFilter === 'commands' && commands.length === 0)) {
- this.setState({
- typeFilter: ''
- });
- }
- }
-
- updateTypeFilter(e, typeFilter) {
- e.preventDefault();
-
- this.setState({
- typeFilter
- });
- }
-
updateFilter(e) {
this.setState({
filter: e.target.value
});
}
- deleteIncomingWebhook(incomingWebhook) {
- AsyncClient.deleteIncomingHook(incomingWebhook.id);
- }
-
- regenOutgoingWebhookToken(outgoingWebhook) {
- AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
- }
-
- deleteOutgoingWebhook(outgoingWebhook) {
- AsyncClient.deleteOutgoingHook(outgoingWebhook.id);
- }
-
- regenCommandToken(command) {
- AsyncClient.regenCommandToken(command.id);
- }
-
- deleteCommand(command) {
- AsyncClient.deleteCommand(command.id);
- }
-
- renderTypeFilters(incomingWebhooks, outgoingWebhooks, commands) {
- const fields = [];
-
- if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0 || commands.length > 0) {
- let filterClassName = 'filter-sort';
- if (this.state.typeFilter === '') {
- filterClassName += ' filter-sort--active';
- }
-
- fields.push(
- <a
- key='allFilter'
- className={filterClassName}
- href='#'
- onClick={(e) => this.updateTypeFilter(e, '')}
- >
- <FormattedMessage
- id='installed_integrations.allFilter'
- defaultMessage='All ({count})'
- values={{
- count: incomingWebhooks.length + outgoingWebhooks.length
- }}
- />
- </a>
- );
- }
-
- if (incomingWebhooks.length > 0) {
- fields.push(
- <span
- key='incomingWebhooksDivider'
- className='divider'
- >
- {'|'}
- </span>
- );
-
- let filterClassName = 'filter-sort';
- if (this.state.typeFilter === 'incomingWebhooks') {
- filterClassName += ' filter-sort--active';
- }
-
- fields.push(
- <a
- key='incomingWebhooksFilter'
- className={filterClassName}
- href='#'
- onClick={(e) => this.updateTypeFilter(e, 'incomingWebhooks')}
- >
- <FormattedMessage
- id='installed_integrations.incomingWebhooksFilter'
- defaultMessage='Incoming Webhooks ({count})'
- values={{
- count: incomingWebhooks.length
- }}
- />
- </a>
- );
- }
-
- if (outgoingWebhooks.length > 0) {
- fields.push(
- <span
- key='outgoingWebhooksDivider'
- className='divider'
- >
- {'|'}
- </span>
- );
-
- let filterClassName = 'filter-sort';
- if (this.state.typeFilter === 'outgoingWebhooks') {
- filterClassName += ' filter-sort--active';
- }
-
- fields.push(
- <a
- key='outgoingWebhooksFilter'
- className={filterClassName}
- href='#'
- onClick={(e) => this.updateTypeFilter(e, 'outgoingWebhooks')}
- >
- <FormattedMessage
- id='installed_integrations.outgoingWebhooksFilter'
- defaultMessage='Outgoing Webhooks ({count})'
- values={{
- count: outgoingWebhooks.length
- }}
- />
- </a>
- );
- }
-
- if (commands.length > 0) {
- fields.push(
- <span
- key='commandsDivider'
- className='divider'
- >
- {'|'}
- </span>
- );
-
- let filterClassName = 'filter-sort';
- if (this.state.typeFilter === 'commands') {
- filterClassName += ' filter-sort--active';
- }
-
- fields.push(
- <a
- key='commandsFilter'
- className={filterClassName}
- href='#'
- onClick={(e) => this.updateTypeFilter(e, 'commands')}
- >
- <FormattedMessage
- id='installed_integrations.commandsFilter'
- defaultMessage='Slash Commands ({count})'
- values={{
- count: commands.length
- }}
- />
- </a>
- );
- }
-
- return (
- <div className='backstage-filters__sort'>
- {fields}
- </div>
- );
- }
-
render() {
- const incomingWebhooks = this.state.incomingWebhooks;
- const outgoingWebhooks = this.state.outgoingWebhooks;
- const commands = this.state.commands;
-
- // TODO description, name, creator filtering
const filter = this.state.filter.toLowerCase();
- const integrations = [];
- if (!this.state.typeFilter || this.state.typeFilter === 'incomingWebhooks') {
- for (const incomingWebhook of incomingWebhooks) {
- if (filter) {
- const channel = ChannelStore.get(incomingWebhook.channel_id);
-
- if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
- continue;
- }
- }
-
- integrations.push(
- <InstalledIncomingWebhook
- key={incomingWebhook.id}
- incomingWebhook={incomingWebhook}
- onDelete={this.deleteIncomingWebhook}
- />
- );
- }
- }
-
- if (!this.state.typeFilter || this.state.typeFilter === 'outgoingWebhooks') {
- for (const outgoingWebhook of outgoingWebhooks) {
- if (filter) {
- const channel = ChannelStore.get(outgoingWebhook.channel_id);
-
- if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
- continue;
- }
- }
-
- integrations.push(
- <InstalledOutgoingWebhook
- key={outgoingWebhook.id}
- outgoingWebhook={outgoingWebhook}
- onRegenToken={this.regenOutgoingWebhookToken}
- onDelete={this.deleteOutgoingWebhook}
- />
- );
- }
- }
-
- if (!this.state.typeFilter || this.state.typeFilter === 'commands') {
- for (const command of commands) {
- if (filter) {
- const channel = ChannelStore.get(command.channel_id);
-
- if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
- continue;
- }
- }
-
- integrations.push(
- <InstalledCommand
- key={command.id}
- command={command}
- onRegenToken={this.regenCommandToken}
- onDelete={this.deleteCommand}
- />
- );
- }
- }
+ const children = React.Children.map(this.props.children, (child) => {
+ return React.cloneElement(child, {filter});
+ });
return (
- <div className='backstage-content row'>
+ <div className='backstage-content'>
<div className='installed-integrations'>
<div className='backstage-header'>
<h1>
- <FormattedMessage
- id='installed_integrations.header'
- defaultMessage='Installed Integrations'
- />
+ {this.props.header}
</h1>
<Link
className='add-integrations-link'
- to={'/settings/integrations/add'}
+ to={this.props.addLink}
>
<button
type='button'
className='btn btn-primary'
>
<span>
- <FormattedMessage
- id='installed_integrations.add'
- defaultMessage='Add Integration'
- />
+ {this.props.addText}
</span>
</button>
</Link>
</div>
<div className='backstage-filters'>
- {this.renderTypeFilters(incomingWebhooks, outgoingWebhooks, commands)}
<div className='backstage-filter__search'>
<i className='fa fa-search'></i>
<input
@@ -376,7 +75,7 @@ export default class InstalledIntegrations extends React.Component {
</div>
</div>
<div className='backstage-list'>
- {integrations}
+ {children}
</div>
</div>
</div>
diff --git a/webapp/components/backstage/installed_outgoing_webhook.jsx b/webapp/components/backstage/installed_outgoing_webhook.jsx
index 530474dc3..b8704ccef 100644
--- a/webapp/components/backstage/installed_outgoing_webhook.jsx
+++ b/webapp/components/backstage/installed_outgoing_webhook.jsx
@@ -13,7 +13,8 @@ export default class InstalledOutgoingWebhook extends React.Component {
return {
outgoingWebhook: React.PropTypes.object.isRequired,
onRegenToken: React.PropTypes.func.isRequired,
- onDelete: React.PropTypes.func.isRequired
+ onDelete: React.PropTypes.func.isRequired,
+ filter: React.PropTypes.string
};
}
@@ -36,29 +37,82 @@ export default class InstalledOutgoingWebhook extends React.Component {
this.props.onDelete(this.props.outgoingWebhook);
}
+ matchesFilter(outgoingWebhook, channel, filter) {
+ if (!filter) {
+ return true;
+ }
+
+ if (outgoingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
+ outgoingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+
+ for (const trigger of outgoingWebhook.trigger_words) {
+ if (trigger.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+ }
+
+ if (channel) {
+ if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
render() {
const outgoingWebhook = this.props.outgoingWebhook;
-
const channel = ChannelStore.get(outgoingWebhook.channel_id);
- const channelName = channel ? channel.display_name : 'cannot find channel';
+
+ if (!this.matchesFilter(outgoingWebhook, channel, this.props.filter)) {
+ return null;
+ }
+
+ let displayName;
+ if (outgoingWebhook.display_name) {
+ displayName = outgoingWebhook.display_name;
+ } else if (channel) {
+ displayName = channel.display_name;
+ } else {
+ displayName = (
+ <FormattedMessage
+ id='installed_outgoing_webhooks.unknown_channel'
+ defaultMessage='A Private Webhook'
+ />
+ );
+ }
+
+ let description = null;
+ if (outgoingWebhook.description) {
+ description = (
+ <div className='item-details__row'>
+ <span className='item-details__description'>
+ {outgoingWebhook.description}
+ </span>
+ </div>
+ );
+ }
return (
<div className='backstage-list__item'>
<div className='item-details'>
<div className='item-details__row'>
<span className='item-details__name'>
- {outgoingWebhook.display_name || channelName}
- </span>
- <span className='item-details__type'>
- <FormattedMessage
- id='installed_integrations.outgoingWebhookType'
- defaultMessage='(Outgoing Webhook)'
- />
+ {displayName}
</span>
</div>
+ {description}
<div className='item-details__row'>
- <span className='item-details__description'>
- {outgoingWebhook.description}
+ <span className='item-details__token'>
+ <FormattedMessage
+ id='installed_integrations.token'
+ defaultMessage='Token: {token}'
+ values={{
+ token: outgoingWebhook.token
+ }}
+ />
</span>
</div>
<div className='item-details__row'>
@@ -98,4 +152,21 @@ export default class InstalledOutgoingWebhook extends React.Component {
</div>
);
}
+
+ static matches(outgoingWebhook, filter) {
+ if (outgoingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
+ outgoingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+
+ if (outgoingWebhook.channel_id) {
+ const channel = ChannelStore.get(outgoingWebhook.channel_id);
+
+ if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/backstage/installed_outgoing_webhooks.jsx
new file mode 100644
index 000000000..15d927a41
--- /dev/null
+++ b/webapp/components/backstage/installed_outgoing_webhooks.jsx
@@ -0,0 +1,91 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import IntegrationStore from 'stores/integration_store.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
+import InstalledIntegrations from './installed_integrations.jsx';
+
+export default class InstalledOutgoingWebhooks extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
+
+ this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
+ this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
+
+ this.state = {
+ outgoingWebhooks: []
+ };
+ }
+
+ componentWillMount() {
+ IntegrationStore.addChangeListener(this.handleIntegrationChange);
+
+ if (window.mm_config.EnableOutgoingWebhooks === 'true') {
+ if (IntegrationStore.hasReceivedOutgoingWebhooks()) {
+ this.setState({
+ outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
+ });
+ } else {
+ AsyncClient.listOutgoingHooks();
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ }
+
+ handleIntegrationChange() {
+ this.setState({
+ outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
+ });
+ }
+
+ regenOutgoingWebhookToken(outgoingWebhook) {
+ AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
+ }
+
+ deleteOutgoingWebhook(outgoingWebhook) {
+ AsyncClient.deleteOutgoingHook(outgoingWebhook.id);
+ }
+
+ render() {
+ const outgoingWebhooks = this.state.outgoingWebhooks.map((outgoingWebhook) => {
+ return (
+ <InstalledOutgoingWebhook
+ key={outgoingWebhook.id}
+ outgoingWebhook={outgoingWebhook}
+ onRegenToken={this.regenOutgoingWebhookToken}
+ onDelete={this.deleteOutgoingWebhook}
+ />
+ );
+ });
+
+ return (
+ <InstalledIntegrations
+ header={
+ <FormattedMessage
+ id='installed_outgoing_webhooks.header'
+ defaultMessage='Installed Outgoing Webhooks'
+ />
+ }
+ addText={
+ <FormattedMessage
+ id='installed_outgoing_webhooks.add'
+ defaultMessage='Add Outgoing Webhook'
+ />
+ }
+ addLink='/settings/integrations/outgoing_webhooks/add'
+ >
+ {outgoingWebhooks}
+ </InstalledIntegrations>
+ );
+ }
+}
diff --git a/webapp/components/backstage/add_integration_option.jsx b/webapp/components/backstage/integration_option.jsx
index b17ebb185..dd7cc0c4c 100644
--- a/webapp/components/backstage/add_integration_option.jsx
+++ b/webapp/components/backstage/integration_option.jsx
@@ -5,7 +5,7 @@ import React from 'react';
import {Link} from 'react-router';
-export default class AddIntegrationOption extends React.Component {
+export default class IntegrationOption extends React.Component {
static get propTypes() {
return {
image: React.PropTypes.string.isRequired,
@@ -21,16 +21,16 @@ export default class AddIntegrationOption extends React.Component {
return (
<Link
to={link}
- className='add-integration'
+ className='integration-option'
>
<img
- className='add-integration__image'
+ className='integration-option__image'
src={image}
/>
- <div className='add-integration__title'>
+ <div className='integration-option__title'>
{title}
</div>
- <div className='add-integration__description'>
+ <div className='integration-option__description'>
{description}
</div>
</Link>
diff --git a/webapp/components/backstage/add_integration.jsx b/webapp/components/backstage/integrations.jsx
index 0ab36e101..71232ea45 100644
--- a/webapp/components/backstage/add_integration.jsx
+++ b/webapp/components/backstage/integrations.jsx
@@ -4,76 +4,76 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
-import AddIntegrationOption from './add_integration_option.jsx';
+import IntegrationOption from './integration_option.jsx';
import WebhookIcon from 'images/webhook_icon.jpg';
-export default class AddIntegration extends React.Component {
+export default class Integrations extends React.Component {
render() {
const options = [];
if (window.mm_config.EnableIncomingWebhooks === 'true') {
options.push(
- <AddIntegrationOption
+ <IntegrationOption
key='incomingWebhook'
image={WebhookIcon}
title={
<FormattedMessage
- id='add_integration.incomingWebhook.title'
+ id='integrations.incomingWebhook.title'
defaultMessage='Incoming Webhook'
/>
}
description={
<FormattedMessage
- id='add_integration.incomingWebhook.description'
- defaultMessage='Create webhook URLs for use in external integrations.'
+ id='integrations.incomingWebhook.description'
+ defaultMessage='Incoming webhooks allow external integrations to send messages'
/>
}
- link={'/settings/integrations/add/incoming_webhook'}
+ link={'/settings/integrations/incoming_webhooks'}
/>
);
}
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
options.push(
- <AddIntegrationOption
+ <IntegrationOption
key='outgoingWebhook'
image={WebhookIcon}
title={
<FormattedMessage
- id='add_integration.outgoingWebhook.title'
+ id='integrations.outgoingWebhook.title'
defaultMessage='Outgoing Webhook'
/>
}
description={
<FormattedMessage
- id='add_integration.outgoingWebhook.description'
- defaultMessage='Create webhooks to send new message events to an external integration.'
+ id='integrations.outgoingWebhook.description'
+ defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages'
/>
}
- link={'/settings/integrations/add/outgoing_webhook'}
+ link={'/settings/integrations/outgoing_webhooks'}
/>
);
}
if (window.mm_config.EnableCommands === 'true') {
options.push(
- <AddIntegrationOption
+ <IntegrationOption
key='command'
image={WebhookIcon}
title={
<FormattedMessage
- id='add_integration.command.title'
+ id='integrations.command.title'
defaultMessage='Slash Command'
/>
}
description={
<FormattedMessage
- id='add_integration.command.description'
- defaultMessage='Create slash commands to send events to external integrations and receive a response.'
+ id='integrations.command.description'
+ defaultMessage='Slash commands send events to an external integration'
/>
}
- link={'/settings/integrations/add/command'}
+ link={'/settings/integrations/commands'}
/>
);
}
@@ -83,8 +83,8 @@ export default class AddIntegration extends React.Component {
<div className='backstage-header'>
<h1>
<FormattedMessage
- id='add_integration.header'
- defaultMessage='Add Integration'
+ id='integrations.header'
+ defaultMessage='Integrations'
/>
</h1>
</div>
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 12671284a..fd8f44c36 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -37,7 +37,7 @@
"add_command.autocompleteHint.placeholder": "Example: [Patient Name]",
"add_command.description": "Description",
"add_command.displayName": "Display Name",
- "add_command.header": "Add Slash Command",
+ "add_command.header": "Add",
"add_command.iconUrl": "Response Icon",
"add_command.iconUrl.help": "Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.",
"add_command.iconUrl.placeholder": "https://www.example.com/myicon.png",
@@ -61,22 +61,15 @@
"add_incoming_webhook.channel": "Channel",
"add_incoming_webhook.channelRequired": "A valid channel is required",
"add_incoming_webhook.description": "Description",
- "add_incoming_webhook.header": "Add Incoming Webhook",
+ "add_incoming_webhook.header": "Add",
"add_incoming_webhook.name": "Name",
"add_incoming_webhook.save": "Save",
- "add_integration.command.description": "Create slash commands to send events to external integrations and receive a response.",
- "add_integration.command.title": "Slash Command",
- "add_integration.header": "Add Integration",
- "add_integration.incomingWebhook.description": "Create webhook URLs for use in external integrations.",
- "add_integration.incomingWebhook.title": "Incoming Webhook",
- "add_integration.outgoingWebhook.description": "Create webhooks to send new message events to an external integration.",
- "add_integration.outgoingWebhook.title": "Outgoing Webhook",
"add_outgoing_webhook.callbackUrls": "Callback URLs (One Per Line)",
"add_outgoing_webhook.callbackUrlsRequired": "One or more callback URLs are required",
"add_outgoing_webhook.cancel": "Cancel",
"add_outgoing_webhook.channel": "Channel",
"add_outgoing_webhook.description": "Description",
- "add_outgoing_webhook.header": "Add Outgoing Webhook",
+ "add_outgoing_webhook.header": "Add",
"add_outgoing_webhook.name": "Name",
"add_outgoing_webhook.save": "Save",
"add_outgoing_webhook.triggerWOrds": "Trigger Words (One Per Line)",
@@ -624,11 +617,9 @@
"authorize.title": "An application would like to connect to your {teamName} account",
"backstage_navbar.backToMattermost": "Back to {siteName}",
"backstage_sidebar.integrations": "Integrations",
- "backstage_sidebar.integrations.add": "Add Integration",
- "backstage_sidebar.integrations.add.command": "Slash Command",
- "backstage_sidebar.integrations.add.incomingWebhook": "Incoming Webhook",
- "backstage_sidebar.integrations.add.outgoingWebhook": "Outgoing Webhook",
- "backstage_sidebar.integrations.installed": "Installed Integrations",
+ "backstage_sidebar.integrations.incoming_webhooks": "Incoming Webhooks",
+ "backstage_sidebar.integrations.outgoing_webhooks": "Outgoing Webhooks",
+ "backstage_sidebar.integrations.commands": "Commands",
"center_panel.recent": "Click here to jump to recent messages. ",
"chanel_header.addMembers": "Add Members",
"change_url.close": "Close",
@@ -850,19 +841,24 @@
"get_team_invite_link_modal.help": "Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it's regenerated in Team Settings by a Team Admin.",
"get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
"get_team_invite_link_modal.title": "Team Invite Link",
- "installed_integrations.add": "Add Integration",
- "installed_integrations.allFilter": "All ({count})",
- "installed_integrations.commandType": "(Slash Command)",
- "installed_integrations.commandsFilter": "Slash Commands ({count})",
+ "installed_commands.add": "Add Slash Command",
+ "installed_commands.header": "Slash Commands",
+ "installed_incoming_webhooks.add": "Add Incoming Webhook",
+ "installed_incoming_webhooks.header": "Incoming Webhooks",
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
"installed_integrations.delete": "Delete",
- "installed_integrations.header": "Installed Integrations",
- "installed_integrations.incomingWebhookType": "(Incoming Webhook)",
- "installed_integrations.incomingWebhooksFilter": "Incoming Webhooks ({count})",
- "installed_integrations.outgoingWebhookType": "(Outgoing Webhook)",
- "installed_integrations.outgoingWebhooksFilter": "Outgoing Webhooks ({count})",
"installed_integrations.regenToken": "Regen Token",
"installed_integrations.search": "Search Integrations",
+ "installed_integrations.token": "Token: {token}",
+ "installed_outgoing_webhooks.add": "Add Outgoing Webhook",
+ "installed_outgoing_webhooks.header": "Outgoing Webhooks",
+ "integrations.command.description": "Slash commands send events to external integrations",
+ "integrations.command.title": "Slash Command",
+ "integrations.header": "Integrations",
+ "integrations.incomingWebhook.description": "Incoming webhooks allow external integrations to send messages",
+ "integrations.incomingWebhook.title": "Incoming Webhook",
+ "integrations.outgoingWebhook.description": "Outgoing webhooks allow external integrations to receive and respond to messages",
+ "integrations.outgoingWebhook.title": "Outgoing Webhook",
"intro_messages.DM": "This is the start of your direct message history with {teammate}.<br />Direct messages and files shared here are not shown to people outside this area.",
"intro_messages.anyMember": " Any member can join and read this channel.",
"intro_messages.beginning": "Beginning of {name}",
diff --git a/webapp/root.jsx b/webapp/root.jsx
index a76f7cf7e..9268643f3 100644
--- a/webapp/root.jsx
+++ b/webapp/root.jsx
@@ -38,8 +38,10 @@ import AdminConsole from 'components/admin_console/admin_controller.jsx';
import TutorialView from 'components/tutorial/tutorial_view.jsx';
import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
-import InstalledIntegrations from 'components/backstage/installed_integrations.jsx';
-import AddIntegration from 'components/backstage/add_integration.jsx';
+import Integrations from 'components/backstage/integrations.jsx';
+import InstalledIncomingWebhooks from 'components/backstage/installed_incoming_webhooks.jsx';
+import InstalledOutgoingWebhooks from 'components/backstage/installed_outgoing_webhooks.jsx';
+import InstalledCommands from 'components/backstage/installed_commands.jsx';
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx';
import AddCommand from 'components/backstage/add_command.jsx';
@@ -253,41 +255,57 @@ function renderRootComponent() {
onEnter={onLoggedOut}
/>
<Route path='settings/integrations'>
- <IndexRedirect to='installed'/>
- <Route
- path='installed'
+ <IndexRoute
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
- center: InstalledIntegrations
+ center: Integrations
}}
/>
- <Route path='add'>
+ <Route path='incoming_webhooks'>
<IndexRoute
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
- center: AddIntegration
+ center: InstalledIncomingWebhooks
}}
/>
<Route
- path='incoming_webhook'
+ path='add'
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
center: AddIncomingWebhook
}}
/>
+ </Route>
+ <Route path='outgoing_webhooks'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: InstalledOutgoingWebhooks
+ }}
+ />
<Route
- path='outgoing_webhook'
+ path='add'
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
center: AddOutgoingWebhook
}}
/>
+ </Route>
+ <Route path='commands'>
+ <IndexRoute
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: InstalledCommands
+ }}
+ />
<Route
- path='command'
+ path='add'
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
diff --git a/webapp/sass/routes/_backstage.scss b/webapp/sass/routes/_backstage.scss
index 4b0c07b7e..f6e0a8ac0 100644
--- a/webapp/sass/routes/_backstage.scss
+++ b/webapp/sass/routes/_backstage.scss
@@ -207,11 +207,12 @@ body {
font-weight: 600;
}
- .item-details__type {
+ .item-details__trigger {
margin-left: 6px;
}
.item-details__description,
+ .item-details__token,
.item-details__creation {
color: $dark-gray;
display: inline-block;
@@ -283,7 +284,7 @@ body {
}
}
-.add-integration {
+.integration-option {
background-color: $white;
border: 1px solid $light-gray;
display: inline-block;
@@ -300,16 +301,16 @@ body {
}
}
-.add-integration__image {
+.integration-option__image {
height: 80px;
width: 80px;
}
-.add-integration__title {
+.integration-option__title {
color: $black;
margin-bottom: 10px;
}
-.add-integration__description {
+.integration-option__description {
color: $dark-gray;
}