diff options
author | 94117nl <rttededersixtwo@gmail.com> | 2017-07-04 07:58:45 -0500 |
---|---|---|
committer | Harrison Healey <harrisonmhealey@gmail.com> | 2017-07-04 08:58:45 -0400 |
commit | 29b98ec383e47f28e7e190434f872072815cb6cb (patch) | |
tree | a052a0771163f3ddb6a0724b4fd846505e899235 | |
parent | 0b112999b55c51b0a942c2b9439453b061ffce3c (diff) | |
download | chat-29b98ec383e47f28e7e190434f872072815cb6cb.tar.gz chat-29b98ec383e47f28e7e190434f872072815cb6cb.tar.bz2 chat-29b98ec383e47f28e7e190434f872072815cb6cb.zip |
PLT-6445 Migrate add_command.jsx to be pure and use Redux (#6804)
* Migrate add_command.jsx to be pure and use redux
* Add basic test for AddCommand component
-rw-r--r-- | webapp/components/integrations/components/add_command/add_command.jsx (renamed from webapp/components/integrations/components/add_command.jsx) | 85 | ||||
-rw-r--r-- | webapp/components/integrations/components/add_command/index.js | 25 | ||||
-rw-r--r-- | webapp/routes/route_integrations.jsx | 2 | ||||
-rw-r--r-- | webapp/tests/components/integrations/__snapshots__/add_command.test.jsx.snap | 396 | ||||
-rw-r--r-- | webapp/tests/components/integrations/add_command.test.jsx | 30 |
5 files changed, 495 insertions, 43 deletions
diff --git a/webapp/components/integrations/components/add_command.jsx b/webapp/components/integrations/components/add_command/add_command.jsx index 2141dda4a..28f6115f9 100644 --- a/webapp/components/integrations/components/add_command.jsx +++ b/webapp/components/integrations/components/add_command/add_command.jsx @@ -1,4 +1,4 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React from 'react'; @@ -6,8 +6,6 @@ import PropTypes from 'prop-types'; import * as Utils from 'utils/utils.jsx'; -import {addCommand} from 'actions/integration_actions.jsx'; - import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; import {FormattedMessage} from 'react-intl'; import FormError from 'components/form_error.jsx'; @@ -18,29 +16,31 @@ import Constants from 'utils/constants.jsx'; const REQUEST_POST = 'P'; const REQUEST_GET = 'G'; -export default class AddCommand extends React.Component { - static get propTypes() { - return { - team: PropTypes.object - }; +export default class AddCommand extends React.PureComponent { + static propTypes = { + + /** + * The team data + */ + team: PropTypes.object, + + /** + * The request state for addCommand action. Contains status and error + */ + addCommandRequest: PropTypes.object.isRequired, + + actions: PropTypes.shape({ + + /** + * The function to call to add new command + */ + addCommand: PropTypes.func.isRequired + }).isRequired } constructor(props) { super(props); - this.handleSubmit = this.handleSubmit.bind(this); - - this.updateDisplayName = this.updateDisplayName.bind(this); - this.updateDescription = this.updateDescription.bind(this); - this.updateTrigger = this.updateTrigger.bind(this); - this.updateUrl = this.updateUrl.bind(this); - this.updateMethod = this.updateMethod.bind(this); - this.updateUsername = this.updateUsername.bind(this); - this.updateIconUrl = this.updateIconUrl.bind(this); - this.updateAutocomplete = this.updateAutocomplete.bind(this); - this.updateAutocompleteHint = this.updateAutocompleteHint.bind(this); - this.updateAutocompleteDescription = this.updateAutocompleteDescription.bind(this); - this.state = { displayName: '', description: '', @@ -58,7 +58,7 @@ export default class AddCommand extends React.Component { }; } - handleSubmit(e) { + handleSubmit = (e) => { e.preventDefault(); if (this.state.saving) { @@ -134,7 +134,8 @@ export default class AddCommand extends React.Component { return; } - if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || command.trigger.length > Constants.MAX_TRIGGER_LENGTH) { + if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || + command.trigger.length > Constants.MAX_TRIGGER_LENGTH) { this.setState({ saving: false, clientError: ( @@ -166,75 +167,75 @@ export default class AddCommand extends React.Component { return; } - addCommand( - command, + this.props.actions.addCommand(command).then( (data) => { - browserHistory.push('/' + this.props.team.name + '/integrations/commands/confirm?type=commands&id=' + data.id); - }, - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); + if (data) { + browserHistory.push(`/${this.props.team.name}/integrations/commands/confirm?type=commands&id=${data.id}`); + } else { + this.setState({ + saving: false, + serverError: this.props.addCommandRequest.error.message + }); + } } ); } - updateDisplayName(e) { + updateDisplayName = (e) => { this.setState({ displayName: e.target.value }); } - updateDescription(e) { + updateDescription = (e) => { this.setState({ description: e.target.value }); } - updateTrigger(e) { + updateTrigger = (e) => { this.setState({ trigger: e.target.value }); } - updateUrl(e) { + updateUrl = (e) => { this.setState({ url: e.target.value }); } - updateMethod(e) { + updateMethod = (e) => { this.setState({ method: e.target.value }); } - updateUsername(e) { + updateUsername = (e) => { this.setState({ username: e.target.value }); } - updateIconUrl(e) { + updateIconUrl = (e) => { this.setState({ iconUrl: e.target.value }); } - updateAutocomplete(e) { + updateAutocomplete = (e) => { this.setState({ autocomplete: e.target.checked }); } - updateAutocompleteHint(e) { + updateAutocompleteHint = (e) => { this.setState({ autocompleteHint: e.target.value }); } - updateAutocompleteDescription(e) { + updateAutocompleteDescription = (e) => { this.setState({ autocompleteDescription: e.target.value }); diff --git a/webapp/components/integrations/components/add_command/index.js b/webapp/components/integrations/components/add_command/index.js new file mode 100644 index 000000000..9ac7db220 --- /dev/null +++ b/webapp/components/integrations/components/add_command/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 {addCommand} from 'mattermost-redux/actions/integrations'; + +import AddCommand from './add_command.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + addCommandRequest: state.requests.integrations.addCommand + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + addCommand + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddCommand); diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx index c7c7497b8..dd3ebe663 100644 --- a/webapp/routes/route_integrations.jsx +++ b/webapp/routes/route_integrations.jsx @@ -74,7 +74,7 @@ export default { { path: 'add', getComponents: (location, callback) => { - System.import('components/integrations/components/add_command.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/add_command').then(RouteUtils.importComponentSuccess(callback)); } }, { diff --git a/webapp/tests/components/integrations/__snapshots__/add_command.test.jsx.snap b/webapp/tests/components/integrations/__snapshots__/add_command.test.jsx.snap new file mode 100644 index 000000000..99e5d7ad8 --- /dev/null +++ b/webapp/tests/components/integrations/__snapshots__/add_command.test.jsx.snap @@ -0,0 +1,396 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/integrations/AddCommand should match snapshot 1`] = ` +<div + className="backstage-content row" +> + <BackstageHeader> + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/test/integrations/commands" + > + <FormattedMessage + defaultMessage="Slash Commands" + id="installed_command.header" + values={Object {}} + /> + </Link> + <FormattedMessage + defaultMessage="Add" + id="integrations.add" + values={Object {}} + /> + </BackstageHeader> + <div + className="backstage-form" + > + <form + className="form-horizontal" + onSubmit={[Function]} + > + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="displayName" + > + <FormattedMessage + defaultMessage="Display Name" + id="add_command.displayName" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="displayName" + maxLength="64" + onChange={[Function]} + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="Display name for your slash command made of up to 64 characters." + id="add_command.displayName.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="description" + > + <FormattedMessage + defaultMessage="Description" + id="add_command.description" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="description" + maxLength="128" + onChange={[Function]} + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="Description for your incoming webhook." + id="add_command.description.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="trigger" + > + <FormattedMessage + defaultMessage="Command Trigger Word" + id="add_command.trigger" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="trigger" + maxLength={128} + onChange={[Function]} + placeholder="Command trigger e.g. \\"hello\\" not including the slash" + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="Trigger word must be unique, and cannot begin with a slash or contain any spaces." + id="add_command.trigger.help" + values={Object {}} + /> + </div> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="Examples: client, employee, patient, weather" + id="add_command.trigger.helpExamples" + values={Object {}} + /> + </div> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="Reserved: {link}" + id="add_command.trigger.helpReserved" + values={ + Object { + "link": <a + href="https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands" + rel="noopener noreferrer" + target="_blank" + > + <FormattedMessage + defaultMessage="see list of built-in slash commands" + id="add_command.trigger.helpReservedLinkText" + values={Object {}} + /> + </a>, + } + } + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="url" + > + <FormattedMessage + defaultMessage="Request URL" + id="add_command.url" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="url" + maxLength="1024" + onChange={[Function]} + placeholder="Must start with http:// or https://" + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="The callback URL to receive the HTTP POST or GET event request when the slash command is run." + id="add_command.url.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="method" + > + <FormattedMessage + defaultMessage="Request Method" + id="add_command.method" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <select + className="form-control" + id="method" + onChange={[Function]} + value="P" + > + <option + value="P" + > + POST + </option> + <option + value="G" + > + GET + </option> + </select> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="The type of command request issued to the Request URL." + id="add_command.method.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="username" + > + <FormattedMessage + defaultMessage="Response Username" + id="add_command.username" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="username" + maxLength="64" + onChange={[Function]} + placeholder="Username" + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="(Optional) Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols \\"-\\", \\"_\\", and \\".\\" ." + id="add_command.username.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="iconUrl" + > + <FormattedMessage + defaultMessage="Response Icon" + id="add_command.iconUrl" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8" + > + <input + className="form-control" + id="iconUrl" + maxLength="1024" + onChange={[Function]} + placeholder="https://www.example.com/myicon.png" + type="text" + value="" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="(Optional) 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." + id="add_command.iconUrl.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="form-group" + > + <label + className="control-label col-sm-4" + htmlFor="autocomplete" + > + <FormattedMessage + defaultMessage="Autocomplete" + id="add_command.autocomplete" + values={Object {}} + /> + </label> + <div + className="col-md-5 col-sm-8 checkbox" + > + <input + checked={false} + id="autocomplete" + onChange={[Function]} + type="checkbox" + /> + <div + className="form__help" + > + <FormattedMessage + defaultMessage="(Optional) Show slash command in autocomplete list." + id="add_command.autocomplete.help" + values={Object {}} + /> + </div> + </div> + </div> + <div + className="backstage-form__footer" + > + <FormError + error={null} + errors={ + Array [ + "", + null, + ] + } + type="backstage" + /> + <Link + className="btn btn-sm" + onlyActiveOnIndex={false} + style={Object {}} + to="/test/integrations/commands" + > + <FormattedMessage + defaultMessage="Cancel" + id="add_command.cancel" + values={Object {}} + /> + </Link> + <SpinnerButton + className="btn btn-primary" + onClick={[Function]} + spinning={false} + type="submit" + > + <FormattedMessage + defaultMessage="Save" + id="add_command.save" + values={Object {}} + /> + </SpinnerButton> + </div> + </form> + </div> +</div> +`; diff --git a/webapp/tests/components/integrations/add_command.test.jsx b/webapp/tests/components/integrations/add_command.test.jsx new file mode 100644 index 000000000..cf8a31e07 --- /dev/null +++ b/webapp/tests/components/integrations/add_command.test.jsx @@ -0,0 +1,30 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import * as Utils from 'utils/utils.jsx'; +import AddCommand from 'components/integrations/components/add_command/add_command.jsx'; + +describe('components/integrations/AddCommand', () => { + test('should match snapshot', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + const teamId = Utils.generateId(); + + const wrapper = shallow( + <AddCommand + team={{ + id: teamId, + name: 'test' + }} + addCommandRequest={{ + status: 'not_started', + error: null + }} + actions={{addCommand: emptyFunction}} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); +}); |