mirror of
https://github.com/ansible/awx.git
synced 2026-05-23 16:47:45 -02:30
add generic onChange prop to MultiSelect
This commit is contained in:
@@ -5,7 +5,9 @@ import styled from 'styled-components';
|
|||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
transition: all 0.2s ease-out;
|
transition: all 0.2s ease-out;
|
||||||
overflow: hidden;
|
${props => !props.isExpanded && `
|
||||||
|
overflow: hidden;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function ExpandingContainer({ isExpanded, children }) {
|
function ExpandingContainer({ isExpanded, children }) {
|
||||||
@@ -21,6 +23,7 @@ function ExpandingContainer({ isExpanded, children }) {
|
|||||||
css={`
|
css={`
|
||||||
height: ${height}px;
|
height: ${height}px;
|
||||||
`}
|
`}
|
||||||
|
isExpanded={isExpanded}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -45,10 +45,17 @@ class MultiSelect extends Component {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
onAddNewItem: PropTypes.func.isRequired,
|
onAddNewItem: PropTypes.func,
|
||||||
onRemoveItem: PropTypes.func.isRequired,
|
onRemoveItem: PropTypes.func,
|
||||||
|
onChange: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onAddNewItem: () => {},
|
||||||
|
onRemoveItem: () => {},
|
||||||
|
onChange: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -92,19 +99,21 @@ class MultiSelect extends Component {
|
|||||||
|
|
||||||
handleSelection(e, item) {
|
handleSelection(e, item) {
|
||||||
const { chipItems } = this.state;
|
const { chipItems } = this.state;
|
||||||
const { onAddNewItem } = this.props;
|
const { onAddNewItem, onChange } = this.props;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const items = chipItems.concat({ name: item.name, id: item.id });
|
||||||
this.setState({
|
this.setState({
|
||||||
chipItems: chipItems.concat({ name: item.name, id: item.id }),
|
chipItems: items,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
});
|
});
|
||||||
onAddNewItem(item);
|
onAddNewItem(item);
|
||||||
|
onChange(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddItem(event) {
|
handleAddItem(event) {
|
||||||
const { input, chipItems } = this.state;
|
const { input, chipItems } = this.state;
|
||||||
const { onAddNewItem } = this.props;
|
const { onAddNewItem, onChange } = this.props;
|
||||||
const isIncluded = chipItems.some(chipItem => chipItem.name === input);
|
const isIncluded = chipItems.some(chipItem => chipItem.name === input);
|
||||||
|
|
||||||
if (!input) {
|
if (!input) {
|
||||||
@@ -120,12 +129,14 @@ class MultiSelect extends Component {
|
|||||||
}
|
}
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
const items = chipItems.concat({ name: input, id: input });
|
||||||
this.setState({
|
this.setState({
|
||||||
chipItems: chipItems.concat({ name: input, id: input }),
|
chipItems: items,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
input: '',
|
input: '',
|
||||||
});
|
});
|
||||||
onAddNewItem(input);
|
onAddNewItem(input);
|
||||||
|
onChange(items);
|
||||||
} else if (event.key === 'Tab') {
|
} else if (event.key === 'Tab') {
|
||||||
this.setState({ input: '' });
|
this.setState({ input: '' });
|
||||||
}
|
}
|
||||||
@@ -136,12 +147,13 @@ class MultiSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeChip(e, item) {
|
removeChip(e, item) {
|
||||||
const { onRemoveItem } = this.props;
|
const { onRemoveItem, onChange } = this.props;
|
||||||
const { chipItems } = this.state;
|
const { chipItems } = this.state;
|
||||||
const chips = chipItems.filter(chip => chip.id !== item.id);
|
const chips = chipItems.filter(chip => chip.id !== item.id);
|
||||||
|
|
||||||
this.setState({ chipItems: chips });
|
this.setState({ chipItems: chips });
|
||||||
onRemoveItem(item);
|
onRemoveItem(item);
|
||||||
|
onChange(chips);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ describe('<MultiSelect />', () => {
|
|||||||
expect(getInitialChipItems).toBeCalled();
|
expect(getInitialChipItems).toBeCalled();
|
||||||
expect(component.state().chipItems.length).toBe(2);
|
expect(component.state().chipItems.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleSelection add item to chipItems', async () => {
|
test('handleSelection add item to chipItems', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@@ -45,12 +46,15 @@ describe('<MultiSelect />', () => {
|
|||||||
await sleep(1);
|
await sleep(1);
|
||||||
expect(component.state().chipItems.length).toBe(2);
|
expect(component.state().chipItems.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleAddItem adds a chip only when Tab is pressed', () => {
|
test('handleAddItem adds a chip only when Tab is pressed', () => {
|
||||||
const onAddNewItem = jest.fn();
|
const onAddNewItem = jest.fn();
|
||||||
|
const onChange = jest.fn();
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
onAddNewItem={onAddNewItem}
|
onAddNewItem={onAddNewItem}
|
||||||
onRemoveItem={jest.fn()}
|
onRemoveItem={jest.fn()}
|
||||||
|
onChange={onChange}
|
||||||
associatedItems={associatedItems}
|
associatedItems={associatedItems}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
@@ -68,14 +72,18 @@ describe('<MultiSelect />', () => {
|
|||||||
expect(component.state().input.length).toBe(0);
|
expect(component.state().input.length).toBe(0);
|
||||||
expect(component.state().isExpanded).toBe(false);
|
expect(component.state().isExpanded).toBe(false);
|
||||||
expect(onAddNewItem).toBeCalled();
|
expect(onAddNewItem).toBeCalled();
|
||||||
|
expect(onChange).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removeChip removes chip properly', () => {
|
test('removeChip removes chip properly', () => {
|
||||||
const onRemoveItem = jest.fn();
|
const onRemoveItem = jest.fn();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
onAddNewItem={jest.fn()}
|
onAddNewItem={jest.fn()}
|
||||||
onRemoveItem={onRemoveItem}
|
onRemoveItem={onRemoveItem}
|
||||||
|
onChange={onChange}
|
||||||
associatedItems={associatedItems}
|
associatedItems={associatedItems}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
@@ -89,5 +97,6 @@ describe('<MultiSelect />', () => {
|
|||||||
.removeChip(event, { name: 'Foo', id: 1, organization: 1 });
|
.removeChip(event, { name: 'Foo', id: 1, organization: 1 });
|
||||||
expect(component.state().chipItems.length).toBe(1);
|
expect(component.state().chipItems.length).toBe(1);
|
||||||
expect(onRemoveItem).toBeCalled();
|
expect(onRemoveItem).toBeCalled();
|
||||||
|
expect(onChange).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -501,6 +501,7 @@ class JobTemplateForm extends Component {
|
|||||||
id="template-job-slicing"
|
id="template-job-slicing"
|
||||||
name="job_slice_count"
|
name="job_slice_count"
|
||||||
type="number"
|
type="number"
|
||||||
|
min="1"
|
||||||
label={i18n._(t`Job Slicing`)}
|
label={i18n._(t`Job Slicing`)}
|
||||||
tooltip={i18n._(t`Divide the work done by this job template
|
tooltip={i18n._(t`Divide the work done by this job template
|
||||||
into the specified number of job slices, each running the
|
into the specified number of job slices, each running the
|
||||||
@@ -551,6 +552,42 @@ class JobTemplateForm extends Component {
|
|||||||
t`Select the Instance Groups for this Organization to run on.`
|
t`Select the Instance Groups for this Organization to run on.`
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormGroup label={i18n._(t`Job Tags`)} fieldId="template-job-tags">
|
||||||
|
<Tooltip
|
||||||
|
position="right"
|
||||||
|
content={i18n._(t`Tags are useful when you have a large
|
||||||
|
playbook, and you want to run a specific part of a play
|
||||||
|
or task. Use commas to separate multiple tags. Refer to
|
||||||
|
Ansible Tower documentation for details on the usage of
|
||||||
|
tags.`)}
|
||||||
|
>
|
||||||
|
<QuestionCircleIcon />
|
||||||
|
</Tooltip>
|
||||||
|
<MultiSelect
|
||||||
|
onAddNewItem={this.handleNewLabel}
|
||||||
|
onRemoveItem={this.removeLabel}
|
||||||
|
associatedItems={template.job_tags.split(',')}
|
||||||
|
options={loadedLabels}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup label={i18n._(t`Skip Tags`)} fieldId="template-skip-tags">
|
||||||
|
<Tooltip
|
||||||
|
position="right"
|
||||||
|
content={i18n._(t`Skip tags are useful when you have a
|
||||||
|
large playbook, and you want to skip specific parts of a
|
||||||
|
play or task. Use commas to separate multiple tags. Refer
|
||||||
|
to Ansible Tower documentation for details on the usage
|
||||||
|
of tags.`)}
|
||||||
|
>
|
||||||
|
<QuestionCircleIcon />
|
||||||
|
</Tooltip>
|
||||||
|
<MultiSelect
|
||||||
|
onAddNewItem={this.handleNewLabel}
|
||||||
|
onRemoveItem={this.removeLabel}
|
||||||
|
associatedItems={template.skip_tags.split(',')}
|
||||||
|
options={loadedLabels}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
Reference in New Issue
Block a user