mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 04:17:36 -02:30
Fixes failing test and addresses PR issues
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import {
|
import { encodeQueryString } from '@util/qs';
|
||||||
encodeQueryString
|
|
||||||
} from '@util/qs';
|
|
||||||
|
|
||||||
const defaultHttp = axios.create({
|
const defaultHttp = axios.create({
|
||||||
xsrfCookieName: 'csrftoken',
|
xsrfCookieName: 'csrftoken',
|
||||||
@@ -28,7 +26,7 @@ class Base {
|
|||||||
|
|
||||||
read(params) {
|
read(params) {
|
||||||
return this.http.get(this.baseUrl, {
|
return this.http.get(this.baseUrl, {
|
||||||
params
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class JobTemplates extends InstanceGroupsMixin(Base) {
|
|||||||
|
|
||||||
this.launch = this.launch.bind(this);
|
this.launch = this.launch.bind(this);
|
||||||
this.readLaunch = this.readLaunch.bind(this);
|
this.readLaunch = this.readLaunch.bind(this);
|
||||||
this.updateLabels = this.updateLabels.bind(this);
|
this.associateLabel = this.associateLabel.bind(this);
|
||||||
|
this.disassociateLabel = this.disassociateLabel.bind(this);
|
||||||
|
this.generateLabel = this.generateLabel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(id, data) {
|
launch(id, data) {
|
||||||
@@ -19,8 +21,16 @@ class JobTemplates extends InstanceGroupsMixin(Base) {
|
|||||||
return this.http.get(`${this.baseUrl}${id}/launch/`);
|
return this.http.get(`${this.baseUrl}${id}/launch/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLabels(id, data) {
|
associateLabel(id, label) {
|
||||||
return this.http.post(`${this.baseUrl}${id}/labels/`, data)
|
return this.http.post(`${this.baseUrl}${id}/labels/`, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
disassociateLabel(id, label) {
|
||||||
|
return this.http.post(`${this.baseUrl}${id}/labels/`, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateLabel(orgId, label) {
|
||||||
|
return this.http.post(`${this.baseUrl}${orgId}/labels/`, label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,10 +51,9 @@ class MultiSelect extends Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.myRef = React.createRef();
|
|
||||||
this.state = {
|
this.state = {
|
||||||
input: '',
|
input: '',
|
||||||
chipItems: [],
|
chipItems: this.getInitialChipItems(),
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
};
|
};
|
||||||
this.handleAddItem = this.handleAddItem.bind(this);
|
this.handleAddItem = this.handleAddItem.bind(this);
|
||||||
@@ -65,57 +64,58 @@ class MultiSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.renderChips();
|
|
||||||
document.addEventListener('mousedown', this.handleClick, false);
|
document.addEventListener('mousedown', this.handleClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('mousedown', this.handleClick, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitialChipItems() {
|
||||||
|
const { associatedItems } = this.props;
|
||||||
|
return associatedItems.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
id: item.id,
|
||||||
|
organization: item.organization,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
handleClick(e, option) {
|
handleClick(e, option) {
|
||||||
if (this.node && this.node.contains(e.target)) {
|
if (this.node && this.node.contains(e.target)) {
|
||||||
if (option) {
|
if (option) {
|
||||||
this.handleSelection(e, option);
|
this.handleSelection(e, option);
|
||||||
}
|
}
|
||||||
this.setState({ isExpanded: true });
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({ isExpanded: false });
|
this.setState({ isExpanded: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChips() {
|
|
||||||
const { associatedItems } = this.props;
|
|
||||||
const items = associatedItems.map(item => ({
|
|
||||||
name: item.name,
|
|
||||||
id: item.id,
|
|
||||||
organization: item.organization,
|
|
||||||
}));
|
|
||||||
this.setState({
|
|
||||||
chipItems: items,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSelection(e, item) {
|
handleSelection(e, item) {
|
||||||
const { chipItems } = this.state;
|
const { chipItems } = this.state;
|
||||||
const { onAddNewItem } = this.props;
|
const { onAddNewItem } = this.props;
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
chipItems: chipItems.concat({ name: item.name, id: item.id }),
|
chipItems: chipItems.concat({ name: item.name, id: item.id }),
|
||||||
|
isExpanded: false,
|
||||||
});
|
});
|
||||||
onAddNewItem(item);
|
onAddNewItem(item);
|
||||||
e.preventDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddItem(event) {
|
handleAddItem(event) {
|
||||||
const { input, chipItems } = this.state;
|
const { input, chipItems } = this.state;
|
||||||
const { onAddNewItem } = this.props;
|
const { onAddNewItem } = this.props;
|
||||||
const newChip = { name: input, id: Math.random() };
|
const newChip = { name: input, id: Math.random() };
|
||||||
if (event.key === 'Tab') {
|
if (event.key !== 'Tab') {
|
||||||
this.setState({
|
return;
|
||||||
chipItems: chipItems.concat(newChip),
|
|
||||||
isExpanded: false,
|
|
||||||
input: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
onAddNewItem(input);
|
|
||||||
}
|
}
|
||||||
|
this.setState({
|
||||||
|
chipItems: chipItems.concat(newChip),
|
||||||
|
isExpanded: false,
|
||||||
|
input: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
onAddNewItem(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange(e) {
|
handleInputChange(e) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sleep } from '@testUtils/testUtils';
|
||||||
import MultiSelect, { _MultiSelect } from './MultiSelect';
|
import MultiSelect, { _MultiSelect } from './MultiSelect';
|
||||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
@@ -10,7 +11,10 @@ describe('<MultiSelect />', () => {
|
|||||||
const options = [{ name: 'Angry', id: 3 }, { name: 'Potato', id: 4 }];
|
const options = [{ name: 'Angry', id: 3 }, { name: 'Potato', id: 4 }];
|
||||||
|
|
||||||
test('Initially render successfully', () => {
|
test('Initially render successfully', () => {
|
||||||
const renderChips = jest.spyOn(_MultiSelect.prototype, 'renderChips');
|
const getInitialChipItems = jest.spyOn(
|
||||||
|
_MultiSelect.prototype,
|
||||||
|
'getInitialChipItems'
|
||||||
|
);
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
onAddNewItem={jest.fn()}
|
onAddNewItem={jest.fn()}
|
||||||
@@ -21,7 +25,7 @@ describe('<MultiSelect />', () => {
|
|||||||
);
|
);
|
||||||
const component = wrapper.find('MultiSelect');
|
const component = wrapper.find('MultiSelect');
|
||||||
|
|
||||||
expect(renderChips).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 () => {
|
||||||
@@ -33,9 +37,12 @@ describe('<MultiSelect />', () => {
|
|||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const event = { preventDefault: () => {} };
|
|
||||||
const component = wrapper.find('MultiSelect');
|
const component = wrapper.find('MultiSelect');
|
||||||
component.instance().handleSelection(event, { name: 'Apollo', id: 5 });
|
component
|
||||||
|
.find('input[aria-label="labels"]')
|
||||||
|
.simulate('keydown', { key: 'Tab' });
|
||||||
|
component.update();
|
||||||
|
await sleep(1);
|
||||||
expect(component.state().chipItems.length).toBe(3);
|
expect(component.state().chipItems.length).toBe(3);
|
||||||
});
|
});
|
||||||
test('handleAddItem adds a chip only when Tab is pressed', () => {
|
test('handleAddItem adds a chip only when Tab is pressed', () => {
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
export {
|
export { default } from './MultiSelect';
|
||||||
default
|
|
||||||
}
|
|
||||||
from './MultiSelect';
|
|
||||||
|
|||||||
@@ -21,28 +21,27 @@ class JobTemplateEdit extends Component {
|
|||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubmit(values, newLabels, removedLabels) {
|
async handleSubmit(values, newLabels = [], removedLabels = []) {
|
||||||
const {
|
const {
|
||||||
template: { id, type },
|
template: { id, type },
|
||||||
history,
|
history,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const disassociatedLabels = removedLabels
|
const disassociatedLabels = removedLabels.forEach(removedLabel =>
|
||||||
? removedLabels.forEach(removedLabel =>
|
JobTemplatesAPI.disassociateLabel(id, removedLabel)
|
||||||
JobTemplatesAPI.updateLabels(id, removedLabel)
|
);
|
||||||
)
|
|
||||||
: null;
|
|
||||||
const associatedLabels = newLabels
|
const associatedLabels = newLabels
|
||||||
? newLabels.forEach(newLabel =>
|
.filter(newLabel => !newLabel.organization)
|
||||||
JobTemplatesAPI.updateLabels(id, newLabel)
|
.forEach(newLabel => JobTemplatesAPI.associateLabel(id, newLabel));
|
||||||
)
|
const generatedLabels = newLabels
|
||||||
: null;
|
.filter(newLabel => newLabel.organization)
|
||||||
|
.forEach(newLabel => JobTemplatesAPI.generateLabel(id, newLabel));
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
JobTemplatesAPI.update(id, { ...values }),
|
JobTemplatesAPI.update(id, { ...values }),
|
||||||
disassociatedLabels,
|
disassociatedLabels,
|
||||||
associatedLabels,
|
associatedLabels,
|
||||||
|
generatedLabels,
|
||||||
]);
|
]);
|
||||||
history.push(`/templates/${type}/${id}/details`);
|
history.push(`/templates/${type}/${id}/details`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -36,9 +36,26 @@ describe('<JobTemplateEdit />', () => {
|
|||||||
description: 'new description',
|
description: 'new description',
|
||||||
job_type: 'check',
|
job_type: 'check',
|
||||||
};
|
};
|
||||||
|
const newLabels = [
|
||||||
|
{ associate: true, id: 3 },
|
||||||
|
{ associate: true, id: 3 },
|
||||||
|
{ name: 'Mapel', organization: 1 },
|
||||||
|
{ name: 'Tree', organization: 1 },
|
||||||
|
];
|
||||||
|
const removedLabels = [
|
||||||
|
{ disassociate: true, id: 1 },
|
||||||
|
{ disassociate: true, id: 2 },
|
||||||
|
];
|
||||||
|
|
||||||
wrapper.find('JobTemplateForm').prop('handleSubmit')(updatedTemplateData);
|
wrapper.find('JobTemplateForm').prop('handleSubmit')(
|
||||||
|
updatedTemplateData,
|
||||||
|
newLabels,
|
||||||
|
removedLabels
|
||||||
|
);
|
||||||
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, updatedTemplateData);
|
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, updatedTemplateData);
|
||||||
|
expect(JobTemplatesAPI.disassociateLabel).toHaveBeenCalledTimes(2);
|
||||||
|
expect(JobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(2);
|
||||||
|
expect(JobTemplatesAPI.generateLabel).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should navigate to job template detail when cancel is clicked', () => {
|
test('should navigate to job template detail when cancel is clicked', () => {
|
||||||
|
|||||||
@@ -67,13 +67,18 @@ class JobTemplateForm extends Component {
|
|||||||
};
|
};
|
||||||
this.handleNewLabel = this.handleNewLabel.bind(this);
|
this.handleNewLabel = this.handleNewLabel.bind(this);
|
||||||
this.loadLabels = this.loadLabels.bind(this);
|
this.loadLabels = this.loadLabels.bind(this);
|
||||||
this.disassociateLabel = this.disassociateLabel.bind(this);
|
this.removeLabel = this.removeLabel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadLabels(QSConfig);
|
this.loadLabels(QSConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The function below assumes that the user has no more than 400
|
||||||
|
// labels. For the vast majority of users this will be more thans
|
||||||
|
// enough.This can be updated to allow more than 400 labels if we
|
||||||
|
// decide it is necessary.
|
||||||
|
|
||||||
async loadLabels(QueryConfig) {
|
async loadLabels(QueryConfig) {
|
||||||
this.setState({ contentError: null, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
let loadedLabels;
|
let loadedLabels;
|
||||||
@@ -127,23 +132,26 @@ class JobTemplateForm extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disassociateLabel(label) {
|
removeLabel(label) {
|
||||||
const { removedLabels, loadedLabels, newLabels } = this.state;
|
const { removedLabels, newLabels } = this.state;
|
||||||
const isNewCreatedLabel = loadedLabels.some(
|
const { template } = this.props;
|
||||||
loadedLabel => loadedLabel.name !== label.name
|
|
||||||
|
const isAssociatedLabel = template.summary_fields.labels.results.some(
|
||||||
|
tempLabel => tempLabel.id === label.id
|
||||||
);
|
);
|
||||||
if (isNewCreatedLabel) {
|
|
||||||
const filteredLabels = newLabels.filter(
|
if (isAssociatedLabel) {
|
||||||
newLabel => newLabel.name !== label.name
|
|
||||||
);
|
|
||||||
this.setState({ newLabels: filteredLabels });
|
|
||||||
} else {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
removedLabels: removedLabels.concat({
|
removedLabels: removedLabels.concat({
|
||||||
disassociate: true,
|
disassociate: true,
|
||||||
id: label.id,
|
id: label.id,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
const filteredLabels = newLabels.filter(
|
||||||
|
newLabel => newLabel.name !== label.name
|
||||||
|
);
|
||||||
|
this.setState({ newLabels: filteredLabels });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +293,7 @@ class JobTemplateForm extends Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
onAddNewItem={this.handleNewLabel}
|
onAddNewItem={this.handleNewLabel}
|
||||||
onRemoveItem={this.disassociateLabel}
|
onRemoveItem={this.removeLabel}
|
||||||
associatedItems={template.summary_fields.labels.results}
|
associatedItems={template.summary_fields.labels.results}
|
||||||
options={loadedLabels}
|
options={loadedLabels}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -36,14 +36,16 @@ describe('<JobTemplateForm />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('initially renders successfully', () => {
|
test('initially renders successfully', () => {
|
||||||
mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<JobTemplateForm
|
<JobTemplateForm
|
||||||
template={mockData}
|
template={mockData}
|
||||||
handleSubmit={jest.fn()}
|
handleSubmit={jest.fn()}
|
||||||
handleCancel={jest.fn()}
|
handleCancel={jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const component = wrapper.find('ChipGroup');
|
||||||
expect(LabelsAPI.read).toHaveBeenCalled();
|
expect(LabelsAPI.read).toHaveBeenCalled();
|
||||||
|
expect(component.find('span#pf-random-id-1').text()).toEqual('Sushi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should update form values on input changes', async () => {
|
test('should update form values on input changes', async () => {
|
||||||
@@ -131,8 +133,7 @@ describe('<JobTemplateForm />', () => {
|
|||||||
|
|
||||||
wrapper.setState({ newLabels: [], loadedLabels: [], removedLabels: [] });
|
wrapper.setState({ newLabels: [], loadedLabels: [], removedLabels: [] });
|
||||||
multiSelect.setState({ input: 'Foo' });
|
multiSelect.setState({ input: 'Foo' });
|
||||||
|
component.find('input[aria-label="labels"]').prop('onKeyDown')(event);
|
||||||
wrapper.find('input[aria-label="labels"]').prop('onKeyDown')(event);
|
|
||||||
expect(handleNewLabel).toHaveBeenCalledWith('Foo');
|
expect(handleNewLabel).toHaveBeenCalledWith('Foo');
|
||||||
|
|
||||||
component.instance().handleNewLabel({ name: 'Bar', id: 2 });
|
component.instance().handleNewLabel({ name: 'Bar', id: 2 });
|
||||||
@@ -149,21 +150,16 @@ describe('<JobTemplateForm />', () => {
|
|||||||
handleCancel={jest.fn()}
|
handleCancel={jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const multiSelect = wrapper.find('MultiSelect');
|
|
||||||
const component = wrapper.find('JobTemplateForm');
|
const component = wrapper.find('JobTemplateForm');
|
||||||
|
// This asserts that the user generated a label or clicked
|
||||||
component.setState({
|
// on a label option, and then changed their mind and
|
||||||
newLabels: [{ name: 'Foo', id: 1 }],
|
// removed the label.
|
||||||
loadedLabels: [{ name: 'Bar', id: 3 }],
|
component.instance().removeLabel({ name: 'Alex', id: 17 });
|
||||||
removedLabels: [],
|
|
||||||
});
|
|
||||||
component.update();
|
|
||||||
multiSelect.setState({ input: 'Wowza' });
|
|
||||||
component.instance().disassociateLabel({ name: 'Foo', id: 1 });
|
|
||||||
expect(component.state().newLabels.length).toBe(0);
|
expect(component.state().newLabels.length).toBe(0);
|
||||||
expect(component.state().removedLabels.length).toBe(0);
|
expect(component.state().removedLabels.length).toBe(0);
|
||||||
|
// This asserts that the user removed a label that was associated
|
||||||
component.instance().disassociateLabel({ name: 'Bar', id: 3 });
|
// with the template when the template loaded.
|
||||||
|
component.instance().removeLabel({ name: 'Sushi', id: 1 });
|
||||||
expect(component.state().newLabels.length).toBe(0);
|
expect(component.state().newLabels.length).toBe(0);
|
||||||
expect(component.state().removedLabels.length).toBe(1);
|
expect(component.state().removedLabels.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user