[MM-47090]: Migrate "components/integrations/abstract_command.jsx" to Typescript (#23325)

Этот коммит содержится в:
Sai Deepesh
2023-07-02 12:25:10 +05:30
коммит произвёл GitHub
родитель 8abc8ed65c
Коммит 70d0a0fe70
6 изменённых файлов: 177 добавлений и 154 удалений

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

@@ -43,7 +43,7 @@ exports[`components/integrations/AbstractCommand 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="display_name" value="display_name"
@@ -76,7 +76,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<input <input
className="form-control" className="form-control"
id="description" id="description"
maxLength="128" maxLength={128}
onChange={[Function]} onChange={[Function]}
type="text" type="text"
value="description" value="description"
@@ -177,7 +177,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="url" id="url"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -258,7 +258,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="username" id="username"
maxLength="64" maxLength={64}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -297,7 +297,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="iconUrl" id="iconUrl"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -367,7 +367,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="autocompleteHint" id="autocompleteHint"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -406,7 +406,7 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="description" id="description"
maxLength="128" maxLength={128}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -462,7 +462,9 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
id="Footer" id="Footer"
/> />
</SpinnerButton> </SpinnerButton>
renderExtra <div>
renderExtra
</div>
</div> </div>
</form> </form>
</div> </div>
@@ -512,7 +514,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<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="display_name" value="display_name"
@@ -545,7 +547,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<input <input
className="form-control" className="form-control"
id="description" id="description"
maxLength="128" maxLength={128}
onChange={[Function]} onChange={[Function]}
type="text" type="text"
value="description" value="description"
@@ -646,7 +648,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="url" id="url"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -727,7 +729,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="username" id="username"
maxLength="64" maxLength={64}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -766,7 +768,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="iconUrl" id="iconUrl"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -836,7 +838,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="autocompleteHint" id="autocompleteHint"
maxLength="1024" maxLength={1024}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -875,7 +877,7 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
<LocalizedInput <LocalizedInput
className="form-control" className="form-control"
id="description" id="description"
maxLength="128" maxLength={128}
onChange={[Function]} onChange={[Function]}
placeholder={ placeholder={
Object { Object {
@@ -934,7 +936,9 @@ exports[`components/integrations/AbstractCommand should match snapshot, displays
id="Footer" id="Footer"
/> />
</SpinnerButton> </SpinnerButton>
renderExtra <div>
renderExtra
</div>
</div> </div>
</form> </form>
</div> </div>

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

@@ -1,16 +1,18 @@
// 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 React from 'react'; import React, {FormEvent} from 'react';
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import AbstractCommand from 'components/integrations/abstract_command.jsx'; import AbstractCommand from 'components/integrations/abstract_command';
import {TestHelper} from 'utils/test_helper';
describe('components/integrations/AbstractCommand', () => { describe('components/integrations/AbstractCommand', () => {
const header = {id: 'Header', defaultMessage: 'Header'}; const header = {id: 'Header', defaultMessage: 'Header'};
const footer = {id: 'Footer', defaultMessage: 'Footer'}; const footer = {id: 'Footer', defaultMessage: 'Footer'};
const loading = {id: 'Loading', defaultMessage: 'Loading'}; const loading = {id: 'Loading', defaultMessage: 'Loading'};
const method: 'G' | 'P' | '' = 'G';
const command = { const command = {
id: 'r5tpgt4iepf45jt768jz84djic', id: 'r5tpgt4iepf45jt768jz84djic',
display_name: 'display_name', display_name: 'display_name',
@@ -20,23 +22,21 @@ describe('components/integrations/AbstractCommand', () => {
auto_complete_hint: 'auto_complete_hint', auto_complete_hint: 'auto_complete_hint',
auto_complete_desc: 'auto_complete_desc', auto_complete_desc: 'auto_complete_desc',
token: 'jb6oyqh95irpbx8fo9zmndkp1r', token: 'jb6oyqh95irpbx8fo9zmndkp1r',
create_at: '1499722850203', create_at: 1499722850203,
creator_id: '88oybd1dwfdoxpkpw1h5kpbyco', creator_id: '88oybd1dwfdoxpkpw1h5kpbyco',
delete_at: 0, delete_at: 0,
icon_url: 'https://google.com/icon', icon_url: 'https://google.com/icon',
method: 'G', method,
team_id: 'm5gix3oye3du8ghk4ko6h9cq7y', team_id: 'm5gix3oye3du8ghk4ko6h9cq7y',
update_at: 1504468859001, update_at: 1504468859001,
url: 'https://google.com/command', url: 'https://google.com/command',
username: 'username', username: 'username',
}; };
const team = { const team = TestHelper.getTeamMock({name: 'test', id: command.team_id});
name: 'test',
id: command.team_id,
};
const action = jest.fn().mockImplementation( const action = jest.fn().mockImplementation(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
process.nextTick(() => resolve()); process.nextTick(() => resolve());
}); });
}, },
@@ -47,14 +47,14 @@ describe('components/integrations/AbstractCommand', () => {
header, header,
footer, footer,
loading, loading,
renderExtra: 'renderExtra', renderExtra: <div>{'renderExtra'}</div>,
serverError: '', serverError: '',
initialCommand: command, initialCommand: command,
action, action,
}; };
test('should match snapshot', () => { test('should match snapshot', () => {
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...baseProps}/>, <AbstractCommand {...baseProps}/>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@@ -63,7 +63,7 @@ describe('components/integrations/AbstractCommand', () => {
test('should match snapshot, displays client error', () => { test('should match snapshot, displays client error', () => {
const newSeverError = 'server error'; const newSeverError = 'server error';
const props = {...baseProps, serverError: newSeverError}; const props = {...baseProps, serverError: newSeverError};
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...props}/>, <AbstractCommand {...props}/>,
); );
@@ -75,7 +75,7 @@ describe('components/integrations/AbstractCommand', () => {
}); });
test('should call action function', () => { test('should call action function', () => {
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...baseProps}/>, <AbstractCommand {...baseProps}/>,
); );
@@ -86,7 +86,7 @@ describe('components/integrations/AbstractCommand', () => {
}); });
test('should match object returned by getStateFromCommand', () => { test('should match object returned by getStateFromCommand', () => {
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...baseProps}/>, <AbstractCommand {...baseProps}/>,
); );
@@ -109,67 +109,77 @@ describe('components/integrations/AbstractCommand', () => {
}); });
test('should match state when method is called', () => { test('should match state when method is called', () => {
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...baseProps}/>, <AbstractCommand {...baseProps}/>,
); );
const displayName = 'new display_name'; const displayName = 'new display_name';
wrapper.instance().updateDisplayName({target: {value: displayName}}); const displayNameEvent = {preventDefault: jest.fn(), target: {value: displayName}} as any;
wrapper.instance().updateDisplayName(displayNameEvent);
expect(wrapper.state('displayName')).toEqual(displayName); expect(wrapper.state('displayName')).toEqual(displayName);
const description = 'new description'; const description = 'new description';
wrapper.instance().updateDescription({target: {value: description}}); const descriptionEvent = {preventDefault: jest.fn(), target: {value: description}} as any;
wrapper.instance().updateDescription(descriptionEvent);
expect(wrapper.state('description')).toEqual(description); expect(wrapper.state('description')).toEqual(description);
const trigger = 'new trigger'; const trigger = 'new trigger';
wrapper.instance().updateTrigger({target: {value: trigger}}); const triggerEvent = {preventDefault: jest.fn(), target: {value: trigger}} as any;
wrapper.instance().updateTrigger(triggerEvent);
expect(wrapper.state('trigger')).toEqual(trigger); expect(wrapper.state('trigger')).toEqual(trigger);
const url = 'new url'; const url = 'new url';
wrapper.instance().updateUrl({target: {value: url}}); const urlEvent = {preventDefault: jest.fn(), target: {value: url}} as any;
wrapper.instance().updateUrl(urlEvent);
expect(wrapper.state('url')).toEqual(url); expect(wrapper.state('url')).toEqual(url);
const method = 'new method'; const method = 'P';
wrapper.instance().updateMethod({target: {value: method}}); const methodEvent = {preventDefault: jest.fn(), target: {value: method}} as any;
wrapper.instance().updateMethod(methodEvent);
expect(wrapper.state('method')).toEqual(method); expect(wrapper.state('method')).toEqual(method);
const username = 'new username'; const username = 'new username';
wrapper.instance().updateUsername({target: {value: username}}); const usernameEvent = {preventDefault: jest.fn(), target: {value: username}} as any;
wrapper.instance().updateUsername(usernameEvent);
expect(wrapper.state('username')).toEqual(username); expect(wrapper.state('username')).toEqual(username);
const iconUrl = 'new iconUrl'; const iconUrl = 'new iconUrl';
wrapper.instance().updateIconUrl({target: {value: iconUrl}}); const iconUrlEvent = {preventDefault: jest.fn(), target: {value: iconUrl}} as any;
wrapper.instance().updateIconUrl(iconUrlEvent);
expect(wrapper.state('iconUrl')).toEqual(iconUrl); expect(wrapper.state('iconUrl')).toEqual(iconUrl);
wrapper.instance().updateAutocomplete({target: {checked: true}}); const trueUpdateAutocompleteEvent = {target: {checked: true}} as any;
const falseeUpdateAutocompleteEvent = {target: {checked: false}} as any;
wrapper.instance().updateAutocomplete(trueUpdateAutocompleteEvent);
expect(wrapper.state('autocomplete')).toEqual(true); expect(wrapper.state('autocomplete')).toEqual(true);
wrapper.instance().updateAutocomplete({target: {checked: false}}); wrapper.instance().updateAutocomplete(falseeUpdateAutocompleteEvent);
expect(wrapper.state('autocomplete')).toEqual(false); expect(wrapper.state('autocomplete')).toEqual(false);
const autocompleteHint = 'new autocompleteHint'; const autocompleteHint = 'new autocompleteHint';
wrapper.instance().updateAutocompleteHint({target: {value: autocompleteHint}}); const autocompleteHintEvent = {preventDefault: jest.fn(), target: {value: autocompleteHint}} as any;
wrapper.instance().updateAutocompleteHint(autocompleteHintEvent);
expect(wrapper.state('autocompleteHint')).toEqual(autocompleteHint); expect(wrapper.state('autocompleteHint')).toEqual(autocompleteHint);
const autocompleteDescription = 'new autocompleteDescription'; const autocompleteDescription = 'new autocompleteDescription';
wrapper.instance().updateAutocompleteDescription({target: {value: autocompleteDescription}}); const autocompleteDescriptionEvent = {preventDefault: jest.fn(), target: {value: autocompleteDescription}} as any;
wrapper.instance().updateAutocompleteDescription(autocompleteDescriptionEvent);
expect(wrapper.state('autocompleteDescription')).toEqual(autocompleteDescription); expect(wrapper.state('autocompleteDescription')).toEqual(autocompleteDescription);
}); });
test('should match state when handleSubmit is called', () => { test('should match state when handleSubmit is called', () => {
const newAction = jest.fn().mockImplementation( const newAction = jest.fn().mockImplementation(
() => { () => {
return new Promise((resolve) => { return new Promise<void>((resolve) => {
process.nextTick(() => resolve()); process.nextTick(() => resolve());
}); });
}, },
); );
const props = {...baseProps, action: newAction}; const props = {...baseProps, action: newAction};
const wrapper = shallow( const wrapper = shallow<AbstractCommand>(
<AbstractCommand {...props}/>, <AbstractCommand {...props}/>,
); );
expect(newAction).toHaveBeenCalledTimes(0); expect(newAction).toHaveBeenCalledTimes(0);
const evt = {preventDefault: jest.fn()}; const evt = {preventDefault: jest.fn()} as unknown as FormEvent<Element>;
const handleSubmit = wrapper.instance().handleSubmit; const handleSubmit = wrapper.instance().handleSubmit;
handleSubmit(evt); handleSubmit(evt);
expect(wrapper.state('saving')).toEqual(true); expect(wrapper.state('saving')).toEqual(true);

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

@@ -1,9 +1,9 @@
// 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 React from 'react'; import React, {ChangeEvent} from 'react';
import PropTypes from 'prop-types'; import {FormattedMessage, MessageDescriptor} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import BackstageHeader from 'components/backstage/components/backstage_header'; import BackstageHeader from 'components/backstage/components/backstage_header';
@@ -12,81 +12,98 @@ import * as Utils from 'utils/utils';
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 LocalizedInput from 'components/localized_input/localized_input'; import LocalizedInput from 'components/localized_input/localized_input';
import ExternalLink from 'components/external_link';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import ExternalLink from 'components/external_link'; import {Command} from '@mattermost/types/integrations';
import {Team} from '@mattermost/types/teams';
const REQUEST_POST = 'P'; const REQUEST_POST = 'P';
const REQUEST_GET = 'G'; const REQUEST_GET = 'G';
export default class AbstractCommand extends React.PureComponent { type Props = {
static propTypes = {
/** /**
* The current team * The current team
*/ */
team: PropTypes.object.isRequired, team: Team;
/** /**
* The header text to render, has id and defaultMessage * The header text to render, has id and defaultMessage
*/ */
header: PropTypes.object.isRequired, header: MessageDescriptor;
/** /**
* The footer text to render, has id and defaultMessage * The footer text to render, has id and defaultMessage
*/ */
footer: PropTypes.object.isRequired, footer: MessageDescriptor;
/** /**
* The spinner loading text to render, has id and defaultMessage * The spinner loading text to render, has id and defaultMessage
*/ */
loading: PropTypes.object.isRequired, loading: MessageDescriptor;
/** /**
* Any extra component/node to render * Any extra component/node to render
*/ */
renderExtra: PropTypes.node.isRequired, renderExtra?: JSX.Element;
/** /**
* The server error text after a failed action * The server error text after a failed action
*/ */
serverError: PropTypes.string.isRequired, serverError: string;
/** /**
* The Command used to set the initial state * The Command used to set the initial state
*/ */
initialCommand: PropTypes.object, initialCommand?: Partial<Command>;
/** /**
* The async function to run when the action button is pressed * The async function to run when the action button is pressed
*/ */
action: PropTypes.func.isRequired, action: (command: Command) => Promise<void>;
}; }
constructor(props) { type State= {
saving: boolean;
clientError: null | JSX.Element | string;
trigger: string;
displayName: string;
description: string;
url: string;
method: 'P' | 'G' | '';
username: string;
iconUrl: string;
autocomplete: boolean;
autocompleteHint: string;
autocompleteDescription: string;
}
export default class AbstractCommand extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props); super(props);
this.state = this.getStateFromCommand(this.props.initialCommand || {}); this.state = this.getStateFromCommand(this.props.initialCommand || {});
} }
getStateFromCommand = (command) => { getStateFromCommand = (command: Props['initialCommand']) => {
return { return {
displayName: command.display_name || '', displayName: command?.display_name ?? '',
description: command.description || '', description: command?.description ?? '',
trigger: command.trigger || '', trigger: command?.trigger ?? '',
url: command.url || '', url: command?.url ?? '',
method: command.method || REQUEST_POST, method: command?.method ?? REQUEST_POST,
username: command.username || '', username: command?.username ?? '',
iconUrl: command.icon_url || '', iconUrl: command?.icon_url ?? '',
autocomplete: command.auto_complete || false, autocomplete: command?.auto_complete ?? false,
autocompleteHint: command.auto_complete_hint || '', autocompleteHint: command?.auto_complete_hint ?? '',
autocompleteDescription: command.auto_complete_desc || '', autocompleteDescription: command?.auto_complete_desc ?? '',
saving: false, saving: false,
clientError: null, clientError: null,
}; };
}; };
handleSubmit = (e) => { handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (this.state.saving) { if (this.state.saving) {
@@ -113,11 +130,19 @@ export default class AbstractCommand extends React.PureComponent {
icon_url: this.state.iconUrl, icon_url: this.state.iconUrl,
auto_complete: this.state.autocomplete, auto_complete: this.state.autocomplete,
team_id: this.props.team.id, team_id: this.props.team.id,
auto_complete_desc: '',
auto_complete_hint: '',
token: '',
create_at: 0,
update_at: 0,
delete_at: 0,
id: '',
creator_id: '',
}; };
if (command.auto_complete) { if (command.auto_complete) {
command.auto_complete_desc = this.state.autocompleteDescription; command.auto_complete_desc = this.state.autocompleteDescription ?? '';
command.auto_complete_hint = this.state.autocompleteHint; command.auto_complete_hint = this.state.autocompleteHint ?? '';
} }
if (!command.trigger) { if (!command.trigger) {
@@ -197,61 +222,64 @@ export default class AbstractCommand extends React.PureComponent {
this.props.action(command).then(() => this.setState({saving: false})); this.props.action(command).then(() => this.setState({saving: false}));
}; };
updateDisplayName = (e) => { updateDisplayName = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
displayName: e.target.value, displayName: e.target.value,
}); });
}; };
updateDescription = (e) => { updateDescription = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
description: e.target.value, description: e.target.value,
}); });
}; };
updateTrigger = (e) => { updateTrigger = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
trigger: e.target.value, trigger: e.target.value,
}); });
}; };
updateUrl = (e) => { updateUrl = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
url: e.target.value, url: e.target.value,
}); });
}; };
updateMethod = (e) => { updateMethod = (e: ChangeEvent<HTMLSelectElement>) => {
this.setState({ const methodValue = e.target.value;
method: e.target.value, if (methodValue === 'P' || methodValue === 'G' || methodValue === '') {
}); this.setState({
method: methodValue,
});
}
}; };
updateUsername = (e) => { updateUsername = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
username: e.target.value, username: e.target.value,
}); });
}; };
updateIconUrl = (e) => { updateIconUrl = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
iconUrl: e.target.value, iconUrl: e.target.value,
}); });
}; };
updateAutocomplete = (e) => { updateAutocomplete = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
autocomplete: e.target.checked, autocomplete: e.target.checked,
}); });
}; };
updateAutocompleteHint = (e) => { updateAutocompleteHint = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
autocompleteHint: e.target.value, autocompleteHint: e.target.value,
}); });
}; };
updateAutocompleteDescription = (e) => { updateAutocompleteDescription = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
autocompleteDescription: e.target.value, autocompleteDescription: e.target.value,
}); });
@@ -277,7 +305,7 @@ export default class AbstractCommand extends React.PureComponent {
<LocalizedInput <LocalizedInput
id='autocompleteHint' id='autocompleteHint'
type='text' type='text'
maxLength='1024' maxLength={1024}
className='form-control' className='form-control'
value={this.state.autocompleteHint} value={this.state.autocompleteHint}
onChange={this.updateAutocompleteHint} onChange={this.updateAutocompleteHint}
@@ -308,7 +336,7 @@ export default class AbstractCommand extends React.PureComponent {
<LocalizedInput <LocalizedInput
id='description' id='description'
type='text' type='text'
maxLength='128' maxLength={128}
className='form-control' className='form-control'
value={this.state.autocompleteDescription} value={this.state.autocompleteDescription}
onChange={this.updateAutocompleteDescription} onChange={this.updateAutocompleteDescription}
@@ -358,7 +386,7 @@ export default class AbstractCommand 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}
@@ -385,7 +413,7 @@ export default class AbstractCommand extends React.PureComponent {
<input <input
id='description' id='description'
type='text' type='text'
maxLength='128' maxLength={128}
className='form-control' className='form-control'
value={this.state.description} value={this.state.description}
onChange={this.updateDescription} onChange={this.updateDescription}
@@ -465,7 +493,7 @@ export default class AbstractCommand extends React.PureComponent {
<LocalizedInput <LocalizedInput
id='url' id='url'
type='text' type='text'
maxLength='1024' maxLength={1024}
className='form-control' className='form-control'
value={this.state.url} value={this.state.url}
onChange={this.updateUrl} onChange={this.updateUrl}
@@ -525,7 +553,7 @@ export default class AbstractCommand extends React.PureComponent {
<LocalizedInput <LocalizedInput
id='username' id='username'
type='text' type='text'
maxLength='64' maxLength={64}
className='form-control' className='form-control'
value={this.state.username} value={this.state.username}
onChange={this.updateUsername} onChange={this.updateUsername}
@@ -553,7 +581,7 @@ export default class AbstractCommand extends React.PureComponent {
<LocalizedInput <LocalizedInput
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}
@@ -612,7 +640,7 @@ export default class AbstractCommand extends React.PureComponent {
className='btn btn-primary' className='btn btn-primary'
type='submit' type='submit'
spinning={this.state.saving} spinning={this.state.saving}
spinningText={Utils.localizeMessage(this.props.loading.id, this.props.loading.defaultMessage)} spinningText={Utils.localizeMessage(this.props.loading?.id ?? '', this.props.loading?.defaultMessage as string)}
onClick={this.handleSubmit} onClick={this.handleSubmit}
id='saveCommand' id='saveCommand'
> >

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

@@ -3,25 +3,9 @@
exports[`components/integrations/AddCommand should match snapshot 1`] = ` exports[`components/integrations/AddCommand should match snapshot 1`] = `
<AbstractCommand <AbstractCommand
action={[Function]} action={[Function]}
footer={ footer="Save"
Object { header="Add"
"defaultMessage": "Save", loading="Saving..."
"id": "add_command.save",
}
}
header={
Object {
"defaultMessage": "Add",
"id": "integrations.add",
}
}
loading={
Object {
"defaultMessage": "Saving...",
"id": "add_command.saving",
}
}
renderExtra=""
serverError="" serverError=""
team={ team={
Object { Object {

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

@@ -3,19 +3,14 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
import {useHistory} from 'react-router-dom'; import {useHistory} from 'react-router-dom';
import {useIntl, MessageDescriptor} from 'react-intl';
import {Command} from '@mattermost/types/integrations'; import {Command} from '@mattermost/types/integrations';
import {Team} from '@mattermost/types/teams'; import {Team} from '@mattermost/types/teams';
import {ActionResult} from 'mattermost-redux/types/actions.js'; import {ActionResult} from 'mattermost-redux/types/actions.js';
import {t} from 'utils/i18n'; import AbstractCommand from '../abstract_command';
import AbstractCommand from '../abstract_command.jsx';
const HEADER = {id: t('integrations.add'), defaultMessage: 'Add'};
const FOOTER = {id: t('add_command.save'), defaultMessage: 'Save'};
const LOADING = {id: t('add_command.saving'), defaultMessage: 'Saving...'};
export type Props = { export type Props = {
@@ -35,7 +30,10 @@ export type Props = {
const AddCommand = ({team, actions}: Props) => { const AddCommand = ({team, actions}: Props) => {
const history = useHistory(); const history = useHistory();
const {formatMessage} = useIntl();
const headerMessage = formatMessage({id: ('integrations.add'), defaultMessage: 'Add'}) as MessageDescriptor;
const footerMessage = formatMessage({id: ('add_command.save'), defaultMessage: 'Save'}) as MessageDescriptor;
const loadingMessage = formatMessage({id: ('add_command.saving'), defaultMessage: 'Saving...'}) as MessageDescriptor;
const [serverError, setServerError] = useState(''); const [serverError, setServerError] = useState('');
const addCommand = async (command: Command) => { const addCommand = async (command: Command) => {
@@ -55,10 +53,9 @@ const AddCommand = ({team, actions}: Props) => {
return ( return (
<AbstractCommand <AbstractCommand
team={team} team={team}
header={HEADER} header={headerMessage}
footer={FOOTER} footer={footerMessage}
loading={LOADING} loading={loadingMessage}
renderExtra={''}
action={addCommand} action={addCommand}
serverError={serverError} serverError={serverError}
/> />

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

@@ -12,7 +12,7 @@ import {getHistory} from 'utils/browser_history';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import LoadingScreen from 'components/loading_screen'; import LoadingScreen from 'components/loading_screen';
import ConfirmModal from 'components/confirm_modal'; import ConfirmModal from 'components/confirm_modal';
import AbstractCommand from '../abstract_command.jsx'; import AbstractCommand from '../abstract_command';
const HEADER = {id: t('integrations.edit'), defaultMessage: 'Edit'}; const HEADER = {id: t('integrations.edit'), defaultMessage: 'Edit'};
const FOOTER = {id: t('edit_command.update'), defaultMessage: 'Update'}; const FOOTER = {id: t('edit_command.update'), defaultMessage: 'Update'};