Merge remote-tracking branch 'origin/master' into add-select-default-option

This commit is contained in:
kialam
2019-02-14 10:38:28 -05:00
14 changed files with 391 additions and 285 deletions

View File

@@ -106,8 +106,8 @@ class APIClient {
return this.http.post(endpoint, data);
}
getInstanceGroups () {
return this.http.get(API_INSTANCE_GROUPS);
getInstanceGroups (params) {
return this.http.get(API_INSTANCE_GROUPS, { params });
}
createInstanceGroups (url, id) {

View File

@@ -157,8 +157,11 @@
//
.pf-c-modal-box__footer {
--pf-c-modal-box__footer--PaddingTop: 0;
--pf-c-modal-box__footer--PaddingBottom: 0;
--pf-c-modal-box__footer--PaddingTop: 20px;
--pf-c-modal-box__footer--PaddingRight: 20px;
--pf-c-modal-box__footer--PaddingBottom: 20px;
--pf-c-modal-box__footer--PaddingLeft: 20px;
justify-content: flex-end;
}
.pf-c-modal-box__header {
@@ -171,6 +174,7 @@
.pf-c-modal-box__body {
--pf-c-modal-box__body--PaddingLeft: 20px;
--pf-c-modal-box__body--PaddingRight: 20px;
--pf-c-modal-box__body--PaddingBottom: 5px;
}
//
@@ -215,12 +219,6 @@
}
}
.at-align-right {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.awx-c-list {
border-top: 1px solid #d7d7d7;
border-bottom: 1px solid #d7d7d7;
@@ -240,4 +238,4 @@
--pf-c-card__footer--PaddingY: 0;
--pf-c-card__body--PaddingX: 0;
--pf-c-card__body--PaddingY: 0;
}
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import {
FormGroup,
Select,
SelectOption,
} from '@patternfly/react-core';
@@ -25,32 +24,28 @@ class AnsibleSelect extends React.Component {
return null;
}
onSelectChange (val) {
const { selectChange } = this.props;
selectChange(val);
onSelectChange (val, event) {
const { onChange, name } = this.props;
event.target.name = name;
onChange(val, event);
}
render () {
const { count } = this.state;
const { labelName, selected, data, defaultSelected } = this.props;
const { label = '', value, data, defaultSelected } = this.props;
let elem;
if (count > 1) {
elem = (
<FormGroup label={labelName} fieldId="ansible-select">
<Select value={selected} onChange={this.onSelectChange} aria-label="Select Input">
<SelectOption key="" value="" label={`Use Default ${labelName}`} />
{data.map((datum) => (datum !== defaultSelected
? (<SelectOption key={datum} value={datum} label={datum} />) : null))
}
</Select>
</FormGroup>
<Select value={value} onChange={this.onSelectChange} aria-label="Select Input">
{data.map((datum) => (datum === defaultSelected
? (<SelectOption key="" value="" label={`Use Default ${label}`} />) : (<SelectOption key={datum} value={datum} label={datum} />)))
}
</Select>
);
} else {
elem = null;
}
return elem;
}
}
export default AnsibleSelect;

View File

@@ -1,6 +1,6 @@
.awx-toolbar {
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
--awx-toolbar--BorderColor: var(--pf-global--Color--light-200);
--awx-toolbar--BorderColor: #ebebeb;
--awx-toolbar--BorderWidth: var(--pf-global--BorderWidth--sm);
border-bottom: var(--awx-toolbar--BorderWidth) solid var(--awx-toolbar--BorderColor);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
ActionGroup,
Toolbar,
ToolbarGroup,
Button
} from '@patternfly/react-core';
const formActionGroupStyle = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: '10px'
};
const buttonGroupStyle = {
marginRight: '20px'
};
export default ({ onSubmit, submitDisabled, onCancel }) => (
<I18n>
{({ i18n }) => (
<ActionGroup style={formActionGroupStyle}>
<Toolbar>
<ToolbarGroup style={buttonGroupStyle}>
<Button aria-label={i18n._(t`Save`)} variant="primary" onClick={onSubmit} isDisabled={submitDisabled}>{i18n._(t`Save`)}</Button>
</ToolbarGroup>
<ToolbarGroup>
<Button aria-label={i18n._(t`Cancel`)} variant="secondary" onClick={onCancel}>{i18n._(t`Cancel`)}</Button>
</ToolbarGroup>
</Toolbar>
</ActionGroup>
)}
</I18n>
);

View File

@@ -1,33 +1,82 @@
import React from 'react';
import React, { Fragment } from 'react';
import { SearchIcon } from '@patternfly/react-icons';
import { SearchIcon, CubesIcon } from '@patternfly/react-icons';
import {
Modal,
Button,
ActionGroup,
Toolbar,
ToolbarGroup,
EmptyState,
EmptyStateIcon,
EmptyStateBody,
Title
} from '@patternfly/react-core';
import { I18n } from '@lingui/react';
import { t, Trans } from '@lingui/macro';
import { Trans, t } from '@lingui/macro';
import CheckboxListItem from '../ListItem';
import SelectedList from '../SelectedList';
import Pagination from '../Pagination';
const paginationStyling = {
paddingLeft: '0',
justifyContent: 'flex-end',
borderRight: '1px solid #ebebeb',
borderBottom: '1px solid #ebebeb',
borderTop: '0'
};
class Lookup extends React.Component {
constructor (props) {
super(props);
this.state = {
isModalOpen: false,
lookupSelectedItems: []
lookupSelectedItems: [],
results: [],
count: 0,
page: 1,
page_size: 5,
error: null
};
this.onSetPage = this.onSetPage.bind(this);
this.handleModalToggle = this.handleModalToggle.bind(this);
this.wrapTags = this.wrapTags.bind(this);
this.toggleSelected = this.toggleSelected.bind(this);
this.saveModal = this.saveModal.bind(this);
this.getData = this.getData.bind(this);
}
componentDidMount () {
const { page_size, page } = this.state;
this.getData({ page_size, page });
}
async getData (queryParams) {
const { getItems } = this.props;
const { page } = queryParams;
this.setState({ error: false });
try {
const { data } = await getItems(queryParams);
const { results, count } = data;
const stateToUpdate = {
page,
results,
count
};
this.setState(stateToUpdate);
} catch (err) {
this.setState({ error: true });
}
}
onSetPage = async (pageNumber, pageSize) => {
const page = parseInt(pageNumber, 10);
const page_size = parseInt(pageSize, 10);
this.getData({ page_size, page });
};
toggleSelected (row) {
const { lookupSelectedItems } = this.state;
const selectedIndex = lookupSelectedItems
@@ -44,12 +93,12 @@ class Lookup extends React.Component {
handleModalToggle () {
const { isModalOpen } = this.state;
const { selected } = this.props;
const { value } = this.props;
// Resets the selected items from parent state whenever modal is opened
// This handles the case where the user closes/cancels the modal and
// opens it again
if (!isModalOpen) {
this.setState({ lookupSelectedItems: [...selected] });
this.setState({ lookupSelectedItems: [...value] });
}
this.setState((prevState) => ({
isModalOpen: !prevState.isModalOpen,
@@ -57,13 +106,13 @@ class Lookup extends React.Component {
}
saveModal () {
const { onLookupSave } = this.props;
const { onLookupSave, name } = this.props;
const { lookupSelectedItems } = this.state;
onLookupSave(lookupSelectedItems);
onLookupSave(lookupSelectedItems, name);
this.handleModalToggle();
}
wrapTags (tags) {
wrapTags (tags = []) {
return tags.map(tag => (
<span className="awx-c-tag--pill" key={tag.id}>
{tag.name}
@@ -75,34 +124,61 @@ class Lookup extends React.Component {
}
render () {
const { isModalOpen, lookupSelectedItems } = this.state;
const { data, lookupHeader, selected } = this.props;
const { isModalOpen, lookupSelectedItems, error, results, count, page, page_size } = this.state;
const { lookupHeader = 'items', value } = this.props;
return (
<div className="pf-c-input-group awx-lookup">
<Button className="pf-c-input-group__text" aria-label="search" id="search" onClick={this.handleModalToggle}>
<SearchIcon />
</Button>
<div className="pf-c-form-control">{this.wrapTags(selected)}</div>
<Modal
className="awx-c-modal"
title={`Select ${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={lookupSelectedItems.some(item => item.id === i.id)}
onSelect={() => this.toggleSelected(i)}
/>
))}
</ul>
{lookupSelectedItems.length > 0 && (
<I18n>
{({ i18n }) => (
<I18n>
{({ i18n }) => (
<div className="pf-c-input-group awx-lookup">
<Button className="pf-c-input-group__text" aria-label="search" id="search" onClick={this.handleModalToggle}>
<SearchIcon />
</Button>
<div className="pf-c-form-control">{this.wrapTags(value)}</div>
<Modal
className="awx-c-modal"
title={`Select ${lookupHeader}`}
isOpen={isModalOpen}
onClose={this.handleModalToggle}
actions={[
<Button key="save" variant="primary" onClick={this.saveModal} style={(results.length === 0) ? { display: 'none' } : {}}>{i18n._(t`Save`)}</Button>,
<Button key="cancel" variant="secondary" onClick={this.handleModalToggle}>{(results.length === 0) ? i18n._(t`Close`) : i18n._(t`Cancel`)}</Button>
]}
>
{(results.length === 0) ? (
<EmptyState>
<EmptyStateIcon icon={CubesIcon} />
<Title size="lg">
<Trans>{`No ${lookupHeader} Found`}</Trans>
</Title>
<EmptyStateBody>
<Trans>{`Please add ${lookupHeader.toLowerCase()} to populate this list`}</Trans>
</EmptyStateBody>
</EmptyState>
) : (
<Fragment>
<ul className="pf-c-data-list awx-c-list">
{results.map(i => (
<CheckboxListItem
key={i.id}
itemId={i.id}
name={i.name}
isSelected={lookupSelectedItems.some(item => item.id === i.id)}
onSelect={() => this.toggleSelected(i)}
/>
))}
</ul>
<Pagination
count={count}
page={page}
pageCount={Math.ceil(count / page_size)}
page_size={page_size}
onSetPage={this.onSetPage}
style={paginationStyling}
/>
</Fragment>
)}
{lookupSelectedItems.length > 0 && (
<SelectedList
label={i18n._(t`Selected`)}
selected={lookupSelectedItems}
@@ -110,24 +186,11 @@ class Lookup extends React.Component {
onRemove={this.toggleSelected}
/>
)}
</I18n>
)}
<ActionGroup className="at-align-right">
<Toolbar>
<ToolbarGroup>
<Button className="at-C-SubmitButton" variant="primary" onClick={this.saveModal}>
<Trans>Select</Trans>
</Button>
</ToolbarGroup>
<ToolbarGroup>
<Button className="at-C-CancelButton" variant="secondary" onClick={this.handleModalToggle}>
<Trans>Cancel</Trans>
</Button>
</ToolbarGroup>
</Toolbar>
</ActionGroup>
</Modal>
</div>
{ error ? <div>error</div> : '' }
</Modal>
</div>
)}
</I18n>
);
}
}

View File

@@ -105,10 +105,13 @@ class Pagination extends Component {
pageCount,
page_size,
pageSizeOptions,
style
} = this.props;
const { value, isOpen } = this.state;
const opts = pageSizeOptions.slice().reverse().filter(o => o !== page_size);
let opts;
if (pageSizeOptions) {
opts = pageSizeOptions.slice().reverse().filter(o => o !== page_size);
}
const isOnFirst = page === 1;
const isOnLast = page === pageCount;
@@ -119,33 +122,35 @@ class Pagination extends Component {
return (
<I18n>
{({ i18n }) => (
<div className="awx-pagination">
<div className="awx-pagination__page-size-selection">
<Trans>Items Per Page</Trans>
<Dropdown
onToggle={this.onTogglePageSize}
onSelect={this.onSelectPageSize}
direction={up}
isOpen={isOpen}
toggle={(
<DropdownToggle
className="togglePageSize"
onToggle={this.onTogglePageSize}
>
{page_size}
</DropdownToggle>
)}
>
{opts.map(option => (
<DropdownItem
key={option}
component="button"
>
{option}
</DropdownItem>
))}
</Dropdown>
</div>
<div className="awx-pagination" style={style}>
{opts && (
<div className="awx-pagination__page-size-selection">
<Trans>Items Per Page</Trans>
<Dropdown
onToggle={this.onTogglePageSize}
onSelect={this.onSelectPageSize}
direction={up}
isOpen={isOpen}
toggle={(
<DropdownToggle
className="togglePageSize"
onToggle={this.onTogglePageSize}
>
{page_size}
</DropdownToggle>
)}
>
{opts.map(option => (
<DropdownItem
key={option}
component="button"
>
{option}
</DropdownItem>
))}
</Dropdown>
</div>
)}
<div className="awx-pagination__counts">
<div className="awx-pagination__item-count">
<Trans>{`Items ${itemMin} - ${itemMax} of ${count}`}</Trans>

View File

@@ -1,6 +1,6 @@
.awx-pagination {
--awx-pagination--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
--awx-pagination--BorderColor: var(--pf-global--BackgroundColor--light-300);
--awx-pagination--BorderColor: #dbdbdb;
--awx-pagination--disabled-BackgroundColor: #f2f2f2;
--awx-pagination--disabled-Color: #C2C2CA;

View File

@@ -3,6 +3,18 @@ import {
Chip
} from '@patternfly/react-core';
const selectedRowStyling = {
paddingTop: '15px',
paddingBottom: '5px',
borderLeft: '0',
borderRight: '0'
};
const selectedLabelStyling = {
fontSize: '14px',
fontWeight: 'bold'
};
class SelectedList extends Component {
constructor (props) {
super(props);
@@ -23,8 +35,8 @@ class SelectedList extends Component {
const { showOverflow } = this.state;
return (
<div className="awx-selectedList">
<div className="pf-l-split">
<div className="pf-l-split__item pf-u-align-items-center">
<div className="pf-l-split" style={selectedRowStyling}>
<div className="pf-l-split__item pf-u-align-items-center" style={selectedLabelStyling}>
{label}
</div>
<div className="pf-l-split__item">

View File

@@ -1,6 +1,6 @@
.awx-selectedList {
--awx-selectedList--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
--awx-selectedList--BorderColor: #d7d7d7;
--awx-selectedList--BorderColor: #ebebeb;
--awx-selectedList--BorderWidth: var(--pf-global--BorderWidth--sm);
--awx-selectedList--FontSize: var(--pf-c-chip__text--FontSize);

View File

@@ -6,10 +6,6 @@ import {
Form,
FormGroup,
TextInput,
ActionGroup,
Toolbar,
ToolbarGroup,
Button,
Gallery,
Card,
CardBody,
@@ -18,76 +14,57 @@ import {
import { ConfigContext } from '../../../context';
import Lookup from '../../../components/Lookup';
import AnsibleSelect from '../../../components/AnsibleSelect';
const format = (data) => {
const results = data.results.map((result) => ({
id: result.id,
name: result.name,
isChecked: false
}));
return results;
};
import FormActionGroup from '../../../components/FormActionGroup';
class OrganizationAdd extends React.Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.onSelectChange = this.onSelectChange.bind(this);
this.getInstanceGroups = this.getInstanceGroups.bind(this);
this.onFieldChange = this.onFieldChange.bind(this);
this.onLookupSave = this.onLookupSave.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.updateSelectedInstanceGroups = this.updateSelectedInstanceGroups.bind(this);
this.onSuccess = this.onSuccess.bind(this);
this.state = {
name: '',
description: '',
custom_virtualenv: '',
instanceGroups: [],
error: '',
defaultEnv: '/venv/ansible/',
};
}
state = {
name: '',
description: '',
results: [],
custom_virtualenv: '',
error: '',
selectedInstanceGroups: [],
defaultEnv: '/venv/ansible/'
};
async componentDidMount () {
const { api } = this.props;
try {
const { data } = await api.getInstanceGroups();
const results = format(data);
this.setState({ results });
} catch (error) {
this.setState({ error });
}
onFieldChange (val, evt) {
this.setState({ [evt.target.name]: val || evt.target.value });
}
onSelectChange (value) {
this.setState({ custom_virtualenv: value });
onLookupSave (val, targetName) {
this.setState({ [targetName]: val });
}
async onSubmit () {
const { api } = this.props;
const { name, description, custom_virtualenv } = this.state;
const { name, description, custom_virtualenv, instanceGroups } = this.state;
const data = {
name,
description,
custom_virtualenv
};
const { selectedInstanceGroups } = this.state;
try {
const { data: response } = await api.createOrganization(data);
const url = response.related.instance_groups;
const instanceGroupsUrl = response.related.instance_groups;
try {
if (selectedInstanceGroups.length > 0) {
selectedInstanceGroups.forEach(async (select) => {
await api.createInstanceGroups(url, select.id);
if (instanceGroups.length > 0) {
instanceGroups.forEach(async (select) => {
await api.createInstanceGroups(instanceGroupsUrl, select.id);
});
}
} catch (err) {
this.setState({ error: err });
} finally {
this.resetForm();
this.onSuccess(response.id);
}
} catch (err) {
@@ -105,32 +82,19 @@ class OrganizationAdd extends React.Component {
history.push(`/organizations/${id}`);
}
updateSelectedInstanceGroups (selectedInstanceGroups) {
this.setState({ selectedInstanceGroups });
}
handleChange (_, evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
resetForm () {
this.setState({
name: '',
description: '',
});
const { results } = this.state;
const reset = results.map((result) => ({ id: result.id, name: result.name, isChecked: false }));
this.setState({ results: reset });
async getInstanceGroups (params) {
const { api } = this.props;
const data = await api.getInstanceGroups(params);
return data;
}
render () {
const {
name,
results,
description,
custom_virtualenv,
selectedInstanceGroups,
defaultEnv,
instanceGroups,
error
} = this.state;
const enabled = name.length > 0; // TODO: add better form validation
@@ -148,11 +112,10 @@ class OrganizationAdd extends React.Component {
>
<TextInput
isRequired
type="text"
id="add-org-form-name"
name="name"
value={name}
onChange={this.handleChange}
onChange={this.onFieldChange}
/>
</FormGroup>
<FormGroup label="Description" fieldId="add-org-form-description">
@@ -160,40 +123,39 @@ class OrganizationAdd extends React.Component {
id="add-org-form-description"
name="description"
value={description}
onChange={this.handleChange}
onChange={this.onFieldChange}
/>
</FormGroup>
<FormGroup label="Instance Groups" fieldId="simple-form-instance-groups">
<FormGroup label="Instance Groups" fieldId="add-org-form-instance-groups">
<Lookup
lookupHeader="Instance Groups"
onLookupSave={this.updateSelectedInstanceGroups}
data={results}
selected={selectedInstanceGroups}
name="instanceGroups"
value={instanceGroups}
onLookupSave={this.onLookupSave}
getItems={this.getInstanceGroups}
/>
</FormGroup>
<ConfigContext.Consumer>
{({ custom_virtualenvs }) => (
<AnsibleSelect
labelName="Ansible Environment"
selected={custom_virtualenv}
selectChange={this.onSelectChange}
data={custom_virtualenvs}
defaultSelected={defaultEnv}
/>
<FormGroup label="Ansible Environment" fieldId="add-org-form-custom-virtualenv">
<AnsibleSelect
label="Ansible Environment"
name="custom_virtualenv"
value={custom_virtualenv}
onChange={this.onFieldChange}
data={custom_virtualenvs}
defaultSelected={defaultEnv}
/>
</FormGroup>
)}
</ConfigContext.Consumer>
</Gallery>
<ActionGroup className="at-align-right">
<Toolbar>
<ToolbarGroup>
<Button className="at-C-SubmitButton" variant="primary" onClick={this.onSubmit} isDisabled={!enabled}>Save</Button>
</ToolbarGroup>
<ToolbarGroup>
<Button className="at-C-CancelButton" variant="secondary" onClick={this.onCancel}>Cancel</Button>
</ToolbarGroup>
</Toolbar>
</ActionGroup>
{ error ? <div>error</div> : '' }
<FormActionGroup
onSubmit={this.onSubmit}
submitDisabled={!enabled}
onCancel={this.onCancel}
/>
{error ? <div>error</div> : ''}
</Form>
</CardBody>
</Card>