diff --git a/awx/ui_next/src/api/index.js b/awx/ui_next/src/api/index.js index d295502309..58f628e75d 100644 --- a/awx/ui_next/src/api/index.js +++ b/awx/ui_next/src/api/index.js @@ -6,6 +6,7 @@ import Jobs from './models/Jobs'; import Labels from './models/Labels'; import Me from './models/Me'; import Organizations from './models/Organizations'; +import Projects from './models/Projects'; import Root from './models/Root'; import Teams from './models/Teams'; import UnifiedJobTemplates from './models/UnifiedJobTemplates'; @@ -21,6 +22,7 @@ const JobsAPI = new Jobs(); const LabelsAPI = new Labels(); const MeAPI = new Me(); const OrganizationsAPI = new Organizations(); +const ProjectsAPI = new Projects(); const RootAPI = new Root(); const TeamsAPI = new Teams(); const UnifiedJobTemplatesAPI = new UnifiedJobTemplates(); @@ -37,6 +39,7 @@ export { LabelsAPI, MeAPI, OrganizationsAPI, + ProjectsAPI, RootAPI, TeamsAPI, UnifiedJobTemplatesAPI, diff --git a/awx/ui_next/src/api/models/Projects.js b/awx/ui_next/src/api/models/Projects.js new file mode 100644 index 0000000000..a5a7d90042 --- /dev/null +++ b/awx/ui_next/src/api/models/Projects.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Projects extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/projects/'; + } +} + +export default Projects; diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx index 8869abd5f8..32910b10ef 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx @@ -57,9 +57,7 @@ describe('', () => { expect(wrapper.find('input#template-playbook').text()).toBe( defaultProps.playbook ); - expect(wrapper.find('input#template-project').text()).toBe( - defaultProps.project - ); + expect(wrapper.find('ProjectLookup').prop('value')).toBe(null); done(); }); diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index c12e50fd5a..bc5e593b19 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -17,6 +17,7 @@ import { required } from '@util/validators'; import styled from 'styled-components'; import { JobTemplate } from '@types'; import InventoriesLookup from './InventoriesLookup'; +import ProjectLookup from './ProjectLookup'; import { LabelsAPI } from '@api'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` @@ -56,6 +57,7 @@ class JobTemplateForm extends Component { loadedLabels: [], newLabels: [], removedLabels: [], + project: props.template.summary_fields.project, inventory: props.template.summary_fields.inventory, }; this.handleNewLabel = this.handleNewLabel.bind(this); @@ -153,6 +155,7 @@ class JobTemplateForm extends Component { contentError, hasContentLoading, inventory, + project, newLabels, removedLabels, } = this.state; @@ -258,15 +261,21 @@ class JobTemplateForm extends Component { /> )} /> - ( + { + form.setFieldValue('project', value.id); + this.setState({ project: value }); + }} + required + /> + )} /> ', () => { name: 'foo', organization_id: 1, }, + project: { + id: 3, + name: 'qux', + }, labels: { results: [{ name: 'Sushi', id: 1 }, { name: 'Major', id: 2 }] }, }, }; @@ -44,9 +48,13 @@ describe('', () => { /> ); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); - const component = wrapper.find('ChipGroup'); expect(LabelsAPI.read).toHaveBeenCalled(); - expect(component.find('span#pf-random-id-1').text()).toEqual('Sushi'); + expect( + wrapper + .find('FormGroup[fieldId="template-labels"] MultiSelect Chip') + .first() + .text() + ).toEqual('Sushi'); done(); }); @@ -77,8 +85,9 @@ describe('', () => { name: 'inventory', }); expect(form.state('values').inventory).toEqual(3); - wrapper.find('input#template-project').simulate('change', { - target: { value: 4, name: 'project' }, + wrapper.find('ProjectLookup').prop('onChange')({ + id: 4, + name: 'project', }); expect(form.state('values').project).toEqual(4); wrapper.find('input#template-playbook').simulate('change', { diff --git a/awx/ui_next/src/screens/Template/shared/ProjectLookup.jsx b/awx/ui_next/src/screens/Template/shared/ProjectLookup.jsx new file mode 100644 index 0000000000..edded07d57 --- /dev/null +++ b/awx/ui_next/src/screens/Template/shared/ProjectLookup.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { string, func, bool } from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { FormGroup, Tooltip } from '@patternfly/react-core'; +import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; +import { ProjectsAPI } from '@api'; +import { Project } from '@types'; +import Lookup from '@components/Lookup'; +import styled from 'styled-components'; + +const QuestionCircleIcon = styled(PFQuestionCircleIcon)` + margin-left: 10px; +`; + +const loadProjects = async params => ProjectsAPI.read(params); + +class ProjectLookup extends React.Component { + render() { + const { value, tooltip, onChange, required, i18n } = this.props; + + return ( + + {tooltip && ( + + + + )} + + + ); + } +} + +ProjectLookup.propTypes = { + value: Project, + tooltip: string, + onChange: func.isRequired, + required: bool, +}; + +ProjectLookup.defaultProps = { + value: null, + tooltip: '', + required: false, +}; + +export default withI18n()(ProjectLookup);