Merge pull request #87 from ansible/lookup-form-component

Lookup form component
This commit is contained in:
kialam
2019-01-16 13:32:28 -05:00
committed by GitHub
11 changed files with 406 additions and 39 deletions

View File

@@ -4,6 +4,7 @@ const API_LOGOUT = `${API_ROOT}logout/`;
const API_V2 = `${API_ROOT}v2/`;
const API_CONFIG = `${API_V2}config/`;
const API_ORGANIZATIONS = `${API_V2}organizations/`;
const API_INSTANCE_GROUPS = `${API_V2}instance_groups/`;
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
@@ -68,6 +69,14 @@ class APIClient {
return this.http.get(endpoint);
}
getInstanceGroups () {
return this.http.get(API_INSTANCE_GROUPS);
}
createInstanceGroups (url, id) {
return this.http.post(url, { id });
}
}
export default APIClient;

View File

@@ -119,7 +119,30 @@
--pf-c-about-modal-box--MaxWidth: 63rem;
}
.pf-c-list {
li {
list-style-type: none;
margin: 0;
padding: 0;
}
}
.awx-lookup {
min-height: 36px;
}
.pf-c-input-group__text {
&:hover {
cursor: pointer;
}
}
.awx-c-tag--pill {
color: white;
background-color: rgb(0, 123, 186);
border-radius: 3px;
margin: 1px 2px;
padding: 0 10px;
display: inline-block;
}
//
// layout styles
//
@@ -127,4 +150,41 @@
display: flex;
flex-direction: row;
justify-content: flex-end;
}
}
//
// list styles
//
.awx-c-list {
border-top: 1px solid #d7d7d7;
border-bottom: 1px solid #d7d7d7;
}
//
// pf modal overrides
//
.awx-c-modal {
width: 550px;
margin: 0;
}
.awx-c-icon--remove {
padding-left: 10px;
&:hover {
cursor: pointer;
}
}
.pf-c-modal-box__footer {
--pf-c-modal-box__footer--PaddingTop: 0;
--pf-c-modal-box__footer--PaddingBottom: 0;
}
.pf-c-modal-box__header {
--pf-c-modal-box__header--PaddingTop: 10px;
--pf-c-modal-box__header--PaddingRight: 0;
--pf-c-modal-box__header--PaddingBottom: 0;
--pf-c-modal-box__header--PaddingLeft: 20px;
}
.pf-c-modal-box__body {
--pf-c-modal-box__body--PaddingLeft: 20px;
--pf-c-modal-box__body--PaddingRight: 20px;
}

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
@@ -12,7 +12,7 @@ import heroImg from '@patternfly/patternfly-next/assets/images/pfbg_992.jpg';
import brandImg from '../../images/tower-logo-white.svg';
import logoImg from '../../images/tower-logo-login.svg';
class About extends Component {
class About extends React.Component {
static createSpeechBubble (version) {
let text = `Tower ${version}`;
let top = '';

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Checkbox,
} from '@patternfly/react-core';
export default ({
itemId,
name,
isSelected,
onSelect,
}) => (
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
<div className="pf-c-data-list__check">
<I18n>
{({ i18n }) => (
<Checkbox
checked={isSelected}
onChange={onSelect}
aria-label={i18n._(t`selected ${itemId}`)}
id={`selectd-${itemId}`}
value={itemId}
/>
)}
</I18n>
</div>
<div className="pf-c-data-list__cell">
<label htmlFor={`selectd-${itemId}`} className="check-action-item">
<b>{name}</b>
</label>
</div>
</li>
);

View File

@@ -0,0 +1,3 @@
import CheckboxListItem from './CheckboxListItem';
export default CheckboxListItem;

View File

@@ -0,0 +1,91 @@
import React from 'react';
import { SearchIcon } from '@patternfly/react-icons';
import {
Modal,
Button,
ActionGroup,
Toolbar,
ToolbarGroup,
} from '@patternfly/react-core';
import CheckboxListItem from '../ListItem'
class Lookup extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
}
this.handleModalToggle = this.handleModalToggle.bind(this);
this.onLookup = this.onLookup.bind(this);
this.onChecked = this.onChecked.bind(this);
this.wrapTags = this.wrapTags.bind(this);
this.onRemove = this.onRemove.bind(this);
}
handleModalToggle() {
this.setState((prevState, _) => ({
isModalOpen: !prevState.isModalOpen,
}));
};
onLookup() {
this.handleModalToggle();
}
onChecked(_, evt) {
this.props.lookupChange(evt.target.value);
};
onRemove(evt) {
this.props.lookupChange(evt.target.id);
}
wrapTags(tags) {
return tags.filter(tag => tag.isChecked).map((tag, index) => {
return (
<span className="awx-c-tag--pill" key={index}>{tag.name}<span className="awx-c-icon--remove" id={tag.id} onClick={this.onRemove}>x</span></span>
)
})
}
render() {
const { isModalOpen } = this.state;
const { data } = this.props;
return (
<div className="pf-c-input-group awx-lookup">
<span className="pf-c-input-group__text" aria-label="search" id="search" onClick={this.onLookup}><SearchIcon /></span>
<div className="pf-c-form-control">{this.wrapTags(this.props.data)}</div>
<Modal
className="awx-c-modal"
title={`Select ${this.props.lookupHeader}`}
isOpen={isModalOpen}
onClose={this.handleModalToggle}
>
<ul className="pf-c-data-list awx-c-list">
{data.map(i =>
<CheckboxListItem
key={i.id}
itemId={i.id}
name={i.name}
isSelected={i.isChecked}
onSelect={this.onChecked}
/>
)}
</ul>
<ActionGroup className="at-align-right">
<Toolbar>
<ToolbarGroup>
<Button className="at-C-SubmitButton" variant="primary" onClick={this.handleModalToggle} >Select</Button>
</ToolbarGroup>
<ToolbarGroup>
<Button className="at-C-CancelButton" variant="secondary" onClick={this.handleModalToggle}>Cancel</Button>
</ToolbarGroup>
</Toolbar>
</ActionGroup>
</Modal>
</div>
)
}
}
export default Lookup;

View File

@@ -0,0 +1,3 @@
import Lookup from './Lookup';
export default Lookup;

View File

@@ -19,38 +19,53 @@ import {
} from '@patternfly/react-core';
import { ConfigContext } from '../../../context';
import Lookup from '../../../components/Lookup';
import AnsibleSelect from '../../../components/AnsibleSelect'
const { light } = PageSectionVariants;
class OrganizationAdd extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.onSelectChange = this.onSelectChange.bind(this);
this.onLookupChange = this.onLookupChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.resetForm = this.resetForm.bind(this);
this.onSuccess = this.onSuccess.bind(this);
this.onCancel = this.onCancel.bind(this);
this.format = this.format.bind(this);
}
state = {
name: '',
description: '',
instanceGroups: '',
results: [],
instance_groups: [],
custom_virtualenv: '',
error:'',
error: '',
};
onSelectChange(value, _) {
this.setState({ custom_virtualenv: value });
};
onLookupChange(id, _) {
let selected = { ...this.state.results }
const index = id - 1;
selected[index].isChecked = !selected[index].isChecked;
this.setState({ selected })
}
resetForm() {
this.setState({
...this.state,
name: '',
description: ''
description: '',
});
let reset = [];
this.state.results.map((result) => {
reset.push({ id: result.id, name: result.name, isChecked: false });
})
this.setState({ results: reset });
}
handleChange(_, evt) {
@@ -60,16 +75,57 @@ class OrganizationAdd extends React.Component {
async onSubmit() {
const { api } = this.props;
const data = Object.assign({}, { ...this.state });
await api.createOrganization(data);
this.resetForm();
try {
const { data: response } = await api.createOrganization(data);
const url = response.related.instance_groups;
const selected = this.state.results.filter(group => group.isChecked);
try {
if (selected.length > 0) {
selected.forEach( async (select) => {
await api.createInstanceGroups(url, select.id);
});
}
} catch (err) {
this.setState({ createInstanceGroupsError: err })
} finally {
this.resetForm();
this.onSuccess(response.id);
}
}
catch (err) {
this.setState({ onSubmitError: err })
}
}
onCancel() {
this.props.history.push('/organizations');
}
onSuccess(id) {
this.props.history.push(`/organizations/${id}`);
}
format(data) {
let results = [];
data.results.map((result) => {
results.push({ id: result.id, name: result.name, isChecked: false });
});
return results;
};
async componentDidMount() {
const { api } = this.props;
try {
const { data } = await api.getInstanceGroups();
const results = this.format(data);
this.setState({ results });
} catch (error) {
this.setState({ getInstanceGroupsError: error })
}
}
render() {
const { name } = this.state;
const { name, results } = this.state;
const enabled = name.length > 0; // TODO: add better form validation
return (
@@ -106,13 +162,11 @@ class OrganizationAdd extends React.Component {
onChange={this.handleChange}
/>
</FormGroup>
{/* LOOKUP MODAL PLACEHOLDER */}
<FormGroup label="Instance Groups" fieldId="simple-form-instance-groups">
<TextInput
id="add-org-form-instance-groups"
name="instance-groups"
value={this.state.instanceGroups}
onChange={this.handleChange}
<Lookup
lookupHeader="Instance Groups"
lookupChange={this.onLookupChange}
data={results}
/>
</FormGroup>
<ConfigContext.Consumer>