MM-47095 Migrate AbstractIncomingWebhook to TypeScript (#22654)

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Dismated
2023-04-04 19:28:40 +03:00
коммит произвёл GitHub
родитель 6919761122
Коммит 42ae1536cb
5 изменённых файлов: 148 добавлений и 103 удалений

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {ChangeEventHandler} from 'react';
import {Channel} from '@mattermost/types/channels';
@@ -10,7 +10,7 @@ import * as Utils from 'utils/utils';
type Props = {
channels: Channel[];
onChange?: () => void;
onChange?: ChangeEventHandler<HTMLSelectElement>;
value?: string;
selectOpen: boolean;
selectPrivate: boolean;

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

@@ -43,7 +43,7 @@ exports[`components/integrations/AbstractIncomingWebhook should call action func
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value="testIncomingWebhook"
@@ -76,7 +76,7 @@ exports[`components/integrations/AbstractIncomingWebhook should call action func
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value="testing"
@@ -107,8 +107,8 @@ exports[`components/integrations/AbstractIncomingWebhook should call action func
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value="88cxd9wpzpbpfp8pad78xj75pr"
@@ -172,7 +172,7 @@ exports[`components/integrations/AbstractIncomingWebhook should call action func
<input
className="form-control"
id="username"
maxLength="22"
maxLength={22}
onChange={[Function]}
type="text"
value=""
@@ -205,7 +205,7 @@ exports[`components/integrations/AbstractIncomingWebhook should call action func
<input
className="form-control"
id="iconURL"
maxLength="1024"
maxLength={1024}
onChange={[Function]}
type="text"
value=""
@@ -304,7 +304,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot 1
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value="testIncomingWebhook"
@@ -337,7 +337,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot 1
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value="testing"
@@ -368,8 +368,8 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot 1
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value="88cxd9wpzpbpfp8pad78xj75pr"
@@ -433,7 +433,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot 1
<input
className="form-control"
id="username"
maxLength="22"
maxLength={22}
onChange={[Function]}
type="text"
value=""
@@ -466,7 +466,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot 1
<input
className="form-control"
id="iconURL"
maxLength="1024"
maxLength={1024}
onChange={[Function]}
type="text"
value=""
@@ -565,7 +565,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value=""
@@ -598,7 +598,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value=""
@@ -629,8 +629,8 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value=""
@@ -694,7 +694,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="username"
maxLength="22"
maxLength={22}
onChange={[Function]}
type="text"
value=""
@@ -727,7 +727,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="iconURL"
maxLength="1024"
maxLength={1024}
onChange={[Function]}
type="text"
value=""
@@ -829,7 +829,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value="testIncomingWebhook"
@@ -862,7 +862,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value="testing"
@@ -893,8 +893,8 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value="88cxd9wpzpbpfp8pad78xj75pr"
@@ -958,7 +958,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="username"
maxLength="22"
maxLength={22}
onChange={[Function]}
type="text"
value=""
@@ -1057,7 +1057,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value="testIncomingWebhook"
@@ -1090,7 +1090,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value="testing"
@@ -1121,8 +1121,8 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value="88cxd9wpzpbpfp8pad78xj75pr"
@@ -1186,7 +1186,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="iconURL"
maxLength="1024"
maxLength={1024}
onChange={[Function]}
type="text"
value=""
@@ -1285,7 +1285,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="displayName"
maxLength="64"
maxLength={64}
onChange={[Function]}
type="text"
value="testIncomingWebhook"
@@ -1318,7 +1318,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="description"
maxLength="500"
maxLength={500}
onChange={[Function]}
type="text"
value="testing"
@@ -1349,8 +1349,8 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
className="col-md-5 col-sm-8"
>
<Connect(ChannelSelect)
id="channelId"
onChange={[Function]}
selectDm={false}
selectOpen={true}
selectPrivate={true}
value="88cxd9wpzpbpfp8pad78xj75pr"
@@ -1414,7 +1414,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="username"
maxLength="22"
maxLength={22}
onChange={[Function]}
type="text"
value=""
@@ -1447,7 +1447,7 @@ exports[`components/integrations/AbstractIncomingWebhook should match snapshot,
<input
className="form-control"
id="iconURL"
maxLength="1024"
maxLength={1024}
onChange={[Function]}
type="text"
value=""

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

@@ -4,10 +4,28 @@
import React from 'react';
import {shallow} from 'enzyme';
import ChannelSelect from 'components/channel_select';
import AbstractIncomingWebhook from 'components/integrations/abstract_incoming_webhook';
import {Team} from '@mattermost/types/teams';
describe('components/integrations/AbstractIncomingWebhook', () => {
const team = {name: 'team_name'};
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'};
@@ -16,6 +34,15 @@ describe('components/integrations/AbstractIncomingWebhook', () => {
display_name: 'testIncomingWebhook',
channel_id: '88cxd9wpzpbpfp8pad78xj75pr',
description: 'testing',
id: 'test_id',
team_id: 'test_team_id',
create_at: 0,
update_at: 0,
delete_at: 0,
user_id: 'test_user_id',
username: '',
icon_url: '',
channel_locked: false,
};
const enablePostUsernameOverride = true;
const enablePostIconOverride = true;
@@ -104,7 +131,7 @@ describe('components/integrations/AbstractIncomingWebhook', () => {
};
const wrapper = shallow(<AbstractIncomingWebhook {...requiredProps}/>);
wrapper.find('#channelId').simulate('change', evt);
wrapper.find(ChannelSelect).simulate('change', evt);
expect(wrapper.state('channelId')).toBe(newChannelId);
});

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

@@ -1,87 +1,100 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import React, {ChangeEventHandler, FormEvent, MouseEvent, PureComponent} from 'react';
import {FormattedMessage, MessageDescriptor} from 'react-intl';
import {Link} from 'react-router-dom';
import BackstageHeader from 'components/backstage/components/backstage_header';
import ChannelSelect from 'components/channel_select';
import FormError from 'components/form_error';
import SpinnerButton from 'components/spinner_button';
import {Team} from '@mattermost/types/teams';
import {localizeMessage} from 'utils/utils';
import {IncomingWebhook} from '@mattermost/types/integrations';
export default class AbstractIncomingWebhook extends React.PureComponent {
static propTypes = {
interface State {
displayName: string;
description: string;
channelId: string;
channelLocked: boolean;
username: string;
iconURL: string;
saving: boolean;
serverError: string;
clientError: JSX.Element | null;
}
/**
* The current team
*/
team: PropTypes.object.isRequired,
interface Props {
/**
* The header text to render, has id and defaultMessage
*/
header: PropTypes.object.isRequired,
/**
* The current team
*/
team: Team;
/**
* The footer text to render, has id and defaultMessage
*/
footer: PropTypes.object.isRequired,
/**
* The header text to render, has id and defaultMessage
*/
header: MessageDescriptor;
/**
* The spinner loading text to render, has id and defaultMessage
*/
loading: PropTypes.object.isRequired,
/**
* The footer text to render, has id and defaultMessage
*/
footer: MessageDescriptor;
/**
* The server error text after a failed action
*/
serverError: PropTypes.string.isRequired,
/**
* The spinner loading text to render, has id and defaultMessage
*/
loading: MessageDescriptor;
/**
* The hook used to set the initial state
*/
initialHook: PropTypes.object,
/**
* The server error text after a failed action
*/
serverError: string;
/**
* Whether to allow configuration of the default post username.
*/
enablePostUsernameOverride: PropTypes.bool.isRequired,
/**
* The hook used to set the initial state
*/
initialHook?: IncomingWebhook | Record<string, never>;
/**
* Whether to allow configuration of the default post icon.
*/
enablePostIconOverride: PropTypes.bool.isRequired,
/**
* Whether to allow configuration of the default post username.
*/
enablePostUsernameOverride: boolean;
/**
* The async function to run when the action button is pressed
*/
action: PropTypes.func.isRequired,
}
/**
* Whether to allow configuration of the default post icon.
*/
enablePostIconOverride: boolean;
constructor(props) {
/**
* The async function to run when the action button is pressed
*/
action: (hook: IncomingWebhook) => Promise<void>;
}
export default class AbstractIncomingWebhook extends PureComponent<Props, State> {
constructor(props: Props | Readonly<Props>) {
super(props);
this.state = this.getStateFromHook(this.props.initialHook || {});
}
getStateFromHook = (hook) => {
getStateFromHook = (hook: IncomingWebhook | Record<string, never>) => {
return {
displayName: hook.display_name || '',
description: hook.description || '',
channelId: hook.channel_id || '',
channelLocked: hook.channel_locked || false,
username: hook.username || '',
iconURL: hook.icon_url || '',
displayName: hook?.display_name || '',
description: hook?.description || '',
channelId: hook?.channel_id || '',
channelLocked: hook?.channel_locked || false,
username: hook?.username || '',
iconURL: hook?.icon_url || '',
saving: false,
serverError: '',
clientError: null,
};
}
handleSubmit = (e) => {
handleSubmit = (e: MouseEvent<HTMLElement> | FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (this.state.saving) {
@@ -91,7 +104,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
this.setState({
saving: true,
serverError: '',
clientError: '',
clientError: null,
});
if (!this.state.channelId) {
@@ -115,50 +128,56 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
description: this.state.description,
username: this.state.username,
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,
team_id: this.props.initialHook?.team_id || '',
user_id: this.props.initialHook?.user_id || '',
};
this.props.action(hook).then(() => this.setState({saving: false}));
}
updateDisplayName = (e) => {
updateDisplayName: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({
displayName: e.target.value,
});
}
updateDescription = (e) => {
updateDescription: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({
description: e.target.value,
});
}
updateChannelId = (e) => {
updateChannelId: ChangeEventHandler<HTMLSelectElement> = (e) => {
this.setState({
channelId: e.target.value,
});
}
updateChannelLocked = (e) => {
updateChannelLocked: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({
channelLocked: e.target.checked,
});
}
updateUsername = (e) => {
updateUsername: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({
username: e.target.value,
});
}
updateIconURL = (e) => {
updateIconURL: ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({
iconURL: e.target.value,
});
}
render() {
var headerToRender = this.props.header;
var footerToRender = this.props.footer;
const headerToRender = this.props.header;
const footerToRender = this.props.footer;
return (
<div className='backstage-content'>
@@ -177,7 +196,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
<div className='backstage-form'>
<form
className='form-horizontal'
onSubmit={this.handleSubmit}
onSubmit={(e) => this.handleSubmit(e)}
>
<div className='form-group'>
<label
@@ -193,7 +212,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
<input
id='displayName'
type='text'
maxLength='64'
maxLength={64}
className='form-control'
value={this.state.displayName}
onChange={this.updateDisplayName}
@@ -220,7 +239,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
<input
id='description'
type='text'
maxLength='500'
maxLength={500}
className='form-control'
value={this.state.description}
onChange={this.updateDescription}
@@ -245,11 +264,11 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
</label>
<div className='col-md-5 col-sm-8'>
<ChannelSelect
id='channelId'
value={this.state.channelId}
onChange={this.updateChannelId}
selectOpen={true}
selectPrivate={true}
selectDm={false}
/>
<div className='form__help'>
<FormattedMessage
@@ -299,7 +318,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
<input
id='username'
type='text'
maxLength='22'
maxLength={22}
className='form-control'
value={this.state.username}
onChange={this.updateUsername}
@@ -328,7 +347,7 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
<input
id='iconURL'
type='text'
maxLength='1024'
maxLength={1024}
className='form-control'
value={this.state.iconURL}
onChange={this.updateIconURL}
@@ -360,8 +379,8 @@ export default class AbstractIncomingWebhook extends React.PureComponent {
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
spinningText={localizeMessage(this.props.loading.id, this.props.loading.defaultMessage)}
onClick={this.handleSubmit}
spinningText={localizeMessage(this.props.loading.id as string, this.props.loading.defaultMessage as string)}
onClick={(e) => this.handleSubmit(e)}
id='saveWebhook'
>
<FormattedMessage

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent, ReactNode} from 'react';
import React, {ButtonHTMLAttributes, PureComponent, ReactNode} from 'react';
import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
@@ -9,10 +9,9 @@ type Props = {
children?: ReactNode;
spinning: boolean;
spinningText: ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
export default class SpinnerButton extends PureComponent<Props & React.ButtonHTMLAttributes<HTMLButtonElement>> {
export default class SpinnerButton extends PureComponent<Props & ButtonHTMLAttributes<HTMLButtonElement>> {
public static defaultProps: Partial<Props> = {
spinning: false,
}