mirror of
https://github.com/ansible/awx.git
synced 2026-05-15 05:17:36 -02:30
WIP split Lookup into Lookup & CategoryLookup
This commit is contained in:
348
awx/ui_next/src/components/Lookup/CategoryLookup.jsx
Normal file
348
awx/ui_next/src/components/Lookup/CategoryLookup.jsx
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
string,
|
||||||
|
bool,
|
||||||
|
arrayOf,
|
||||||
|
func,
|
||||||
|
number,
|
||||||
|
oneOfType,
|
||||||
|
shape,
|
||||||
|
} from 'prop-types';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import { SearchIcon } from '@patternfly/react-icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
InputGroup as PFInputGroup,
|
||||||
|
Modal,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import AnsibleSelect from '../AnsibleSelect';
|
||||||
|
import PaginatedDataList from '../PaginatedDataList';
|
||||||
|
import VerticalSeperator from '../VerticalSeparator';
|
||||||
|
import DataListToolbar from '../DataListToolbar';
|
||||||
|
import CheckboxListItem from '../CheckboxListItem';
|
||||||
|
import SelectedList from '../SelectedList';
|
||||||
|
import { ChipGroup, Chip, CredentialChip } from '../Chip';
|
||||||
|
import { QSConfig } from '@types';
|
||||||
|
|
||||||
|
const SearchButton = styled(Button)`
|
||||||
|
::after {
|
||||||
|
border: var(--pf-c-button--BorderWidth) solid
|
||||||
|
var(--pf-global--BorderColor--200);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InputGroup = styled(PFInputGroup)`
|
||||||
|
${props =>
|
||||||
|
props.multiple &&
|
||||||
|
`
|
||||||
|
--pf-c-form-control--Height: 90px;
|
||||||
|
overflow-y: auto;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ChipHolder = styled.div`
|
||||||
|
--pf-c-form-control--BorderTopColor: var(--pf-global--BorderColor--200);
|
||||||
|
--pf-c-form-control--BorderRightColor: var(--pf-global--BorderColor--200);
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class CategoryLookup extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.assertCorrectValueType();
|
||||||
|
let selectedItems = [];
|
||||||
|
if (props.value) {
|
||||||
|
selectedItems = props.multiple ? [...props.value] : [props.value];
|
||||||
|
}
|
||||||
|
this.state = {
|
||||||
|
isModalOpen: false,
|
||||||
|
selectedItems,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||||
|
this.addItem = this.addItem.bind(this);
|
||||||
|
this.removeItem = this.removeItem.bind(this);
|
||||||
|
this.saveModal = this.saveModal.bind(this);
|
||||||
|
this.clearQSParams = this.clearQSParams.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCorrectValueType() {
|
||||||
|
const { multiple, value, selectCategoryOptions } = this.props;
|
||||||
|
if (selectCategoryOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!multiple && Array.isArray(value)) {
|
||||||
|
throw new Error(
|
||||||
|
'CategoryLookup value must not be an array unless `multiple` is set'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (multiple && !Array.isArray(value)) {
|
||||||
|
throw new Error(
|
||||||
|
'CategoryLookup value must be an array if `multiple` is set'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(row) {
|
||||||
|
const { selectedItems } = this.state;
|
||||||
|
const { onToggleItem } = this.props;
|
||||||
|
if (onToggleItem) {
|
||||||
|
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
selectedItems: selectedItems.filter(item => item.id !== row.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(row) {
|
||||||
|
const { selectedItems } = this.state;
|
||||||
|
const { multiple, onToggleItem } = this.props;
|
||||||
|
if (onToggleItem) {
|
||||||
|
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const index = selectedItems.findIndex(item => item.id === row.id);
|
||||||
|
|
||||||
|
if (!multiple) {
|
||||||
|
this.setState({ selectedItems: [row] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (index > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ selectedItems: [...selectedItems, row] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: clean up
|
||||||
|
handleModalToggle() {
|
||||||
|
const { isModalOpen } = this.state;
|
||||||
|
const { value, multiple, selectCategory } = 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) {
|
||||||
|
let selectedItems = [];
|
||||||
|
if (value) {
|
||||||
|
selectedItems = multiple ? [...value] : [value];
|
||||||
|
}
|
||||||
|
this.setState({ selectedItems });
|
||||||
|
} else {
|
||||||
|
this.clearQSParams();
|
||||||
|
if (selectCategory) {
|
||||||
|
selectCategory(null, 'Machine');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState(prevState => ({
|
||||||
|
isModalOpen: !prevState.isModalOpen,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemAndSave(row) {
|
||||||
|
const { value, onChange, multiple } = this.props;
|
||||||
|
if (multiple) {
|
||||||
|
onChange(value.filter(item => item.id !== row.id));
|
||||||
|
} else if (value.id === row.id) {
|
||||||
|
onChange(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModal() {
|
||||||
|
const { onChange, multiple } = this.props;
|
||||||
|
const { selectedItems } = this.state;
|
||||||
|
const value = multiple ? selectedItems : selectedItems[0] || null;
|
||||||
|
|
||||||
|
this.handleModalToggle();
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearQSParams() {
|
||||||
|
const { qsConfig, history } = this.props;
|
||||||
|
const parts = history.location.search.replace(/^\?/, '').split('&');
|
||||||
|
const ns = qsConfig.namespace;
|
||||||
|
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
||||||
|
history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isModalOpen, selectedItems, error } = this.state;
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
items,
|
||||||
|
count,
|
||||||
|
lookupHeader,
|
||||||
|
value,
|
||||||
|
columns,
|
||||||
|
multiple,
|
||||||
|
name,
|
||||||
|
onBlur,
|
||||||
|
qsConfig,
|
||||||
|
required,
|
||||||
|
selectCategory,
|
||||||
|
selectCategoryOptions,
|
||||||
|
selectedCategory,
|
||||||
|
i18n,
|
||||||
|
} = this.props;
|
||||||
|
const header = lookupHeader || i18n._(t`Items`);
|
||||||
|
const canDelete = !required || (multiple && value.length > 1);
|
||||||
|
const chips = () => {
|
||||||
|
return selectCategoryOptions && selectCategoryOptions.length > 0 ? (
|
||||||
|
<ChipGroup>
|
||||||
|
{(multiple ? value : [value]).map(chip => (
|
||||||
|
<CredentialChip
|
||||||
|
key={chip.id}
|
||||||
|
onClick={() => this.removeItemAndSave(chip)}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
credential={chip}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
|
) : (
|
||||||
|
<ChipGroup>
|
||||||
|
{(multiple ? value : [value]).map(chip => (
|
||||||
|
<Chip
|
||||||
|
key={chip.id}
|
||||||
|
onClick={() => this.removeItemAndSave(chip)}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
>
|
||||||
|
{chip.name}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<InputGroup onBlur={onBlur}>
|
||||||
|
<SearchButton
|
||||||
|
aria-label="Search"
|
||||||
|
id={id}
|
||||||
|
onClick={this.handleModalToggle}
|
||||||
|
variant={ButtonVariant.tertiary}
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</SearchButton>
|
||||||
|
<ChipHolder className="pf-c-form-control">
|
||||||
|
{value ? chips(value) : null}
|
||||||
|
</ChipHolder>
|
||||||
|
</InputGroup>
|
||||||
|
<Modal
|
||||||
|
className="awx-c-modal"
|
||||||
|
title={i18n._(t`Select ${header}`)}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={this.handleModalToggle}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key="select"
|
||||||
|
variant="primary"
|
||||||
|
onClick={this.saveModal}
|
||||||
|
style={selectedItems.length === 0 ? { display: 'none' } : {}}
|
||||||
|
>
|
||||||
|
{i18n._(t`Select`)}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={this.handleModalToggle}
|
||||||
|
>
|
||||||
|
{i18n._(t`Cancel`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{selectCategoryOptions && selectCategoryOptions.length > 0 && (
|
||||||
|
<ToolbarItem css=" display: flex; align-items: center;">
|
||||||
|
<span css="flex: 0 0 25%;">Selected Category</span>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<AnsibleSelect
|
||||||
|
css="flex: 1 1 75%;"
|
||||||
|
id="multiCredentialsLookUp-select"
|
||||||
|
label="Selected Category"
|
||||||
|
data={selectCategoryOptions}
|
||||||
|
value={selectedCategory.label}
|
||||||
|
onChange={selectCategory}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
)}
|
||||||
|
{selectedItems.length > 0 && (
|
||||||
|
<SelectedList
|
||||||
|
label={i18n._(t`Selected`)}
|
||||||
|
selected={selectedItems}
|
||||||
|
showOverflowAfter={5}
|
||||||
|
onRemove={this.removeItem}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
isCredentialList={
|
||||||
|
selectCategoryOptions && selectCategoryOptions.length > 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PaginatedDataList
|
||||||
|
items={items}
|
||||||
|
itemCount={count}
|
||||||
|
pluralizedItemName={lookupHeader}
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
toolbarColumns={columns}
|
||||||
|
renderItem={item => (
|
||||||
|
<CheckboxListItem
|
||||||
|
key={item.id}
|
||||||
|
itemId={item.id}
|
||||||
|
name={multiple ? item.name : name}
|
||||||
|
label={item.name}
|
||||||
|
isSelected={selectedItems.some(i => i.id === item.id)}
|
||||||
|
onSelect={() => this.addItem(item)}
|
||||||
|
isRadio={
|
||||||
|
!multiple ||
|
||||||
|
(selectCategoryOptions &&
|
||||||
|
selectCategoryOptions.length &&
|
||||||
|
selectedCategory.value !== 'Vault')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||||
|
showPageSizeOptions={false}
|
||||||
|
/>
|
||||||
|
{error ? <div>error: {error.message}</div> : ''}
|
||||||
|
</Modal>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item = shape({
|
||||||
|
id: number.isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
CategoryLookup.propTypes = {
|
||||||
|
id: string,
|
||||||
|
items: arrayOf(shape({})).isRequired,
|
||||||
|
// TODO: change to `header`
|
||||||
|
lookupHeader: string,
|
||||||
|
name: string,
|
||||||
|
onChange: func.isRequired,
|
||||||
|
value: oneOfType([Item, arrayOf(Item)]),
|
||||||
|
multiple: bool,
|
||||||
|
required: bool,
|
||||||
|
qsConfig: QSConfig.isRequired,
|
||||||
|
selectCategory: func.isRequired,
|
||||||
|
selectCategoryOptions: oneOfType(shape({})).isRequired,
|
||||||
|
selectedCategory: shape({}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
CategoryLookup.defaultProps = {
|
||||||
|
id: 'lookup-search',
|
||||||
|
lookupHeader: null,
|
||||||
|
name: null,
|
||||||
|
value: null,
|
||||||
|
multiple: false,
|
||||||
|
required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CategoryLookup as _CategoryLookup };
|
||||||
|
export default withI18n()(withRouter(CategoryLookup));
|
||||||
@@ -1,84 +1,114 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { arrayOf, string, func, object } from 'prop-types';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { InstanceGroupsAPI } from '@api';
|
import { InstanceGroupsAPI } from '@api';
|
||||||
import Lookup from '@components/Lookup';
|
import Lookup from '@components/Lookup';
|
||||||
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
|
const QS_CONFIG = getQSConfig('instance-groups', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
});
|
||||||
|
// const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
|
||||||
|
|
||||||
class InstanceGroupsLookup extends React.Component {
|
function InstanceGroupsLookup({
|
||||||
render() {
|
value,
|
||||||
const { value, tooltip, onChange, className, i18n } = this.props;
|
onChange,
|
||||||
|
tooltip,
|
||||||
|
className,
|
||||||
|
history,
|
||||||
|
i18n,
|
||||||
|
}) {
|
||||||
|
const [instanceGroups, setInstanceGroups] = useState([]);
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
/*
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||||
|
try {
|
||||||
|
const { data } = await InstanceGroupsAPI.read(params);
|
||||||
|
setInstanceGroups(data.results);
|
||||||
|
setCount(data.count);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [history.location]);
|
||||||
|
|
||||||
|
/*
|
||||||
Wrapping <div> added to workaround PF bug:
|
Wrapping <div> added to workaround PF bug:
|
||||||
https://github.com/patternfly/patternfly-react/issues/2855
|
https://github.com/patternfly/patternfly-react/issues/2855
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={i18n._(t`Instance Groups`)}
|
label={i18n._(t`Instance Groups`)}
|
||||||
fieldId="org-instance-groups"
|
fieldId="org-instance-groups"
|
||||||
>
|
>
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
<Tooltip position="right" content={tooltip}>
|
<Tooltip position="right" content={tooltip}>
|
||||||
<QuestionCircleIcon />
|
<QuestionCircleIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Lookup
|
<Lookup
|
||||||
id="org-instance-groups"
|
id="org-instance-groups"
|
||||||
lookupHeader={i18n._(t`Instance Groups`)}
|
lookupHeader={i18n._(t`Instance Groups`)}
|
||||||
name="instanceGroups"
|
name="instanceGroups"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
getItems={getInstanceGroups}
|
items={instanceGroups}
|
||||||
qsNamespace="instance-group"
|
count={count}
|
||||||
multiple
|
qsConfig={QS_CONFIG}
|
||||||
columns={[
|
multiple
|
||||||
{
|
columns={[
|
||||||
name: i18n._(t`Name`),
|
{
|
||||||
key: 'name',
|
name: i18n._(t`Name`),
|
||||||
isSortable: true,
|
key: 'name',
|
||||||
isSearchable: true,
|
isSortable: true,
|
||||||
},
|
isSearchable: true,
|
||||||
{
|
},
|
||||||
name: i18n._(t`Modified`),
|
{
|
||||||
key: 'modified',
|
name: i18n._(t`Modified`),
|
||||||
isSortable: false,
|
key: 'modified',
|
||||||
isNumeric: true,
|
isSortable: false,
|
||||||
},
|
isNumeric: true,
|
||||||
{
|
},
|
||||||
name: i18n._(t`Created`),
|
{
|
||||||
key: 'created',
|
name: i18n._(t`Created`),
|
||||||
isSortable: false,
|
key: 'created',
|
||||||
isNumeric: true,
|
isSortable: false,
|
||||||
},
|
isNumeric: true,
|
||||||
]}
|
},
|
||||||
sortedColumnKey="name"
|
]}
|
||||||
/>
|
sortedColumnKey="name"
|
||||||
</FormGroup>
|
/>
|
||||||
</div>
|
{error ? <div>error {error.message}</div> : ''}
|
||||||
);
|
</FormGroup>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceGroupsLookup.propTypes = {
|
InstanceGroupsLookup.propTypes = {
|
||||||
value: PropTypes.arrayOf(PropTypes.object).isRequired,
|
value: arrayOf(object).isRequired,
|
||||||
tooltip: PropTypes.string,
|
tooltip: string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: func.isRequired,
|
||||||
|
className: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
InstanceGroupsLookup.defaultProps = {
|
InstanceGroupsLookup.defaultProps = {
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
|
className: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(InstanceGroupsLookup);
|
export default withI18n()(withRouter(InstanceGroupsLookup));
|
||||||
|
|||||||
@@ -15,20 +15,17 @@ import {
|
|||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
InputGroup as PFInputGroup,
|
InputGroup as PFInputGroup,
|
||||||
Modal,
|
Modal,
|
||||||
ToolbarItem,
|
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import AnsibleSelect from '../AnsibleSelect';
|
|
||||||
import PaginatedDataList from '../PaginatedDataList';
|
import PaginatedDataList from '../PaginatedDataList';
|
||||||
import VerticalSeperator from '../VerticalSeparator';
|
|
||||||
import DataListToolbar from '../DataListToolbar';
|
import DataListToolbar from '../DataListToolbar';
|
||||||
import CheckboxListItem from '../CheckboxListItem';
|
import CheckboxListItem from '../CheckboxListItem';
|
||||||
import SelectedList from '../SelectedList';
|
import SelectedList from '../SelectedList';
|
||||||
import { ChipGroup, Chip, CredentialChip } from '../Chip';
|
import { ChipGroup, Chip } from '../Chip';
|
||||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
import { QSConfig } from '@types';
|
||||||
|
|
||||||
const SearchButton = styled(Button)`
|
const SearchButton = styled(Button)`
|
||||||
::after {
|
::after {
|
||||||
@@ -66,42 +63,16 @@ class Lookup extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
selectedItems,
|
selectedItems,
|
||||||
results: [],
|
|
||||||
count: 0,
|
|
||||||
error: null,
|
|
||||||
};
|
};
|
||||||
this.qsConfig = getQSConfig(props.qsNamespace, {
|
|
||||||
page: 1,
|
|
||||||
page_size: 5,
|
|
||||||
order_by: props.sortedColumnKey,
|
|
||||||
});
|
|
||||||
this.handleModalToggle = this.handleModalToggle.bind(this);
|
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||||
this.addItem = this.addItem.bind(this);
|
this.addItem = this.addItem.bind(this);
|
||||||
this.removeItem = this.removeItem.bind(this);
|
this.removeItem = this.removeItem.bind(this);
|
||||||
this.saveModal = this.saveModal.bind(this);
|
this.saveModal = this.saveModal.bind(this);
|
||||||
this.getData = this.getData.bind(this);
|
|
||||||
this.clearQSParams = this.clearQSParams.bind(this);
|
this.clearQSParams = this.clearQSParams.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { location, selectedCategory } = this.props;
|
|
||||||
if (
|
|
||||||
location !== prevProps.location ||
|
|
||||||
prevProps.selectedCategory !== selectedCategory
|
|
||||||
) {
|
|
||||||
this.getData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertCorrectValueType() {
|
assertCorrectValueType() {
|
||||||
const { multiple, value, selectCategoryOptions } = this.props;
|
const { multiple, value } = this.props;
|
||||||
if (selectCategoryOptions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!multiple && Array.isArray(value)) {
|
if (!multiple && Array.isArray(value)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Lookup value must not be an array unless `multiple` is set'
|
'Lookup value must not be an array unless `multiple` is set'
|
||||||
@@ -112,27 +83,6 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData() {
|
|
||||||
const {
|
|
||||||
getItems,
|
|
||||||
location: { search },
|
|
||||||
} = this.props;
|
|
||||||
const queryParams = parseQueryString(this.qsConfig, search);
|
|
||||||
|
|
||||||
this.setState({ error: false });
|
|
||||||
try {
|
|
||||||
const { data } = await getItems(queryParams);
|
|
||||||
const { results, count } = data;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
results,
|
|
||||||
count,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ error: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(row) {
|
removeItem(row) {
|
||||||
const { selectedItems } = this.state;
|
const { selectedItems } = this.state;
|
||||||
const { onToggleItem } = this.props;
|
const { onToggleItem } = this.props;
|
||||||
@@ -163,55 +113,11 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
this.setState({ selectedItems: [...selectedItems, row] });
|
this.setState({ selectedItems: [...selectedItems, row] });
|
||||||
}
|
}
|
||||||
// toggleSelected(row) {
|
|
||||||
// const {
|
|
||||||
// name,
|
|
||||||
// onChange,
|
|
||||||
// multiple,
|
|
||||||
// onToggleItem,
|
|
||||||
// selectCategoryOptions,
|
|
||||||
// onChange,
|
|
||||||
// value
|
|
||||||
// } = this.props;
|
|
||||||
// const {
|
|
||||||
// selectedItems: updatedSelectedItems,
|
|
||||||
// isModalOpen,
|
|
||||||
// } = this.state;
|
|
||||||
|
|
||||||
// const selectedIndex = updatedSelectedItems.findIndex(
|
|
||||||
// selectedRow => selectedRow.id === row.id
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// if (multiple) {
|
|
||||||
//
|
|
||||||
// if (selectCategoryOptions) {
|
|
||||||
//
|
|
||||||
// onToggleItem(row, isModalOpen);
|
|
||||||
// }
|
|
||||||
// if (selectedIndex > -1) {
|
|
||||||
//
|
|
||||||
// const valueToUpdate = value.filter(itemValue => itemValue.id !==row.id );
|
|
||||||
// onChange(valueToUpdate)
|
|
||||||
// } else {
|
|
||||||
//
|
|
||||||
// onChange([...value, row])
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
//
|
|
||||||
// onChange(row)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Updates the selected items from parent state
|
|
||||||
// This handles the case where the user removes chips from the lookup input
|
|
||||||
// while the modal is closed
|
|
||||||
// if (!isModalOpen) {
|
|
||||||
// onChange(updatedSelectedItems, name);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
// TODO: cleanup
|
||||||
handleModalToggle() {
|
handleModalToggle() {
|
||||||
const { isModalOpen } = this.state;
|
const { isModalOpen } = this.state;
|
||||||
const { value, multiple, selectCategory } = this.props;
|
const { value, multiple } = this.props;
|
||||||
// Resets the selected items from parent state whenever modal is opened
|
// Resets the selected items from parent state whenever modal is opened
|
||||||
// This handles the case where the user closes/cancels the modal and
|
// This handles the case where the user closes/cancels the modal and
|
||||||
// opens it again
|
// opens it again
|
||||||
@@ -223,20 +129,17 @@ class Lookup extends React.Component {
|
|||||||
this.setState({ selectedItems });
|
this.setState({ selectedItems });
|
||||||
} else {
|
} else {
|
||||||
this.clearQSParams();
|
this.clearQSParams();
|
||||||
if (selectCategory) {
|
|
||||||
selectCategory(null, 'Machine');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
isModalOpen: !prevState.isModalOpen,
|
isModalOpen: !prevState.isModalOpen,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItemAndSave(row) {
|
removeItemAndSave(item) {
|
||||||
const { value, onChange, multiple } = this.props;
|
const { value, onChange, multiple } = this.props;
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
onChange(value.filter(item => item.id !== row.id));
|
onChange(value.filter(i => i.id !== item.id));
|
||||||
} else if (value.id === row.id) {
|
} else if (value.id === item.id) {
|
||||||
onChange(null);
|
onChange(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,58 +154,31 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearQSParams() {
|
clearQSParams() {
|
||||||
const { history } = this.props;
|
const { qsConfig, history } = this.props;
|
||||||
const parts = history.location.search.replace(/^\?/, '').split('&');
|
const parts = history.location.search.replace(/^\?/, '').split('&');
|
||||||
const ns = this.qsConfig.namespace;
|
const ns = qsConfig.namespace;
|
||||||
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
||||||
history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isModalOpen, selectedItems, error, results, count } = this.state;
|
const { isModalOpen, selectedItems } = this.state;
|
||||||
const {
|
const {
|
||||||
form,
|
|
||||||
id,
|
id,
|
||||||
lookupHeader,
|
lookupHeader,
|
||||||
value,
|
value,
|
||||||
|
items,
|
||||||
|
count,
|
||||||
columns,
|
columns,
|
||||||
multiple,
|
multiple,
|
||||||
name,
|
name,
|
||||||
onBlur,
|
onBlur,
|
||||||
selectCategory,
|
|
||||||
required,
|
required,
|
||||||
|
qsConfig,
|
||||||
i18n,
|
i18n,
|
||||||
selectCategoryOptions,
|
|
||||||
selectedCategory,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const header = lookupHeader || i18n._(t`Items`);
|
const header = lookupHeader || i18n._(t`Items`);
|
||||||
const canDelete = !required || (multiple && value.length > 1);
|
const canDelete = !required || (multiple && value.length > 1);
|
||||||
const chips = () => {
|
|
||||||
return selectCategoryOptions && selectCategoryOptions.length > 0 ? (
|
|
||||||
<ChipGroup defaultIsOpen numChips={5}>
|
|
||||||
{(multiple ? value : [value]).map(chip => (
|
|
||||||
<CredentialChip
|
|
||||||
key={chip.id}
|
|
||||||
onClick={() => this.removeItemAndSave(chip)}
|
|
||||||
isReadOnly={!canDelete}
|
|
||||||
credential={chip}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ChipGroup>
|
|
||||||
) : (
|
|
||||||
<ChipGroup defaultIsOpen numChips={5}>
|
|
||||||
{(multiple ? value : [value]).map(chip => (
|
|
||||||
<Chip
|
|
||||||
key={chip.id}
|
|
||||||
onClick={() => this.removeItemAndSave(chip)}
|
|
||||||
isReadOnly={!canDelete}
|
|
||||||
>
|
|
||||||
{chip.name}
|
|
||||||
</Chip>
|
|
||||||
))}
|
|
||||||
</ChipGroup>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<InputGroup onBlur={onBlur}>
|
<InputGroup onBlur={onBlur}>
|
||||||
@@ -315,7 +191,17 @@ class Lookup extends React.Component {
|
|||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
<ChipHolder className="pf-c-form-control">
|
<ChipHolder className="pf-c-form-control">
|
||||||
{value ? chips(value) : null}
|
<ChipGroup defaultIsOpen numChips={5}>
|
||||||
|
{(multiple ? value : [value]).map(chip => (
|
||||||
|
<Chip
|
||||||
|
key={chip.id}
|
||||||
|
onClick={() => this.removeItemAndSave(chip)}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
>
|
||||||
|
{chip.name}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
</ChipHolder>
|
</ChipHolder>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -341,21 +227,6 @@ class Lookup extends React.Component {
|
|||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{selectCategoryOptions && selectCategoryOptions.length > 0 && (
|
|
||||||
<ToolbarItem css=" display: flex; align-items: center;">
|
|
||||||
<span css="flex: 0 0 25%;">Selected Category</span>
|
|
||||||
<VerticalSeperator />
|
|
||||||
<AnsibleSelect
|
|
||||||
css="flex: 1 1 75%;"
|
|
||||||
id="multiCredentialsLookUp-select"
|
|
||||||
label="Selected Category"
|
|
||||||
data={selectCategoryOptions}
|
|
||||||
value={selectedCategory.label}
|
|
||||||
onChange={selectCategory}
|
|
||||||
form={form}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
)}
|
|
||||||
{selectedItems.length > 0 && (
|
{selectedItems.length > 0 && (
|
||||||
<SelectedList
|
<SelectedList
|
||||||
label={i18n._(t`Selected`)}
|
label={i18n._(t`Selected`)}
|
||||||
@@ -363,16 +234,13 @@ class Lookup extends React.Component {
|
|||||||
showOverflowAfter={5}
|
showOverflowAfter={5}
|
||||||
onRemove={this.removeItem}
|
onRemove={this.removeItem}
|
||||||
isReadOnly={!canDelete}
|
isReadOnly={!canDelete}
|
||||||
isCredentialList={
|
|
||||||
selectCategoryOptions && selectCategoryOptions.length > 0
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
items={results}
|
items={items}
|
||||||
itemCount={count}
|
itemCount={count}
|
||||||
pluralizedItemName={lookupHeader}
|
pluralizedItemName={lookupHeader}
|
||||||
qsConfig={this.qsConfig}
|
qsConfig={qsConfig}
|
||||||
toolbarColumns={columns}
|
toolbarColumns={columns}
|
||||||
renderItem={item => (
|
renderItem={item => (
|
||||||
<CheckboxListItem
|
<CheckboxListItem
|
||||||
@@ -382,18 +250,12 @@ class Lookup extends React.Component {
|
|||||||
label={item.name}
|
label={item.name}
|
||||||
isSelected={selectedItems.some(i => i.id === item.id)}
|
isSelected={selectedItems.some(i => i.id === item.id)}
|
||||||
onSelect={() => this.addItem(item)}
|
onSelect={() => this.addItem(item)}
|
||||||
isRadio={
|
isRadio={!multiple}
|
||||||
!multiple ||
|
|
||||||
(selectCategoryOptions &&
|
|
||||||
selectCategoryOptions.length &&
|
|
||||||
selectedCategory.value !== 'Vault')
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||||
showPageSizeOptions={false}
|
showPageSizeOptions={false}
|
||||||
/>
|
/>
|
||||||
{error ? <div>error</div> : ''}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@@ -406,15 +268,16 @@ const Item = shape({
|
|||||||
|
|
||||||
Lookup.propTypes = {
|
Lookup.propTypes = {
|
||||||
id: string,
|
id: string,
|
||||||
getItems: func.isRequired,
|
items: arrayOf(shape({})).isRequired,
|
||||||
|
count: number.isRequired,
|
||||||
|
// TODO: change to `header`
|
||||||
lookupHeader: string,
|
lookupHeader: string,
|
||||||
name: string,
|
name: string,
|
||||||
onChange: func.isRequired,
|
onChange: func.isRequired,
|
||||||
value: oneOfType([Item, arrayOf(Item)]),
|
value: oneOfType([Item, arrayOf(Item)]),
|
||||||
sortedColumnKey: string.isRequired,
|
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
required: bool,
|
required: bool,
|
||||||
qsNamespace: string,
|
qsConfig: QSConfig.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
Lookup.defaultProps = {
|
Lookup.defaultProps = {
|
||||||
@@ -424,7 +287,6 @@ Lookup.defaultProps = {
|
|||||||
value: null,
|
value: null,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
required: false,
|
required: false,
|
||||||
qsNamespace: 'lookup',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Lookup as _Lookup };
|
export { Lookup as _Lookup };
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
import Lookup from '@components/Lookup';
|
import CategoryLookup from '@components/Lookup/CategoryLookup';
|
||||||
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const QS_CONFIG = getQSConfig('credentials', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
});
|
||||||
|
|
||||||
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
||||||
let newCredentialsList;
|
let newCredentialsList;
|
||||||
const isSelectedCredentialInState =
|
const isSelectedCredentialInState =
|
||||||
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length >
|
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length > 0;
|
||||||
0;
|
|
||||||
|
|
||||||
if (isSelectedCredentialInState) {
|
if (isSelectedCredentialInState) {
|
||||||
newCredentialsList = credentialsToUpdate.filter(
|
newCredentialsList = credentialsToUpdate.filter(
|
||||||
@@ -31,6 +38,7 @@ function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
|||||||
}
|
}
|
||||||
return newCredentialsList;
|
return newCredentialsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiCredentialsLookup extends React.Component {
|
class MultiCredentialsLookup extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -48,6 +56,7 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadCredentialTypes();
|
this.loadCredentialTypes();
|
||||||
|
this.loadCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCredentialTypes() {
|
async loadCredentialTypes() {
|
||||||
@@ -80,23 +89,38 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCredentials(params) {
|
async loadCredentials() {
|
||||||
|
const { history, onError } = this.props;
|
||||||
const { selectedCredentialType } = this.state;
|
const { selectedCredentialType } = this.state;
|
||||||
|
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||||
params.credential_type = selectedCredentialType.id || 1;
|
params.credential_type = selectedCredentialType.id || 1;
|
||||||
return CredentialsAPI.read(params);
|
try {
|
||||||
|
const { data } = await CredentialsAPI.read(params);
|
||||||
|
this.setState({
|
||||||
|
credentials: data.results,
|
||||||
|
count: data.count,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
handleCredentialTypeSelect(value, type) {
|
handleCredentialTypeSelect(value, type) {
|
||||||
const { credentialTypes } = this.state;
|
const { credentialTypes } = this.state;
|
||||||
const selectedType = credentialTypes.filter(item => item.label === type);
|
const selectedType = credentialTypes.filter(item => item.label === type);
|
||||||
this.setState({ selectedCredentialType: selectedType[0] });
|
this.setState({ selectedCredentialType: selectedType[0] }, () => {
|
||||||
|
this.loadCredentials();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectedCredentialType, credentialTypes } = this.state;
|
const {
|
||||||
const { tooltip, i18n, credentials, onChange } = this.props;
|
selectedCredentialType,
|
||||||
|
credentialTypes,
|
||||||
|
credentials,
|
||||||
|
count,
|
||||||
|
} = this.state;
|
||||||
|
const { tooltip, i18n, value, onChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
@@ -105,7 +129,7 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{credentialTypes && (
|
{credentialTypes && (
|
||||||
<Lookup
|
<CategoryLookup
|
||||||
selectCategoryOptions={credentialTypes}
|
selectCategoryOptions={credentialTypes}
|
||||||
selectCategory={this.handleCredentialTypeSelect}
|
selectCategory={this.handleCredentialTypeSelect}
|
||||||
selectedCategory={selectedCredentialType}
|
selectedCategory={selectedCredentialType}
|
||||||
@@ -114,11 +138,12 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
id="multiCredential"
|
id="multiCredential"
|
||||||
lookupHeader={i18n._(t`Credentials`)}
|
lookupHeader={i18n._(t`Credentials`)}
|
||||||
name="credentials"
|
name="credentials"
|
||||||
value={credentials}
|
value={value}
|
||||||
multiple
|
multiple
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
getItems={this.loadCredentials}
|
items={credentials}
|
||||||
qsNamespace="credentials"
|
count={count}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
name: i18n._(t`Name`),
|
name: i18n._(t`Name`),
|
||||||
@@ -127,7 +152,6 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
isSearchable: true,
|
isSearchable: true,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
sortedColumnKey="name"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
@@ -137,7 +161,7 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
|
|
||||||
MultiCredentialsLookup.propTypes = {
|
MultiCredentialsLookup.propTypes = {
|
||||||
tooltip: PropTypes.string,
|
tooltip: PropTypes.string,
|
||||||
credentials: PropTypes.arrayOf(
|
value: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
@@ -152,8 +176,8 @@ MultiCredentialsLookup.propTypes = {
|
|||||||
|
|
||||||
MultiCredentialsLookup.defaultProps = {
|
MultiCredentialsLookup.defaultProps = {
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
credentials: [],
|
value: [],
|
||||||
};
|
};
|
||||||
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
||||||
|
|
||||||
export default withI18n()(MultiCredentialsLookup);
|
export default withI18n()(withRouter(MultiCredentialsLookup));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default } from './Lookup';
|
export { default } from './Lookup';
|
||||||
|
export { default as CategoryLookup } from './CategoryLookup';
|
||||||
export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
|
export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
|
||||||
export { default as InventoryLookup } from './InventoryLookup';
|
export { default as InventoryLookup } from './InventoryLookup';
|
||||||
export { default as ProjectLookup } from './ProjectLookup';
|
export { default as ProjectLookup } from './ProjectLookup';
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class SelectedList extends Component {
|
|||||||
isReadOnly,
|
isReadOnly,
|
||||||
isCredentialList,
|
isCredentialList,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
// TODO: replace isCredentialList with renderChip ?
|
||||||
const chips = isCredentialList
|
const chips = isCredentialList
|
||||||
? selected.map(item => (
|
? selected.map(item => (
|
||||||
<CredentialChip
|
<CredentialChip
|
||||||
@@ -66,6 +67,7 @@ SelectedList.propTypes = {
|
|||||||
onRemove: PropTypes.func,
|
onRemove: PropTypes.func,
|
||||||
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isReadOnly: PropTypes.bool,
|
isReadOnly: PropTypes.bool,
|
||||||
|
isCredentialList: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectedList.defaultProps = {
|
SelectedList.defaultProps = {
|
||||||
@@ -73,6 +75,7 @@ SelectedList.defaultProps = {
|
|||||||
label: 'Selected',
|
label: 'Selected',
|
||||||
onRemove: () => null,
|
onRemove: () => null,
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
|
isCredentialList: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectedList;
|
export default SelectedList;
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class JobTemplateForm extends Component {
|
|||||||
fieldId="template-credentials"
|
fieldId="template-credentials"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<MultiCredentialsLookup
|
<MultiCredentialsLookup
|
||||||
credentials={field.value}
|
value={field.value}
|
||||||
onChange={newCredentials =>
|
onChange={newCredentials =>
|
||||||
setFieldValue('credentials', newCredentials)
|
setFieldValue('credentials', newCredentials)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user