diff --git a/awx/ui_next/src/components/Lookup/InstanceGroupsLookup.jsx b/awx/ui_next/src/components/Lookup/InstanceGroupsLookup.jsx
index 089ec6d969..8071c8b26f 100644
--- a/awx/ui_next/src/components/Lookup/InstanceGroupsLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/InstanceGroupsLookup.jsx
@@ -3,32 +3,21 @@ import { arrayOf, string, func, object } from 'prop-types';
import { withRouter } from 'react-router-dom';
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 styled from 'styled-components';
+import { FormGroup } from '@patternfly/react-core';
import { InstanceGroupsAPI } from '@api';
import { getQSConfig, parseQueryString } from '@util/qs';
+import { FieldTooltip } from '@components/FormField';
import Lookup from './NewLookup';
+import SelectList from './shared/SelectList';
-const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
- margin-left: 10px;
-`;
-
-const QS_CONFIG = getQSConfig('instance-groups', {
+const QS_CONFIG = getQSConfig('instance_groups', {
page: 1,
page_size: 5,
order_by: 'name',
});
-// const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
-function InstanceGroupsLookup({
- value,
- onChange,
- tooltip,
- className,
- history,
- i18n,
-}) {
+function InstanceGroupsLookup(props) {
+ const { value, onChange, tooltip, className, history, i18n } = props;
const [instanceGroups, setInstanceGroups] = useState([]);
const [count, setCount] = useState(0);
const [error, setError] = useState(null);
@@ -46,56 +35,62 @@ function InstanceGroupsLookup({
})();
}, [history.location]);
- /*
- Wrapping
added to workaround PF bug:
- https://github.com/patternfly/patternfly-react/issues/2855
- */
return (
-
-
- {tooltip && (
-
-
-
+
+ {tooltip && }
+ (
+ dispatch({ type: 'SELECT_ITEM', item })}
+ deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
+ />
)}
-
- {error ? error {error.message}
: ''}
-
-
+ />
+ {error ?
error {error.message}
: ''}
+
);
}
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
index 0277aa0ad3..005c8d620d 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
@@ -1,18 +1,18 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes 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 styled from 'styled-components';
+import { FormGroup, ToolbarItem } from '@patternfly/react-core';
import { CredentialsAPI, CredentialTypesAPI } from '@api';
-import CategoryLookup from '@components/Lookup/CategoryLookup';
+import AnsibleSelect from '@components/AnsibleSelect';
+import { FieldTooltip } from '@components/FormField';
+import { CredentialChip } from '@components/Chip';
+import VerticalSeperator from '@components/VerticalSeparator';
import { getQSConfig, parseQueryString } from '@util/qs';
-
-const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
- margin-left: 10px;
-`;
+import Lookup from './NewLookup';
+import SelectList from './shared/SelectList';
+import multiCredentialReducer from './shared/multiCredentialReducer';
const QS_CONFIG = getQSConfig('credentials', {
page: 1,
@@ -20,6 +20,7 @@ const QS_CONFIG = getQSConfig('credentials', {
order_by: 'name',
});
+// TODO: move into reducer
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
let newCredentialsList;
const isSelectedCredentialInState =
@@ -39,124 +40,164 @@ function toggleCredentialSelection(credentialsToUpdate, newCredential) {
return newCredentialsList;
}
-class MultiCredentialsLookup extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- selectedCredentialType: { label: 'Machine', id: 1, kind: 'ssh' },
- credentialTypes: [],
- };
- this.loadCredentialTypes = this.loadCredentialTypes.bind(this);
- this.handleCredentialTypeSelect = this.handleCredentialTypeSelect.bind(
- this
- );
- this.loadCredentials = this.loadCredentials.bind(this);
- }
-
- componentDidMount() {
- this.loadCredentialTypes();
- this.loadCredentials();
- }
-
- async loadCredentialTypes() {
- const { onError } = this.props;
- try {
- const { data } = await CredentialTypesAPI.read();
- const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
- const credentialTypes = [];
- data.results.forEach(cred => {
- acceptableTypes.forEach(aT => {
- if (aT === cred.kind) {
- // This object has several repeated values as some of it's children
- // require different field values.
- cred = {
- id: cred.id,
- key: cred.id,
- kind: cred.kind,
- type: cred.namespace,
- value: cred.name,
- label: cred.name,
- isDisabled: false,
- };
- credentialTypes.push(cred);
- }
- });
- });
- this.setState({ credentialTypes });
- } catch (err) {
- onError(err);
- }
- }
-
- async loadCredentials() {
- const { history, onError } = this.props;
- const { selectedCredentialType } = this.state;
- const params = parseQueryString(QS_CONFIG, history.location.search);
- params.credential_type = selectedCredentialType.id || 1;
- try {
- const { data } = await CredentialsAPI.read(params);
- this.setState({
- credentials: data.results,
- count: data.count,
- });
- } catch (err) {
- onError(err);
- }
- }
-
- handleCredentialTypeSelect(value, type) {
- const { credentialTypes } = this.state;
- const selectedType = credentialTypes.filter(item => item.label === type);
- this.setState({ selectedCredentialType: selectedType[0] }, () => {
- this.loadCredentials();
+async function loadCredentialTypes() {
+ const { data } = await CredentialTypesAPI.read();
+ const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
+ const credentialTypes = [];
+ // TODO: cleanup
+ data.results.forEach(cred => {
+ acceptableTypes.forEach(aT => {
+ if (aT === cred.kind) {
+ // This object has several repeated values as some of it's children
+ // require different field values.
+ cred = {
+ id: cred.id,
+ key: cred.id,
+ kind: cred.kind,
+ type: cred.namespace,
+ value: cred.name,
+ label: cred.name,
+ isDisabled: false,
+ };
+ credentialTypes.push(cred);
+ }
});
- }
+ });
+ return credentialTypes;
+}
- render() {
- const {
- selectedCredentialType,
- credentialTypes,
- credentials,
- count,
- } = this.state;
- const { tooltip, i18n, value, onChange } = this.props;
- return (
-
- {tooltip && (
-
-
-
- )}
- {credentialTypes && (
- {
+ (async () => {
+ try {
+ const types = await loadCredentialTypes();
+ setCredentialTypes(types);
+ setSelectedType(types[0]);
+ } catch (err) {
+ onError(err);
+ }
+ })();
+ }, []);
+
+ useEffect(() => {
+ console.log('useEffect', selectedType);
+ (async () => {
+ if (!selectedType) {
+ return;
+ }
+ try {
+ const params = parseQueryString(QS_CONFIG, history.location.search);
+ const { results, count } = await loadCredentials(
+ params,
+ selectedType.id
+ );
+ setCredentials(results);
+ setCredentialsCount(count);
+ } catch (err) {
+ onError(err);
+ }
+ })();
+ }, [selectedType]);
+
+ // handleCredentialTypeSelect(value, type) {
+ // const { credentialTypes } = this.state;
+ // const selectedType = credentialTypes.filter(item => item.label === type);
+ // this.setState({ selectedCredentialType: selectedType[0] }, () => {
+ // this.loadCredentials();
+ // });
+ // }
+
+ // const {
+ // selectedCredentialType,
+ // credentialTypes,
+ // credentials,
+ // credentialsCount,
+ // } = state;
+
+ return (
+
+ {tooltip && }
+ (
+ removeItem(item)}
+ isReadOnly={!canDelete}
+ credential={item}
/>
)}
-
- );
- }
+ renderSelectList={({ state, dispatch, canDelete }) => {
+ return (
+ <>
+ {credentialTypes && credentialTypes.length > 0 && (
+
+ {i18n._(t`Selected Category`)}
+
+ {
+ setSelectedType(
+ credentialTypes.find(o => o.label === label)
+ );
+ }}
+ />
+
+ )}
+ {}}
+ deselectItem={() => {}}
+ />
+ >
+ );
+ }}
+ />
+
+ );
}
MultiCredentialsLookup.propTypes = {
@@ -178,6 +219,6 @@ MultiCredentialsLookup.defaultProps = {
tooltip: '',
value: [],
};
-export { MultiCredentialsLookup as _MultiCredentialsLookup };
+export { MultiCredentialsLookup as _MultiCredentialsLookup };
export default withI18n()(withRouter(MultiCredentialsLookup));
diff --git a/awx/ui_next/src/components/Lookup/NewLookup.jsx b/awx/ui_next/src/components/Lookup/NewLookup.jsx
index 0721e4e4d1..0f3342e5b5 100644
--- a/awx/ui_next/src/components/Lookup/NewLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/NewLookup.jsx
@@ -21,7 +21,6 @@ import { t } from '@lingui/macro';
import styled from 'styled-components';
import reducer, { initReducer } from './shared/reducer';
-import SelectList from './shared/SelectList';
import { ChipGroup, Chip } from '../Chip';
import { QSConfig } from '@types';
@@ -51,20 +50,28 @@ const ChipHolder = styled.div`
function Lookup(props) {
const {
id,
- items,
- count,
+ // items,
+ // count,
header,
- name,
+ // name,
onChange,
onBlur,
- columns,
+ // columns,
value,
multiple,
required,
qsConfig,
+ renderItemChip,
+ renderSelectList,
+ history,
i18n,
} = props;
- const [state, dispatch] = useReducer(reducer, props, initReducer);
+
+ const [state, dispatch] = useReducer(
+ reducer,
+ { value, multiple, required },
+ initReducer
+ );
useEffect(() => {
dispatch({ type: 'SET_MULTIPLE', value: multiple });
@@ -74,10 +81,18 @@ function Lookup(props) {
dispatch({ type: 'SET_VALUE', value });
}, [value]);
+ const clearQSParams = () => {
+ 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('&')}`);
+ };
+
const save = () => {
const { selectedItems } = state;
const val = multiple ? selectedItems : selectedItems[0] || null;
onChange(val);
+ clearQSParams();
dispatch({ type: 'CLOSE_MODAL' });
};
@@ -89,8 +104,12 @@ function Lookup(props) {
}
};
- const { isModalOpen, selectedItems } = state;
+ const closeModal = () => {
+ clearQSParams();
+ dispatch({ type: 'CLOSE_MODAL' });
+ };
+ const { isModalOpen, selectedItems } = state;
const canDelete = !required || (multiple && value.length > 1);
return (
@@ -105,15 +124,13 @@ function Lookup(props) {
- {(multiple ? value : [value]).map(item => (
- removeItem(item)}
- isReadOnly={!canDelete}
- >
- {item.name}
-
- ))}
+ {(multiple ? value : [value]).map(item =>
+ renderItemChip({
+ item,
+ removeItem,
+ canDelete,
+ })
+ )}
@@ -121,7 +138,7 @@ function Lookup(props) {
className="awx-c-modal"
title={i18n._(t`Select ${header || i18n._(t`Items`)}`)}
isOpen={isModalOpen}
- onClose={() => dispatch({ type: 'TOGGLE_MODAL' })}
+ onClose={closeModal}
actions={[
,
-
);
@@ -165,27 +171,38 @@ const Item = shape({
Lookup.propTypes = {
id: string,
- items: arrayOf(shape({})).isRequired,
- count: number.isRequired,
+ // items: arrayOf(shape({})).isRequired,
+ // count: number.isRequired,
// TODO: change to `header`
header: string,
- name: string,
+ // name: string,
onChange: func.isRequired,
value: oneOfType([Item, arrayOf(Item)]),
multiple: bool,
required: bool,
onBlur: func,
qsConfig: QSConfig.isRequired,
+ renderItemChip: func,
+ renderSelectList: func.isRequired,
};
Lookup.defaultProps = {
id: 'lookup-search',
header: null,
- name: null,
+ // name: null,
value: null,
multiple: false,
required: false,
onBlur: () => {},
+ renderItemChip: ({ item, removeItem, canDelete }) => (
+
removeItem(item)}
+ isReadOnly={!canDelete}
+ >
+ {item.name}
+
+ ),
};
export { Lookup as _Lookup };
diff --git a/awx/ui_next/src/components/Lookup/shared/SelectList.jsx b/awx/ui_next/src/components/Lookup/shared/SelectList.jsx
index 96db387c72..128d9371d8 100644
--- a/awx/ui_next/src/components/Lookup/shared/SelectList.jsx
+++ b/awx/ui_next/src/components/Lookup/shared/SelectList.jsx
@@ -26,7 +26,8 @@ function SelectList({
name,
qsConfig,
readOnly,
- dispatch,
+ selectItem,
+ deselectItem,
i18n,
}) {
return (
@@ -36,7 +37,7 @@ function SelectList({
label={i18n._(t`Selected`)}
selected={value}
showOverflowAfter={5}
- onRemove={item => dispatch({ type: 'DESELECT_ITEM', item })}
+ onRemove={item => deselectItem(item)}
isReadOnly={readOnly}
/>
)}
@@ -53,8 +54,8 @@ function SelectList({
name={multiple ? item.name : name}
label={item.name}
isSelected={value.some(i => i.id === item.id)}
- onSelect={() => dispatch({ type: 'SELECT_ITEM', item })}
- onDeselect={() => dispatch({ type: 'DESELECT_ITEM', item })}
+ onSelect={() => selectItem(item)}
+ onDeselect={() => deselectItem(item)}
isRadio={!multiple}
/>
)}
@@ -75,7 +76,8 @@ SelectList.propTypes = {
columns: arrayOf(shape({})).isRequired,
multiple: bool,
qsConfig: QSConfig.isRequired,
- dispatch: func.isRequired,
+ selectItem: func.isRequired,
+ deselectItem: func.isRequired,
};
SelectList.defaultProps = {
multiple: false,
diff --git a/awx/ui_next/src/components/Lookup/shared/reducer.js b/awx/ui_next/src/components/Lookup/shared/reducer.js
index 2e2c88f096..9f7f6b37e0 100644
--- a/awx/ui_next/src/components/Lookup/shared/reducer.js
+++ b/awx/ui_next/src/components/Lookup/shared/reducer.js
@@ -1,3 +1,5 @@
+// import { useReducer, useEffect } from 'react';
+
export default function reducer(state, action) {
// console.log(action, state);
switch (action.type) {
@@ -56,33 +58,13 @@ function toggleModal(state) {
}
function closeModal(state) {
- // TODO clear QSParams & push history state?
- // state.clearQSParams();
return {
...state,
isModalOpen: false,
};
}
-// 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('&')}`);
-// }
-export function initReducer({
- id,
- items,
- count,
- header,
- name,
- onChange,
- value,
- multiple = false,
- required = false,
- qsConfig,
-}) {
+export function initReducer({ value, multiple = false, required = false }) {
assertCorrectValueType(value, multiple);
let selectedItems = [];
if (value) {
@@ -94,7 +76,6 @@ export function initReducer({
multiple,
isModalOpen: false,
required,
- onChange,
};
}
@@ -108,3 +89,18 @@ function assertCorrectValueType(value, multiple) {
throw new Error('Lookup value must be an array if `multiple` is set');
}
}
+//
+// export function useLookup(config) {
+// const { value, multiple, required, onChange, history } = config;
+// const [state, dispatch] = useReducer(
+// config.reducer || reducer,
+// {
+// value,
+// multiple,
+// required,
+// },
+// config.initReducer || initReducer
+// );
+//
+// return [state, dispatch];
+// }