From 28e0b8dc27e71c4b383f49db3be2a31088924bf1 Mon Sep 17 00:00:00 2001 From: 94117nl Date: Wed, 28 Jun 2017 09:38:57 -0500 Subject: PLT-6456 Migrate installed_commands.jsx to be pure and use Redux (#6759) * Add documentation to props, migrate to pure component * Migrate commands_container and installed_commands to redux * Partially move confirm_integration to redux * Add more props to commands_container * Fix identation issue * Remove unused import * Update command token to reference redux store --- .../commands_container/commands_container.jsx | 78 +++++++ .../components/commands_container/index.js | 29 +++ .../components/confirm_integration.jsx | 258 --------------------- .../confirm_integration/confirm_integration.jsx | 258 +++++++++++++++++++++ .../components/confirm_integration/index.js | 16 ++ .../integrations/components/installed_commands.jsx | 121 ---------- .../components/installed_commands/index.js | 25 ++ .../installed_commands/installed_commands.jsx | 148 ++++++++++++ webapp/routes/route_integrations.jsx | 8 +- 9 files changed, 558 insertions(+), 383 deletions(-) create mode 100644 webapp/components/integrations/components/commands_container/commands_container.jsx create mode 100644 webapp/components/integrations/components/commands_container/index.js delete mode 100644 webapp/components/integrations/components/confirm_integration.jsx create mode 100644 webapp/components/integrations/components/confirm_integration/confirm_integration.jsx create mode 100644 webapp/components/integrations/components/confirm_integration/index.js delete mode 100644 webapp/components/integrations/components/installed_commands.jsx create mode 100644 webapp/components/integrations/components/installed_commands/index.js create mode 100644 webapp/components/integrations/components/installed_commands/installed_commands.jsx diff --git a/webapp/components/integrations/components/commands_container/commands_container.jsx b/webapp/components/integrations/components/commands_container/commands_container.jsx new file mode 100644 index 000000000..55ce2017b --- /dev/null +++ b/webapp/components/integrations/components/commands_container/commands_container.jsx @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class CommandsContainer extends React.PureComponent { + static propTypes = { + + /** + * The team data needed to pass into child components + */ + team: PropTypes.object, + + /** + * The user data needed to pass into child components + */ + user: PropTypes.object, + + /** + * The children prop needed to render child component + */ + children: PropTypes.node.isRequired, + + /** + * Set if user is admin + */ + isAdmin: PropTypes.bool, + + /** + * The users collection + */ + users: PropTypes.object, + + /** + * Installed splash commands to display + */ + commands: PropTypes.array, + + actions: PropTypes.shape({ + + /** + * The function to call to fetch team commands + */ + getCustomTeamCommands: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + this.state = { + loading: true + }; + } + + componentDidMount() { + if (window.mm_config.EnableCommands === 'true') { + this.props.actions.getCustomTeamCommands(this.props.team.id).then( + () => this.setState({loading: false}) + ); + } + } + + render() { + return ( +
+ {React.cloneElement(this.props.children, { + loading: this.state.loading, + commands: this.props.commands || [], + users: this.props.users, + team: this.props.team, + user: this.props.user, + isAdmin: this.props.isAdmin + })} +
+ ); + } +} diff --git a/webapp/components/integrations/components/commands_container/index.js b/webapp/components/integrations/components/commands_container/index.js new file mode 100644 index 000000000..ed0a4c138 --- /dev/null +++ b/webapp/components/integrations/components/commands_container/index.js @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getCustomTeamCommands} from 'mattermost-redux/actions/integrations'; + +import {getCommands} from 'mattermost-redux/selectors/entities/integrations'; +import {getUsers} from 'mattermost-redux/selectors/entities/users'; + +import CommandsContainer from './commands_container.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + commands: Object.values(getCommands(state)), + users: getUsers(state) + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getCustomTeamCommands + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(CommandsContainer); \ No newline at end of file diff --git a/webapp/components/integrations/components/confirm_integration.jsx b/webapp/components/integrations/components/confirm_integration.jsx deleted file mode 100644 index 30f6034d5..000000000 --- a/webapp/components/integrations/components/confirm_integration.jsx +++ /dev/null @@ -1,258 +0,0 @@ -import PropTypes from 'prop-types'; - -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; -import {Link, browserHistory} from 'react-router/es6'; - -import UserStore from 'stores/user_store.jsx'; -import IntegrationStore from 'stores/integration_store.jsx'; - -import Constants from 'utils/constants.jsx'; - -export default class ConfirmIntegration extends React.Component { - static get propTypes() { - return { - team: PropTypes.object, - location: PropTypes.object, - loading: PropTypes.bool - }; - } - - constructor(props) { - super(props); - - this.handleIntegrationChange = this.handleIntegrationChange.bind(this); - this.handleKeyPress = this.handleKeyPress.bind(this); - - const userId = UserStore.getCurrentId(); - - this.state = { - type: this.props.location.query.type, - id: this.props.location.query.id, - oauthApps: IntegrationStore.getOAuthApps(userId), - loading: !IntegrationStore.hasReceivedOAuthApps(userId) - }; - } - - componentDidMount() { - IntegrationStore.addChangeListener(this.handleIntegrationChange); - window.addEventListener('keypress', this.handleKeyPress); - } - - componentWillUnmount() { - IntegrationStore.removeChangeListener(this.handleIntegrationChange); - window.removeEventListener('keypress', this.handleKeyPress); - } - - handleIntegrationChange() { - const userId = UserStore.getCurrentId(); - - this.setState({ - oauthApps: IntegrationStore.getOAuthApps(userId), - loading: !IntegrationStore.hasReceivedOAuthApps(userId) - }); - } - - handleKeyPress(e) { - if (e.key === 'Enter') { - browserHistory.push('/' + this.props.team.name + '/integrations/' + this.state.type); - } - } - - render() { - let headerText = null; - let helpText = null; - let tokenText = null; - - if (this.props.loading === true) { - return (
); - } - - if (this.state.type === Constants.Integrations.COMMAND) { - headerText = ( - - ); - helpText = ( -

- -

- ); - tokenText = ( -

- -

- ); - } else if (this.state.type === Constants.Integrations.INCOMING_WEBHOOK) { - headerText = ( - - ); - helpText = ( -

- -

- ); - tokenText = ( -

- -

- ); - } else if (this.state.type === Constants.Integrations.OUTGOING_WEBHOOK) { - headerText = ( - - ); - helpText = ( -

- -

- ); - tokenText = ( -

- -

- ); - } else if (this.state.type === Constants.Integrations.OAUTH_APP) { - let oauthApp = {}; - for (var i = 0; i < this.state.oauthApps.length; i++) { - if (this.state.oauthApps[i].id === this.state.id) { - oauthApp = this.state.oauthApps[i]; - break; - } - } - - if (oauthApp) { - headerText = ( - - ); - - helpText = []; - helpText.push( -

- -

- ); - helpText.push( -

-
- -

- ); - - helpText.push( -

- -

- ); - - tokenText = ( -

- -

- ); - } - } - - return ( -
- - - {headerText} - - - -
-

- -

- {helpText} - {tokenText} -
- - - -
-
-
- ); - } -} diff --git a/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx b/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx new file mode 100644 index 000000000..05dd61efb --- /dev/null +++ b/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx @@ -0,0 +1,258 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; + +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; +import {Link, browserHistory} from 'react-router/es6'; + +import UserStore from 'stores/user_store.jsx'; +import IntegrationStore from 'stores/integration_store.jsx'; + +import Constants from 'utils/constants.jsx'; + +export default class ConfirmIntegration extends React.Component { + static get propTypes() { + return { + team: PropTypes.object, + location: PropTypes.object, + commands: PropTypes.object, + loading: PropTypes.bool + }; + } + + constructor(props) { + super(props); + + this.handleIntegrationChange = this.handleIntegrationChange.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + + const userId = UserStore.getCurrentId(); + + this.state = { + type: this.props.location.query.type, + id: this.props.location.query.id, + oauthApps: IntegrationStore.getOAuthApps(userId), + loading: !IntegrationStore.hasReceivedOAuthApps(userId) + }; + } + + componentDidMount() { + IntegrationStore.addChangeListener(this.handleIntegrationChange); + window.addEventListener('keypress', this.handleKeyPress); + } + + componentWillUnmount() { + IntegrationStore.removeChangeListener(this.handleIntegrationChange); + window.removeEventListener('keypress', this.handleKeyPress); + } + + handleIntegrationChange() { + const userId = UserStore.getCurrentId(); + + this.setState({ + oauthApps: IntegrationStore.getOAuthApps(userId), + loading: !IntegrationStore.hasReceivedOAuthApps(userId) + }); + } + + handleKeyPress(e) { + if (e.key === 'Enter') { + browserHistory.push('/' + this.props.team.name + '/integrations/' + this.state.type); + } + } + + render() { + let headerText = null; + let helpText = null; + let tokenText = null; + + if (this.props.loading === true) { + return (
); + } + + if (this.state.type === Constants.Integrations.COMMAND) { + headerText = ( + + ); + helpText = ( +

+ +

+ ); + tokenText = ( +

+ +

+ ); + } else if (this.state.type === Constants.Integrations.INCOMING_WEBHOOK) { + headerText = ( + + ); + helpText = ( +

+ +

+ ); + tokenText = ( +

+ +

+ ); + } else if (this.state.type === Constants.Integrations.OUTGOING_WEBHOOK) { + headerText = ( + + ); + helpText = ( +

+ +

+ ); + tokenText = ( +

+ +

+ ); + } else if (this.state.type === Constants.Integrations.OAUTH_APP) { + let oauthApp = {}; + for (var i = 0; i < this.state.oauthApps.length; i++) { + if (this.state.oauthApps[i].id === this.state.id) { + oauthApp = this.state.oauthApps[i]; + break; + } + } + + if (oauthApp) { + headerText = ( + + ); + + helpText = []; + helpText.push( +

+ +

+ ); + helpText.push( +

+
+ +

+ ); + + helpText.push( +

+ +

+ ); + + tokenText = ( +

+ +

+ ); + } + } + + return ( +
+ + + {headerText} + + + +
+

+ +

+ {helpText} + {tokenText} +
+ + + +
+
+
+ ); + } +} diff --git a/webapp/components/integrations/components/confirm_integration/index.js b/webapp/components/integrations/components/confirm_integration/index.js new file mode 100644 index 000000000..fe7984f2b --- /dev/null +++ b/webapp/components/integrations/components/confirm_integration/index.js @@ -0,0 +1,16 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {getCommands} from 'mattermost-redux/selectors/entities/integrations'; + +import ConfirmIntegration from './confirm_integration.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + commands: getCommands(state) + }; +} + +export default connect(mapStateToProps)(ConfirmIntegration); \ No newline at end of file diff --git a/webapp/components/integrations/components/installed_commands.jsx b/webapp/components/integrations/components/installed_commands.jsx deleted file mode 100644 index 731b62cd6..000000000 --- a/webapp/components/integrations/components/installed_commands.jsx +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import BackstageList from 'components/backstage/components/backstage_list.jsx'; -import InstalledCommand from './installed_command.jsx'; - -import {regenCommandToken, deleteCommand} from 'actions/integration_actions.jsx'; -import * as Utils from 'utils/utils.jsx'; - -import PropTypes from 'prop-types'; - -import React from 'react'; -import {FormattedMessage} from 'react-intl'; - -export default class InstalledCommands extends React.Component { - static get propTypes() { - return { - team: PropTypes.object, - user: PropTypes.object, - users: PropTypes.object, - commands: PropTypes.array, - loading: PropTypes.bool, - isAdmin: PropTypes.bool - }; - } - - constructor(props) { - super(props); - - this.regenCommandToken = this.regenCommandToken.bind(this); - this.deleteCommand = this.deleteCommand.bind(this); - } - - regenCommandToken(command) { - regenCommandToken(command.id); - } - - deleteCommand(command) { - deleteCommand(command.id); - } - - commandCompare(a, b) { - let nameA = a.display_name; - if (!nameA) { - nameA = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command'); - } - - let nameB = b.display_name; - if (!nameB) { - nameB = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command'); - } - - return nameA.localeCompare(nameB); - } - - render() { - const commands = this.props.commands.sort(this.commandCompare).map((command) => { - const canChange = this.props.isAdmin || this.props.user.id === command.creator_id; - - return ( - - ); - }); - - return ( - - } - addText={ - - } - addLink={'/' + this.props.team.name + '/integrations/commands/add'} - emptyText={ - - } - helpText={ - - - - ) - }} - /> - } - searchPlaceholder={Utils.localizeMessage('installed_commands.search', 'Search Slash Commands')} - loading={this.props.loading} - > - {commands} - - ); - } -} diff --git a/webapp/components/integrations/components/installed_commands/index.js b/webapp/components/integrations/components/installed_commands/index.js new file mode 100644 index 000000000..b8fb9b8bd --- /dev/null +++ b/webapp/components/integrations/components/installed_commands/index.js @@ -0,0 +1,25 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {regenCommandToken, deleteCommand} from 'mattermost-redux/actions/integrations'; + +import InstalledCommands from './installed_commands.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + regenCommandToken, + deleteCommand + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(InstalledCommands); \ No newline at end of file diff --git a/webapp/components/integrations/components/installed_commands/installed_commands.jsx b/webapp/components/integrations/components/installed_commands/installed_commands.jsx new file mode 100644 index 000000000..c84af77eb --- /dev/null +++ b/webapp/components/integrations/components/installed_commands/installed_commands.jsx @@ -0,0 +1,148 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import BackstageList from 'components/backstage/components/backstage_list.jsx'; +import InstalledCommand from '../installed_command.jsx'; + +import * as Utils from 'utils/utils.jsx'; + +import PropTypes from 'prop-types'; + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +export default class InstalledCommands extends React.PureComponent { + static propTypes = { + + /** + * The team object + */ + team: PropTypes.object, + + /** + * The user object + */ + user: PropTypes.object, + + /** + * The users collection + */ + users: PropTypes.object, + + /** + * Installed splash commands to display + */ + commands: PropTypes.array, + + /** + * Set whether to show the loading... animation or not + */ + loading: PropTypes.bool, + + /** + * Set to allow changes to installed splash commands + */ + isAdmin: PropTypes.bool, + + actions: PropTypes.shape({ + + /** + * The function to call when Regenerate Token link is clicked + */ + regenCommandToken: PropTypes.func.isRequired, + + /** + * The function to call when Delete link is clicked + */ + deleteCommand: PropTypes.func.isRequired + }).isRequired + } + + regenCommandToken = (command) => { + this.props.actions.regenCommandToken(command.id); + } + + deleteCommand = (command) => { + this.props.actions.deleteCommand(command.id); + } + + commandCompare(a, b) { + let nameA = a.display_name; + if (!nameA) { + nameA = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command'); + } + + let nameB = b.display_name; + if (!nameB) { + nameB = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command'); + } + + return nameA.localeCompare(nameB); + } + + render() { + const commands = this.props.commands.sort(this.commandCompare).map((command) => { + const canChange = this.props.isAdmin || this.props.user.id === command.creator_id; + + return ( + + ); + }); + + return ( + + } + addText={ + + } + addLink={'/' + this.props.team.name + '/integrations/commands/add'} + emptyText={ + + } + helpText={ + + + + ) + }} + /> + } + searchPlaceholder={Utils.localizeMessage('installed_commands.search', 'Search Slash Commands')} + loading={this.props.loading} + > + {commands} + + ); + } +} \ No newline at end of file diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx index cf59882dd..c7c7497b8 100644 --- a/webapp/routes/route_integrations.jsx +++ b/webapp/routes/route_integrations.jsx @@ -61,14 +61,14 @@ export default { { path: 'commands', getComponents: (location, callback) => { - System.import('components/integrations/components/commands_container.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/commands_container').then(RouteUtils.importComponentSuccess(callback)); }, indexRoute: {onEnter: (nextState, replace) => replace(nextState.location.pathname + '/installed')}, childRoutes: [ { path: 'installed', getComponents: (location, callback) => { - System.import('components/integrations/components/installed_commands.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/installed_commands').then(RouteUtils.importComponentSuccess(callback)); } }, { @@ -86,7 +86,7 @@ export default { { path: 'confirm', getComponents: (location, callback) => { - System.import('components/integrations/components/confirm_integration.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/confirm_integration').then(RouteUtils.importComponentSuccess(callback)); } } ] @@ -110,7 +110,7 @@ export default { { path: 'confirm', getComponents: (location, callback) => { - System.import('components/integrations/components/confirm_integration.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/confirm_integration').then(RouteUtils.importComponentSuccess(callback)); } } ] -- cgit v1.2.3-1-g7c22