Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
// See LICENSE.txt for license information.

import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

export default class FormButton extends PureComponent {
static propTypes = {
executing: PropTypes.bool,
disabled: PropTypes.bool,
executingMessage: PropTypes.node,
defaultMessage: PropTypes.node,
btnClass: PropTypes.string,
extraClasses: PropTypes.string,
saving: PropTypes.bool,
savingMessage: PropTypes.string,
type: PropTypes.string,
};

type Props = {
executing?: boolean;
disabled?: boolean;
executingMessage?: React.ReactNode;
defaultMessage?: React.ReactNode;
btnClass?: string;
extraClasses?: string;
saving?: boolean;
savingMessage?: string;
type?: string;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export default class FormButton extends PureComponent<Props> {
static defaultProps = {
disabled: false,
savingMessage: 'Creating',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,42 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

import Setting from './setting.jsx';

export default class Input extends PureComponent {
static propTypes = {
id: PropTypes.string,
label: PropTypes.node.isRequired,
placeholder: PropTypes.string,
helpText: PropTypes.node,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
addValidate: PropTypes.func.isRequired,
removeValidate: PropTypes.func.isRequired,
maxLength: PropTypes.number,
onChange: PropTypes.func,
disabled: PropTypes.bool,
required: PropTypes.bool,
readOnly: PropTypes.bool,
type: PropTypes.oneOf([
'number',
'input',
'textarea',
'date',
'datetime-local',
]),
};

import React, {PureComponent, ChangeEvent} from 'react';

import Setting from './setting';

type InputType = 'number' | 'input' | 'textarea' | 'date' | 'datetime-local';

type Props = {
id?: string;
label: React.ReactNode;
placeholder?: string;
helpText?: React.ReactNode;
value?: string | number;
addValidate: (fn: () => boolean) => void;
removeValidate: (fn: () => boolean) => void;
maxLength?: number | null;
onChange?: (id: string, value: string | number) => void;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
type?: InputType;
};

type State = {
invalid: boolean;
};

export default class Input extends PureComponent<Props, State> {
static defaultProps = {
type: 'input',
type: 'input' as InputType,
maxLength: null,
required: false,
readOnly: false,
};

constructor(props) {
constructor(props: Props) {
super(props);

this.state = {invalid: false};
}

Expand All @@ -57,33 +52,35 @@ export default class Input extends PureComponent {
}
}

componentDidUpdate(prevProps, prevState) {
componentDidUpdate(prevProps: Props, prevState: State) {
if (prevState.invalid && this.props.value !== prevProps.value) {
this.setState({invalid: false}); //eslint-disable-line react/no-did-update-set-state
}
}

handleChange = (e) => {
handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (this.props.type === 'number') {
this.props.onChange(this.props.id, parseInt(e.target.value, 10));
const numValue = e.target.value === '' ? '' : Number(e.target.value);
this.props.onChange?.(this.props.id ?? '', numValue);
} else {
this.props.onChange(this.props.id, e.target.value);
this.props.onChange?.(this.props.id ?? '', e.target.value);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

isValid = () => {
isValid = (): boolean => {
if (!this.props.required) {
return true;
}
const valid = this.props.value && this.props.value.toString().length !== 0;
const {value} = this.props;
const valid = value !== undefined && value !== null && value !== '';
this.setState({invalid: !valid});
return valid;
};

render() {
const requiredMsg = 'This field is required.';
const style = getStyle();
const value = this.props.value || '';
const value = this.props.value ?? '';

let validationError = null;
if (this.props.required && this.state.invalid) {
Expand All @@ -103,7 +100,7 @@ export default class Input extends PureComponent {
type='text'
placeholder={this.props.placeholder}
value={value}
maxLength={this.props.maxLength}
maxLength={this.props.maxLength ?? undefined}
onChange={this.handleChange}
disabled={this.props.disabled}
readOnly={this.props.readOnly}
Expand All @@ -117,7 +114,7 @@ export default class Input extends PureComponent {
type='number'
placeholder={this.props.placeholder}
value={value}
maxLength={this.props.maxLength}
maxLength={this.props.maxLength ?? undefined}
onChange={this.handleChange}
disabled={this.props.disabled}
readOnly={this.props.readOnly}
Expand All @@ -130,10 +127,10 @@ export default class Input extends PureComponent {
resize='none'
id={this.props.id}
className='form-control'
rows='5'
rows={5}
placeholder={this.props.placeholder}
value={value}
maxLength={this.props.maxLength}
maxLength={this.props.maxLength ?? undefined}
onChange={this.handleChange}
disabled={this.props.disabled}
readOnly={this.props.readOnly}
Expand Down Expand Up @@ -170,6 +167,6 @@ export default class Input extends PureComponent {

const getStyle = () => ({
textarea: {
resize: 'none',
resize: 'none' as const,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,59 @@
// See LICENSE.txt for license information.

import React from 'react';
import PropTypes from 'prop-types';
import type {Theme} from 'mattermost-redux/selectors/entities/preferences';

import {components} from 'react-select';

import ReactSelectSetting from 'components/react_select_setting';
import Input from 'components/input';
import {isTeamField} from 'utils/jira_issue_metadata';

import {JiraFieldCustomTypeEnums} from 'types/model';
import {IssueMetadata, JiraField, AllowedValue, JiraFieldCustomTypeEnums} from 'types/model';

import JiraEpicSelector from './data_selectors/jira_epic_selector';
import JiraAutoCompleteSelector from './data_selectors/jira_autocomplete_selector';
import JiraUserSelector from './data_selectors/jira_user_selector';
import JiraTeamSelector from './data_selectors/jira_team_selector';
import JiraSprintSelector from './data_selectors/jira_sprint_selector';

export default class JiraField extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
instanceID: PropTypes.string.isRequired,
field: PropTypes.object.isRequired,
projectKey: PropTypes.string.isRequired,
issueMetadata: PropTypes.object.isRequired,
obeyRequired: PropTypes.bool,
onChange: PropTypes.func.isRequired,
value: PropTypes.any,
isFilter: PropTypes.bool,
theme: PropTypes.object.isRequired,
addValidate: PropTypes.func.isRequired,
removeValidate: PropTypes.func.isRequired,
};

type JiraFieldData = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JiraField, IssueMetadata, etc. are already exported from types/model. The local Field, FieldSchema, AllowedValue types are structurally narrower than the model's JiraField, which is why isTeamField(field) would fail typechecking, isTeamField is typed against JiraField | FilterField not this local shape. Use the canonical model types so the rest of the codebase stays consistent.

id: string;
name?: string;
value?: string;
iconUrl?: string;
allowedValue: AllowedValue;
label: string;
};

type Props = {
id: string;
instanceID: string;
field: JiraField;
projectKey?: string;
issueMetadata: IssueMetadata | null;
obeyRequired?: boolean;
onChange: (id: string, value: any) => void;
value?: any;
isFilter?: boolean;
theme: Theme;
addValidate: (fn: () => boolean) => void;
removeValidate: (fn: () => boolean) => void;
};

type IconOptionProps = {
data: JiraFieldData;
label: string;
} & any;

export default class JiraField extends React.Component<Props> {
static defaultProps = {
obeyRequired: true,
};

static IconOption = (props) => {
static IconOption = (props: IconOptionProps) => {
let img = null;
if (props.data.allowedValue.iconUrl) {
if (props.data.allowedValue?.iconUrl) {
img = (
<img
style={getStyle().jiraIcon}
Expand All @@ -49,10 +63,7 @@ export default class JiraField extends React.Component {
);
}
return (
<components.Option
{...props}
style={getStyle().selectComponent}
>
<components.Option {...props}>
{img}
{props.data.label}
</components.Option>
Expand Down Expand Up @@ -111,7 +122,7 @@ export default class JiraField extends React.Component {
<JiraEpicSelector

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on line 93 style.getStyle() only returns { jiraIcon: {...} } so selectComponent is undefined and this is a style={undefined} no-op. The original .jsx had the same bug but it was hidden because JSX didn't typecheck. Either restore the missing entry or drop the style prop.

{...selectProps}
issueMetadata={this.props.issueMetadata}
onChange={(value) => {
onChange={(value: string) => {
this.props.onChange(this.props.id, value);
}}
value={this.props.value}
Expand All @@ -126,7 +137,7 @@ export default class JiraField extends React.Component {
<JiraSprintSelector
{...selectProps}
projectKey={this.props.projectKey}
onChange={(selected) => {
onChange={(selected: string | null) => {
if (selected) {
this.props.onChange(this.props.id, Number(selected));
} else {
Expand All @@ -144,7 +155,7 @@ export default class JiraField extends React.Component {
<JiraAutoCompleteSelector
{...selectProps}
fieldName={field.name}
onChange={(value) => {
onChange={(value: string[]) => {
this.props.onChange(this.props.id, value);
}}
value={this.props.value || []}
Expand All @@ -159,7 +170,7 @@ export default class JiraField extends React.Component {
{...selectProps}
projectKey={this.props.projectKey}
fieldName={field.name}
onChange={(value) => {
onChange={(value: string) => {
this.props.onChange(this.props.id, value);
}}
value={this.props.value}
Expand All @@ -174,7 +185,7 @@ export default class JiraField extends React.Component {
<JiraTeamSelector
{...selectProps}
fieldName={field.name}
onChange={(selected) => {
onChange={(selected: string | null) => {
if (selected) {
this.props.onChange(this.props.id, {id: selected});
} else {
Expand Down Expand Up @@ -249,21 +260,21 @@ export default class JiraField extends React.Component {

if (field.allowedValues && field.allowedValues.length) {
const options = field.allowedValues.map((allowedValue) => {
const label = allowedValue.name ? allowedValue.name : allowedValue.value;
const label = allowedValue.name ? allowedValue.name : (allowedValue.value ?? '');
return (
{value: allowedValue.id, label, allowedValue}
);
});

if (field.schema.type === 'array') {
let selectedOptions = [];
let selectedOptions: {value: string; label: string; allowedValue: AllowedValue}[] = [];
if (this.props.value) {
const values = this.props.value.map((v) => v.id);
const values = this.props.value.map((v: {id: string}) => v.id);
selectedOptions = options.filter((opt) => values.includes(opt.value));
}

const onChange = (id, val) => {
const newValue = val ? val.map((v) => ({id: v})) : [];
const onChange = (id: string, val: {value: string}[] | null) => {
const newValue = val ? val.map((v) => ({id: v.value})) : [];
this.props.onChange(id, newValue);
};

Expand All @@ -284,7 +295,7 @@ export default class JiraField extends React.Component {
{...selectProps}
name={this.props.id}
options={options}
onChange={(id, val) => this.props.onChange(id, {id: val})}
onChange={(id: string, val: string) => this.props.onChange(id, {id: val})}
isMulti={false}
value={options.find((option) => option.value === (this.props.value && this.props.value.id))}
components={{Option: JiraField.IconOption}}
Expand All @@ -295,7 +306,7 @@ export default class JiraField extends React.Component {
}
}

export function isFieldSupported(field) {
export function isFieldSupported(field: JiraField | null | undefined) {
if (!field || !field.schema) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// See LICENSE.txt for license information.

import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {bindActionCreators, Dispatch} from 'redux';

import {searchIssues} from 'actions';

import JiraIssueSelector from './jira_issue_selector';

const mapDispatchToProps = (dispatch) => bindActionCreators({
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
searchIssues,
}, dispatch);

Expand Down
Loading