MM-47091 : Migrate "components/integrations/abstract_outgoing_webhook.jsx" and tests to TypeScript (#23977)

This commit is contained in:
Austin DeNoble 2023-07-20 08:59:57 -04:00 committed by GitHub
parent e377d985cd
commit 949a7875cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 309 additions and 156 deletions

View File

@ -6,7 +6,7 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
> >
<BackstageHeader> <BackstageHeader>
<Link <Link
to="/test/integrations/outgoing_webhooks" to="/team_name/integrations/outgoing_webhooks"
> >
<MemoizedFormattedMessage <MemoizedFormattedMessage
defaultMessage="Outgoing Webhooks" defaultMessage="Outgoing Webhooks"
@ -14,8 +14,8 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
/> />
</Link> </Link>
<MemoizedFormattedMessage <MemoizedFormattedMessage
defaultMessage="add" defaultMessage="Header"
id="add" id="header_id"
/> />
</BackstageHeader> </BackstageHeader>
<div <div
@ -43,10 +43,10 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
<input <input
className="form-control" className="form-control"
id="displayName" id="displayName"
maxLength="64" maxLength={64}
onChange={[Function]} onChange={[Function]}
type="text" type="text"
value="" value="testOutgoingWebhook"
/> />
<div <div
className="form__help" className="form__help"
@ -76,10 +76,10 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
<input <input
className="form-control" className="form-control"
id="description" id="description"
maxLength="500" maxLength={500}
onChange={[Function]} onChange={[Function]}
type="text" type="text"
value="" value="testing"
/> />
<div <div
className="form__help" className="form__help"
@ -109,7 +109,7 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
<select <select
className="form-control" className="form-control"
onChange={[Function]} onChange={[Function]}
value="application/x-www-form-urlencoded" value="test_content_type"
> >
<option <option
value="application/x-www-form-urlencoded" value="application/x-www-form-urlencoded"
@ -164,10 +164,11 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
className="col-md-5 col-sm-8" className="col-md-5 col-sm-8"
> >
<Connect(ChannelSelect) <Connect(ChannelSelect)
id="channelId"
onChange={[Function]} onChange={[Function]}
selectDm={false}
selectOpen={true} selectOpen={true}
value="" selectPrivate={false}
value="88cxd9wpzpbpfp8pad78xj75pr"
/> />
<div <div
className="form__help" className="form__help"
@ -197,10 +198,13 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
<textarea <textarea
className="form-control" className="form-control"
id="triggerWords" id="triggerWords"
maxLength="1000" maxLength={1000}
onChange={[Function]} onChange={[Function]}
rows="3" rows={3}
value="" value="test
trigger
word
"
/> />
<div <div
className="form__help" className="form__help"
@ -229,6 +233,7 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
> >
<select <select
className="form-control" className="form-control"
id="triggerWhen"
onChange={[Function]} onChange={[Function]}
value={0} value={0}
> >
@ -271,10 +276,12 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
<textarea <textarea
className="form-control" className="form-control"
id="callbackUrls" id="callbackUrls"
maxLength="1000" maxLength={1000}
onChange={[Function]} onChange={[Function]}
rows="3" rows={3}
value="" value="callbackUrl1.com
callbackUrl2.com
"
/> />
<div <div
className="form__help" className="form__help"
@ -314,7 +321,7 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
/> />
<Link <Link
className="btn btn-link btn-sm" className="btn btn-link btn-sm"
to="/test/integrations/outgoing_webhooks" to="/team_name/integrations/outgoing_webhooks"
> >
<MemoizedFormattedMessage <MemoizedFormattedMessage
defaultMessage="Cancel" defaultMessage="Cancel"
@ -326,12 +333,12 @@ exports[`components/integrations/AbstractOutgoingWebhook should match snapshot 1
id="saveWebhook" id="saveWebhook"
onClick={[Function]} onClick={[Function]}
spinning={false} spinning={false}
spinningText="loading" spinningText="Loading"
type="submit" type="submit"
> >
<MemoizedFormattedMessage <MemoizedFormattedMessage
defaultMessage="save" defaultMessage="Footer"
id="save" id="footer_id"
/> />
</SpinnerButton> </SpinnerButton>
</div> </div>

View File

@ -9,6 +9,8 @@ import ChannelSelect from 'components/channel_select';
import AbstractIncomingWebhook from 'components/integrations/abstract_incoming_webhook'; import AbstractIncomingWebhook from 'components/integrations/abstract_incoming_webhook';
import {Team} from '@mattermost/types/teams'; import {Team} from '@mattermost/types/teams';
type AbstractIncomingWebhookProps = React.ComponentProps<typeof AbstractIncomingWebhook>;
describe('components/integrations/AbstractIncomingWebhook', () => { describe('components/integrations/AbstractIncomingWebhook', () => {
const team: Team = {id: 'team_id', const team: Team = {id: 'team_id',
create_at: 0, create_at: 0,
@ -55,7 +57,7 @@ describe('components/integrations/AbstractIncomingWebhook', () => {
}, },
); );
const requiredProps = { const requiredProps: AbstractIncomingWebhookProps = {
team, team,
header, header,
footer, footer,
@ -80,10 +82,9 @@ describe('components/integrations/AbstractIncomingWebhook', () => {
}); });
test('should match snapshot, displays client error when no initial hook', () => { test('should match snapshot, displays client error when no initial hook', () => {
const newInitialHook = {}; const props = {...requiredProps};
const props = {...requiredProps, initialHook: newInitialHook}; delete props.initialHook;
const wrapper = shallow(<AbstractIncomingWebhook {...props}/>); const wrapper = shallow(<AbstractIncomingWebhook {...props}/>);
wrapper.find('.btn-primary').simulate('click', {preventDefault() { wrapper.find('.btn-primary').simulate('click', {preventDefault() {
return jest.fn(); return jest.fn();
}}); }});

View File

@ -55,7 +55,7 @@ interface Props {
/** /**
* The hook used to set the initial state * The hook used to set the initial state
*/ */
initialHook?: IncomingWebhook | Record<string, never>; initialHook?: IncomingWebhook;
/** /**
* Whether to allow configuration of the default post username. * Whether to allow configuration of the default post username.
@ -77,10 +77,10 @@ export default class AbstractIncomingWebhook extends PureComponent<Props, State>
constructor(props: Props | Readonly<Props>) { constructor(props: Props | Readonly<Props>) {
super(props); super(props);
this.state = this.getStateFromHook(this.props.initialHook || {}); this.state = this.getStateFromHook(this.props.initialHook);
} }
getStateFromHook = (hook: IncomingWebhook | Record<string, never>) => { getStateFromHook = (hook?: IncomingWebhook) => {
return { return {
displayName: hook?.display_name || '', displayName: hook?.display_name || '',
description: hook?.description || '', description: hook?.description || '',

View File

@ -1,42 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook';
describe('components/integrations/AbstractOutgoingWebhook', () => {
const emptyFunction = jest.fn();
const props = {
team: {
id: 'test-team-id',
name: 'test',
},
action: emptyFunction,
enablePostUsernameOverride: false,
enablePostIconOverride: false,
header: {id: 'add', defaultMessage: 'add'},
footer: {id: 'save', defaultMessage: 'save'},
loading: {id: 'loading', defaultMessage: 'loading'},
renderExtra: '',
serverError: '',
};
test('should match snapshot', () => {
const wrapper = shallow(<AbstractOutgoingWebhook {...props}/>);
expect(wrapper).toMatchSnapshot();
});
test('should render username in case of enablePostUsernameOverride is true ', () => {
const usernameTrueProps = {...props, enablePostUsernameOverride: true};
const wrapper = shallow(<AbstractOutgoingWebhook {...usernameTrueProps}/>);
expect(wrapper.find('#username')).toHaveLength(1);
});
test('should render username in case of enablePostUsernameOverride is true ', () => {
const iconUrlTrueProps = {...props, enablePostIconOverride: true};
const wrapper = shallow(<AbstractOutgoingWebhook {...iconUrlTrueProps}/>);
expect(wrapper.find('#iconURL')).toHaveLength(1);
});
});

View File

@ -0,0 +1,165 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook';
import ChannelSelect from 'components/channel_select';
import {Team} from '@mattermost/types/teams';
describe('components/integrations/AbstractOutgoingWebhook', () => {
const team: Team = {
id: 'team_id',
create_at: 0,
update_at: 0,
delete_at: 0,
display_name: 'team_name',
name: 'team_name',
description: 'team_description',
email: 'team_email',
type: 'I',
company_name: 'team_company_name',
allowed_domains: 'team_allowed_domains',
invite_id: 'team_invite_id',
allow_open_invite: false,
scheme_id: 'team_scheme_id',
group_constrained: false,
};
const header = {id: 'header_id', defaultMessage: 'Header'};
const footer = {id: 'footer_id', defaultMessage: 'Footer'};
const loading = {id: 'loading_id', defaultMessage: 'Loading'};
const initialHook = {
display_name: 'testOutgoingWebhook',
channel_id: '88cxd9wpzpbpfp8pad78xj75pr',
creator_id: 'test_creator_id',
description: 'testing',
id: 'test_id',
team_id: 'test_team_id',
token: 'test_token',
trigger_words: ['test', 'trigger', 'word'],
trigger_when: 0,
callback_urls: ['callbackUrl1.com', 'callbackUrl2.com'],
content_type: 'test_content_type',
create_at: 0,
update_at: 0,
delete_at: 0,
user_id: 'test_user_id',
username: '',
icon_url: '',
channel_locked: false,
};
const action = jest.fn().mockImplementation(
() => {
return new Promise<void>((resolve) => {
process.nextTick(() => resolve());
});
},
);
const requiredProps = {
team,
header,
footer,
loading,
initialHook,
enablePostUsernameOverride: false,
enablePostIconOverride: false,
renderExtra: '',
serverError: '',
action,
};
test('should match snapshot', () => {
const wrapper = shallow(<AbstractOutgoingWebhook {...requiredProps}/>);
expect(wrapper).toMatchSnapshot();
});
test('should not render username in case of enablePostUsernameOverride is false ', () => {
const usernameTrueProps = {...requiredProps};
const wrapper = shallow(<AbstractOutgoingWebhook {...usernameTrueProps}/>);
expect(wrapper.find('#username')).toHaveLength(0);
});
test('should not render post icon override in case of enablePostIconOverride is false ', () => {
const iconUrlTrueProps = {...requiredProps};
const wrapper = shallow(<AbstractOutgoingWebhook {...iconUrlTrueProps}/>);
expect(wrapper.find('#iconURL')).toHaveLength(0);
});
test('should render username in case of enablePostUsernameOverride is true ', () => {
const usernameTrueProps = {...requiredProps, enablePostUsernameOverride: true};
const wrapper = shallow(<AbstractOutgoingWebhook {...usernameTrueProps}/>);
expect(wrapper.find('#username')).toHaveLength(1);
});
test('should render post icon override in case of enablePostIconOverride is true ', () => {
const iconUrlTrueProps = {...requiredProps, enablePostIconOverride: true};
const wrapper = shallow(<AbstractOutgoingWebhook {...iconUrlTrueProps}/>);
expect(wrapper.find('#iconURL')).toHaveLength(1);
});
test('should update state.channelId when on channel change', () => {
const newChannelId = 'new_channel_id';
const evt = {
preventDefault: jest.fn(),
target: {value: newChannelId},
};
const wrapper = shallow(<AbstractOutgoingWebhook {...requiredProps}/>);
wrapper.find(ChannelSelect).simulate('change', evt);
expect(wrapper.state('channelId')).toBe(newChannelId);
});
test('should update state.description when on description change', () => {
const newDescription = 'new_description';
const evt = {
preventDefault: jest.fn(),
target: {value: newDescription},
};
const wrapper = shallow(<AbstractOutgoingWebhook {...requiredProps}/>);
wrapper.find('#description').simulate('change', evt);
expect(wrapper.state('description')).toBe(newDescription);
});
test('should update state.username on post username change', () => {
const usernameTrueProps = {...requiredProps, enablePostUsernameOverride: true};
const newUsername = 'new_username';
const evt = {
preventDefault: jest.fn(),
target: {value: newUsername},
};
const wrapper = shallow(<AbstractOutgoingWebhook {...usernameTrueProps}/>);
wrapper.find('#username').simulate('change', evt);
expect(wrapper.state('username')).toBe(newUsername);
});
test('should update state.triggerWhen on selection change', () => {
const wrapper = shallow(<AbstractOutgoingWebhook {...requiredProps}/>);
expect(wrapper.state('triggerWhen')).toBe(0);
const selector = wrapper.find('#triggerWhen');
selector.simulate('change', {target: {value: 1}});
console.log('selector: ', selector.debug());
expect(wrapper.state('triggerWhen')).toBe(1);
});
test('should call action function', () => {
const wrapper = shallow(<AbstractOutgoingWebhook {...requiredProps}/>);
wrapper.find('#displayName').simulate('change', {target: {value: 'name'}});
wrapper.find('.btn-primary').simulate('click', {preventDefault() {
return jest.fn();
}});
expect(action).toBeCalled();
expect(action).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,83 +1,97 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import PropTypes from 'prop-types'; import React, {ChangeEventHandler, FormEvent, MouseEvent} from 'react';
import React from 'react'; import {FormattedMessage, MessageDescriptor} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {localizeMessage} from 'utils/utils'; import {localizeMessage} from 'utils/utils';
import {Team} from '@mattermost/types/teams';
import BackstageHeader from 'components/backstage/components/backstage_header'; import BackstageHeader from 'components/backstage/components/backstage_header';
import ChannelSelect from 'components/channel_select'; import ChannelSelect from 'components/channel_select';
import FormError from 'components/form_error'; import FormError from 'components/form_error';
import SpinnerButton from 'components/spinner_button'; import SpinnerButton from 'components/spinner_button';
import ExternalLink from 'components/external_link'; import ExternalLink from 'components/external_link';
import {DocLinks} from 'utils/constants'; import {DocLinks} from 'utils/constants';
import {OutgoingWebhook} from '@mattermost/types/integrations';
export default class AbstractOutgoingWebhook extends React.PureComponent { interface State {
static propTypes = { callbackUrls: string;
channelId: string;
clientError: JSX.Element | null;
contentType: string;
description: string;
displayName: string;
iconURL: string;
saving: boolean;
triggerWhen: number;
triggerWords: string;
username: string;
}
/** interface Props {
* The current team
*/
team: PropTypes.object.isRequired,
/** /**
* The header text to render, has id and defaultMessage * The current team
*/ */
header: PropTypes.object.isRequired, team: Team;
/** /**
* The footer text to render, has id and defaultMessage * The header text to render, has id and defaultMessage
*/ */
footer: PropTypes.object.isRequired, header: MessageDescriptor;
/** /**
* The spinner loading text to render, has id and defaultMessage * The footer text to render, has id and defaultMessage
*/ */
loading: PropTypes.object.isRequired, footer: MessageDescriptor;
/** /**
* Any extra component/node to render * The spinner loading text to render, has id and defaultMessage
*/ */
renderExtra: PropTypes.node.isRequired, loading: MessageDescriptor;
/** /**
* The server error text after a failed action * Any extra component/node to render
*/ */
serverError: PropTypes.string.isRequired, renderExtra: React.ReactNode;
/** /**
* The hook used to set the initial state * The server error text after a failed action
*/ */
initialHook: PropTypes.object, serverError: string;
/** /**
* The async function to run when the action button is pressed * The hook used to set the initial state
*/ */
action: PropTypes.func.isRequired, initialHook?: OutgoingWebhook;
/** /**
* Whether to allow configuration of the default post username. * The async function to run when the action button is pressed
*/ */
enablePostUsernameOverride: PropTypes.bool.isRequired, action: (hook: OutgoingWebhook) => Promise<void>;
/** /**
* Whether to allow configuration of the default post icon. * Whether to allow configuration of the default post username.
*/ */
enablePostIconOverride: PropTypes.bool.isRequired, enablePostUsernameOverride: boolean;
};
constructor(props) { /**
* Whether to allow configuration of the default post icon.
*/
enablePostIconOverride: boolean;
}
export default class AbstractOutgoingWebhook extends React.PureComponent<Props, State> {
constructor(props: Props | Readonly<Props>) {
super(props); super(props);
this.state = this.getStateFromHook(this.props.initialHook || {}); this.state = this.getStateFromHook(this.props.initialHook);
} }
getStateFromHook = (hook) => { getStateFromHook = (hook?: OutgoingWebhook) => {
let triggerWords = ''; let triggerWords = '';
if (hook.trigger_words) { if (hook?.trigger_words) {
let i = 0; let i = 0;
for (i = 0; i < hook.trigger_words.length; i++) { for (i = 0; i < hook.trigger_words.length; i++) {
triggerWords += hook.trigger_words[i] + '\n'; triggerWords += hook.trigger_words[i] + '\n';
@ -85,7 +99,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
} }
let callbackUrls = ''; let callbackUrls = '';
if (hook.callback_urls) { if (hook?.callback_urls) {
let i = 0; let i = 0;
for (i = 0; i < hook.callback_urls.length; i++) { for (i = 0; i < hook.callback_urls.length; i++) {
callbackUrls += hook.callback_urls[i] + '\n'; callbackUrls += hook.callback_urls[i] + '\n';
@ -93,21 +107,21 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
} }
return { return {
displayName: hook.display_name || '', displayName: hook?.display_name || '',
description: hook.description || '', description: hook?.description || '',
contentType: hook.content_type || 'application/x-www-form-urlencoded', contentType: hook?.content_type || 'application/x-www-form-urlencoded',
channelId: hook.channel_id || '', channelId: hook?.channel_id || '',
triggerWords, triggerWords,
triggerWhen: hook.trigger_when || 0, triggerWhen: hook?.trigger_when || 0,
callbackUrls, callbackUrls,
saving: false, saving: false,
clientError: null, clientError: null,
username: hook.username || '', username: hook?.username || '',
iconURL: hook.icon_url || '', iconURL: hook?.icon_url || '',
}; };
}; };
handleSubmit = (e) => { handleSubmit = (e: MouseEvent<HTMLElement> | FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if (this.state.saving) { if (this.state.saving) {
@ -116,7 +130,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
this.setState({ this.setState({
saving: true, saving: true,
clientError: '', clientError: null,
}); });
const triggerWords = []; const triggerWords = [];
@ -171,67 +185,73 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
team_id: this.props.team.id, team_id: this.props.team.id,
channel_id: this.state.channelId, channel_id: this.state.channelId,
trigger_words: triggerWords, trigger_words: triggerWords,
trigger_when: parseInt(this.state.triggerWhen, 10), trigger_when: this.state.triggerWhen,
callback_urls: callbackUrls, callback_urls: callbackUrls,
display_name: this.state.displayName, display_name: this.state.displayName,
content_type: this.state.contentType, content_type: this.state.contentType,
description: this.state.description, description: this.state.description,
username: this.state.username, username: this.state.username,
icon_url: this.state.iconURL, icon_url: this.state.iconURL,
id: this.props.initialHook?.id || '',
create_at: this.props.initialHook?.create_at || 0,
update_at: this.props.initialHook?.update_at || 0,
delete_at: this.props.initialHook?.delete_at || 0,
creator_id: this.props.initialHook?.creator_id || '',
token: this.props.initialHook?.token || '',
}; };
this.props.action(hook).then(() => this.setState({saving: false})); this.props.action(hook).then(() => this.setState({saving: false}));
}; };
updateDisplayName = (e) => { updateDisplayName: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({ this.setState({
displayName: e.target.value, displayName: e.target.value,
}); });
}; };
updateDescription = (e) => { updateDescription: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({ this.setState({
description: e.target.value, description: e.target.value,
}); });
}; };
updateContentType = (e) => { updateContentType: ChangeEventHandler<HTMLSelectElement> = (e) => {
this.setState({ this.setState({
contentType: e.target.value, contentType: e.target.value,
}); });
}; };
updateChannelId = (e) => { updateChannelId: ChangeEventHandler<HTMLSelectElement> = (e) => {
this.setState({ this.setState({
channelId: e.target.value, channelId: e.target.value,
}); });
}; };
updateTriggerWords = (e) => { updateTriggerWords: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
this.setState({ this.setState({
triggerWords: e.target.value, triggerWords: e.target.value,
}); });
}; };
updateTriggerWhen = (e) => { updateTriggerWhen: ChangeEventHandler<HTMLSelectElement> = (e) => {
this.setState({ this.setState({
triggerWhen: e.target.value, triggerWhen: parseInt(e.target.value, 10),
}); });
}; };
updateCallbackUrls = (e) => { updateCallbackUrls: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
this.setState({ this.setState({
callbackUrls: e.target.value, callbackUrls: e.target.value,
}); });
}; };
updateUsername = (e) => { updateUsername: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({ this.setState({
username: e.target.value, username: e.target.value,
}); });
}; };
updateIconURL = (e) => { updateIconURL: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({ this.setState({
iconURL: e.target.value, iconURL: e.target.value,
}); });
@ -241,9 +261,9 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
const contentTypeOption1 = 'application/x-www-form-urlencoded'; const contentTypeOption1 = 'application/x-www-form-urlencoded';
const contentTypeOption2 = 'application/json'; const contentTypeOption2 = 'application/json';
var headerToRender = this.props.header; const headerToRender = this.props.header;
var footerToRender = this.props.footer; const footerToRender = this.props.footer;
var renderExtra = this.props.renderExtra; const renderExtra = this.props.renderExtra;
return ( return (
<div className='backstage-content'> <div className='backstage-content'>
@ -278,7 +298,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<input <input
id='displayName' id='displayName'
type='text' type='text'
maxLength='64' maxLength={64}
className='form-control' className='form-control'
value={this.state.displayName} value={this.state.displayName}
onChange={this.updateDisplayName} onChange={this.updateDisplayName}
@ -305,7 +325,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<input <input
id='description' id='description'
type='text' type='text'
maxLength='500' maxLength={500}
className='form-control' className='form-control'
value={this.state.description} value={this.state.description}
onChange={this.updateDescription} onChange={this.updateDescription}
@ -377,10 +397,11 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
</label> </label>
<div className='col-md-5 col-sm-8'> <div className='col-md-5 col-sm-8'>
<ChannelSelect <ChannelSelect
id='channelId'
value={this.state.channelId} value={this.state.channelId}
onChange={this.updateChannelId} onChange={this.updateChannelId}
selectOpen={true} selectOpen={true}
selectPrivate={false}
selectDm={false}
/> />
<div className='form__help'> <div className='form__help'>
<FormattedMessage <FormattedMessage
@ -403,8 +424,8 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<div className='col-md-5 col-sm-8'> <div className='col-md-5 col-sm-8'>
<textarea <textarea
id='triggerWords' id='triggerWords'
rows='3' rows={3}
maxLength='1000' maxLength={1000}
className='form-control' className='form-control'
value={this.state.triggerWords} value={this.state.triggerWords}
onChange={this.updateTriggerWords} onChange={this.updateTriggerWords}
@ -429,6 +450,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
</label> </label>
<div className='col-md-5 col-sm-8'> <div className='col-md-5 col-sm-8'>
<select <select
id='triggerWhen'
className='form-control' className='form-control'
value={this.state.triggerWhen} value={this.state.triggerWhen}
onChange={this.updateTriggerWhen} onChange={this.updateTriggerWhen}
@ -465,8 +487,8 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<div className='col-md-5 col-sm-8'> <div className='col-md-5 col-sm-8'>
<textarea <textarea
id='callbackUrls' id='callbackUrls'
rows='3' rows={3}
maxLength='1000' maxLength={1000}
className='form-control' className='form-control'
value={this.state.callbackUrls} value={this.state.callbackUrls}
onChange={this.updateCallbackUrls} onChange={this.updateCallbackUrls}
@ -507,7 +529,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<input <input
id='username' id='username'
type='text' type='text'
maxLength='22' maxLength={22}
className='form-control' className='form-control'
value={this.state.username} value={this.state.username}
onChange={this.updateUsername} onChange={this.updateUsername}
@ -536,7 +558,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
<input <input
id='iconURL' id='iconURL'
type='text' type='text'
maxLength='1024' maxLength={1024}
className='form-control' className='form-control'
value={this.state.iconURL} value={this.state.iconURL}
onChange={this.updateIconURL} onChange={this.updateIconURL}
@ -568,7 +590,7 @@ export default class AbstractOutgoingWebhook extends React.PureComponent {
className='btn btn-primary' className='btn btn-primary'
type='submit' type='submit'
spinning={this.state.saving} spinning={this.state.saving}
spinningText={localizeMessage(this.props.loading.id, this.props.loading.defaultMessage)} spinningText={localizeMessage(this.props.loading.id as string, this.props.loading.defaultMessage as string)}
onClick={this.handleSubmit} onClick={this.handleSubmit}
id='saveWebhook' id='saveWebhook'
> >

View File

@ -6,7 +6,7 @@ import {useHistory} from 'react-router-dom';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook.jsx'; import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook';
import {Team} from '@mattermost/types/teams'; import {Team} from '@mattermost/types/teams';
import {OutgoingWebhook} from '@mattermost/types/integrations'; import {OutgoingWebhook} from '@mattermost/types/integrations';

View File

@ -10,7 +10,7 @@ import {ServerError} from '@mattermost/types/errors';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import ConfirmModal from 'components/confirm_modal'; import ConfirmModal from 'components/confirm_modal';
import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook.jsx'; import AbstractOutgoingWebhook from 'components/integrations/abstract_outgoing_webhook';
import LoadingScreen from 'components/loading_screen'; import LoadingScreen from 'components/loading_screen';
const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'}; const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'};