Reapply prompt on launch for job template fields after rebasing.

This commit is contained in:
mabashian
2020-02-26 16:59:45 -05:00
parent 0e663921d6
commit e80e3f7410
4 changed files with 341 additions and 1400 deletions

View File

@@ -1,82 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { string, bool } from 'prop-types'; import { string, bool } from 'prop-types';
import { useField } from 'formik';
import { Split, SplitItem } from '@patternfly/react-core';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants';
function VariablesField({ id, name, label, readOnly }) {
const [field, meta, helpers] = useField(name);
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
return (
<>
<Split gutter="sm">
<SplitItem>
<label htmlFor={id} className="pf-c-form__label">
<span className="pf-c-form__label-text">{label}</span>
</label>
</SplitItem>
<SplitItem>
<YamlJsonToggle
mode={mode}
onChange={newMode => {
try {
const newVal =
newMode === YAML_MODE
? jsonToYaml(field.value)
: yamlToJson(field.value);
helpers.setValue(newVal);
setMode(newMode);
} catch (err) {
helpers.setError(err.message);
}
}}
/>
</SplitItem>
</Split>
<CodeMirrorInput
mode={mode}
readOnly={readOnly}
{...field}
onChange={newVal => {
helpers.setValue(newVal);
}}
hasErrors={!!meta.error}
/>
{meta.error ? (
<div className="pf-c-form__helper-text pf-m-error" aria-live="polite">
{meta.error}
</div>
) : null}
</>
);
}
VariablesField.propTypes = {
id: string.isRequired,
name: string.isRequired,
label: string.isRequired,
readOnly: bool,
};
VariablesField.defaultProps = {
readOnly: false,
};
export default VariablesField;
/*
import React, { useState } from 'react';
import { string, bool } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Field, useFormikContext } from 'formik'; import { useField } from 'formik';
import { Split, SplitItem } from '@patternfly/react-core';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import { CheckboxField } from '@components/FormField';
import styled from 'styled-components'; import styled from 'styled-components';
import { Split, SplitItem } from '@patternfly/react-core';
import { CheckboxField } from '@components/FormField';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle'; import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
@@ -91,9 +21,8 @@ const StyledCheckboxField = styled(CheckboxField)`
`; `;
function VariablesField({ i18n, id, name, label, readOnly, promptId }) { function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
const { values, setFieldError, setFieldValue } = useFormikContext(); const [field, meta, helpers] = useField(name);
const value = values[name]; const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE);
return ( return (
<div className="pf-c-form__group"> <div className="pf-c-form__group">
@@ -111,12 +40,12 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
try { try {
const newVal = const newVal =
newMode === YAML_MODE newMode === YAML_MODE
? jsonToYaml(value) ? jsonToYaml(field.value)
: yamlToJson(value); : yamlToJson(field.value);
setFieldValue(name, newVal); helpers.setValue(newVal);
setMode(newMode); setMode(newMode);
} catch (err) { } catch (err) {
setFieldError(name, err.message); helpers.setError(err.message);
} }
}} }}
/> />
@@ -130,29 +59,20 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
/> />
)} )}
</FieldHeader> </FieldHeader>
<Field name={name}> <CodeMirrorInput
{({ field, form }) => ( mode={mode}
<> readOnly={readOnly}
<CodeMirrorInput {...field}
mode={mode} onChange={newVal => {
readOnly={readOnly} helpers.setValue(newVal);
{...field} }}
onChange={newVal => { hasErrors={!!meta.error}
form.setFieldValue(name, newVal); />
}} {meta.error ? (
hasErrors={!!form.errors[field.name]} <div className="pf-c-form__helper-text pf-m-error" aria-live="polite">
/> {meta.error}
{form.errors[field.name] ? ( </div>
<div ) : null}
className="pf-c-form__helper-text pf-m-error"
aria-live="polite"
>
{form.errors[field.name]}
</div>
) : null}
</>
)}
</Field>
</div> </div>
); );
} }
@@ -161,10 +81,11 @@ VariablesField.propTypes = {
name: string.isRequired, name: string.isRequired,
label: string.isRequired, label: string.isRequired,
readOnly: bool, readOnly: bool,
promptId: string,
}; };
VariablesField.defaultProps = { VariablesField.defaultProps = {
readOnly: false, readOnly: false,
promptId: null,
}; };
export default withI18n()(VariablesField); export default withI18n()(VariablesField);
*/

View File

@@ -3,210 +3,10 @@ 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, ToolbarItem } from '@patternfly/react-core';
import { CredentialsAPI, CredentialTypesAPI } from '@api';
import AnsibleSelect from '@components/AnsibleSelect';
import { FieldTooltip } from '@components/FormField';
import CredentialChip from '@components/CredentialChip';
import { getQSConfig, parseQueryString } from '@util/qs';
import Lookup from './Lookup';
import OptionsList from './shared/OptionsList';
const QS_CONFIG = getQSConfig('credentials', {
page: 1,
page_size: 5,
order_by: 'name',
});
async function loadCredentialTypes() {
const { data } = await CredentialTypesAPI.read();
const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
return data.results.filter(type => acceptableTypes.includes(type.kind));
}
async function loadCredentials(params, selectedCredentialTypeId) {
params.credential_type = selectedCredentialTypeId || 1;
const { data } = await CredentialsAPI.read(params);
return data;
}
function MultiCredentialsLookup(props) {
const { tooltip, value, onChange, onError, history, i18n } = props;
const [credentialTypes, setCredentialTypes] = useState([]);
const [selectedType, setSelectedType] = useState(null);
const [credentials, setCredentials] = useState([]);
const [credentialsCount, setCredentialsCount] = useState(0);
useEffect(() => {
(async () => {
try {
const types = await loadCredentialTypes();
setCredentialTypes(types);
const match = types.find(type => type.kind === 'ssh') || types[0];
setSelectedType(match);
} catch (err) {
onError(err);
}
})();
}, [onError]);
useEffect(() => {
(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, history.location.search, onError]);
const renderChip = ({ item, removeItem, canDelete }) => (
<CredentialChip
key={item.id}
onClick={() => removeItem(item)}
isReadOnly={!canDelete}
credential={item}
/>
);
const isMultiple = selectedType && selectedType.kind === 'vault';
return (
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
{tooltip && <FieldTooltip content={tooltip} />}
<Lookup
id="multiCredential"
header={i18n._(t`Credentials`)}
value={value}
multiple
onChange={onChange}
qsConfig={QS_CONFIG}
renderItemChip={renderChip}
renderOptionsList={({ state, dispatch, canDelete }) => {
return (
<Fragment>
{credentialTypes && credentialTypes.length > 0 && (
<ToolbarItem css=" display: flex; align-items: center;">
<div css="flex: 0 0 25%; margin-right: 32px">
{i18n._(t`Selected Category`)}
</div>
<AnsibleSelect
css="flex: 1 1 75%;"
id="multiCredentialsLookUp-select"
label={i18n._(t`Selected Category`)}
data={credentialTypes.map(type => ({
key: type.id,
value: type.id,
label: type.name,
isDisabled: false,
}))}
value={selectedType && selectedType.id}
onChange={(e, id) => {
setSelectedType(
credentialTypes.find(o => o.id === parseInt(id, 10))
);
}}
/>
</ToolbarItem>
)}
<OptionsList
value={state.selectedItems}
options={credentials}
optionCount={credentialsCount}
searchColumns={[
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
key: 'created_by__username',
},
{
name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username',
},
]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
multiple={isMultiple}
header={i18n._(t`Credentials`)}
name="credentials"
qsConfig={QS_CONFIG}
readOnly={!canDelete}
selectItem={item => {
if (isMultiple) {
return dispatch({ type: 'SELECT_ITEM', item });
}
const selectedItems = state.selectedItems.filter(
i => i.kind !== item.kind
);
selectedItems.push(item);
return dispatch({
type: 'SET_SELECTED_ITEMS',
selectedItems,
});
}}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
renderItemChip={renderChip}
/>
</Fragment>
);
}}
/>
</FormGroup>
);
}
MultiCredentialsLookup.propTypes = {
tooltip: PropTypes.string,
value: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
description: PropTypes.string,
kind: PropTypes.string,
clound: PropTypes.bool,
})
),
onChange: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
};
MultiCredentialsLookup.defaultProps = {
tooltip: '',
value: [],
};
export { MultiCredentialsLookup as _MultiCredentialsLookup };
export default withI18n()(withRouter(MultiCredentialsLookup));
/*
import React, { Fragment, 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 { ToolbarItem } from '@patternfly/react-core'; import { ToolbarItem } from '@patternfly/react-core';
import { CredentialsAPI, CredentialTypesAPI } from '@api'; import { CredentialsAPI, CredentialTypesAPI } from '@api';
import AnsibleSelect from '@components/AnsibleSelect'; import AnsibleSelect from '@components/AnsibleSelect';
import CredentialChip from '@components/CredentialChip'; import CredentialChip from '@components/CredentialChip';
import VerticalSeperator from '@components/VerticalSeparator';
import { getQSConfig, parseQueryString } from '@util/qs'; import { getQSConfig, parseQueryString } from '@util/qs';
import Lookup from './Lookup'; import Lookup from './Lookup';
import OptionsList from './shared/OptionsList'; import OptionsList from './shared/OptionsList';
@@ -293,8 +93,9 @@ function MultiCredentialsLookup(props) {
<Fragment> <Fragment>
{credentialTypes && credentialTypes.length > 0 && ( {credentialTypes && credentialTypes.length > 0 && (
<ToolbarItem css=" display: flex; align-items: center;"> <ToolbarItem css=" display: flex; align-items: center;">
<div css="flex: 0 0 25%;">{i18n._(t`Selected Category`)}</div> <div css="flex: 0 0 25%; margin-right: 32px">
<VerticalSeperator /> {i18n._(t`Selected Category`)}
</div>
<AnsibleSelect <AnsibleSelect
css="flex: 1 1 75%;" css="flex: 1 1 75%;"
id="multiCredentialsLookUp-select" id="multiCredentialsLookUp-select"
@@ -387,4 +188,3 @@ MultiCredentialsLookup.defaultProps = {
export { MultiCredentialsLookup as _MultiCredentialsLookup }; export { MultiCredentialsLookup as _MultiCredentialsLookup };
export default withI18n()(withRouter(MultiCredentialsLookup)); export default withI18n()(withRouter(MultiCredentialsLookup));
*/

View File

@@ -6,9 +6,12 @@ import { Formik, useField } from 'formik';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Form } from '@patternfly/react-core'; import { Form, FormGroup } from '@patternfly/react-core';
import FormField, { FormSubmitError } from '@components/FormField'; import FormField, {
FormSubmitError,
FieldTooltip,
} from '@components/FormField';
import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import { VariablesField } from '@components/CodeMirrorInput'; import { VariablesField } from '@components/CodeMirrorInput';
import { required } from '@util/validators'; import { required } from '@util/validators';
@@ -23,7 +26,7 @@ function HostFormFields({ host, i18n }) {
const hostAddMatch = useRouteMatch('/hosts/add'); const hostAddMatch = useRouteMatch('/hosts/add');
const inventoryFieldArr = useField({ const inventoryFieldArr = useField({
name: 'inventory', name: 'inventory',
validate: required(i18n._(t`Select aå value for this field`), i18n), validate: required(i18n._(t`Select a value for this field`), i18n),
}); });
const inventoryMeta = inventoryFieldArr[1]; const inventoryMeta = inventoryFieldArr[1];
const inventoryHelpers = inventoryFieldArr[2]; const inventoryHelpers = inventoryFieldArr[2];
@@ -45,22 +48,35 @@ function HostFormFields({ host, i18n }) {
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
/> />
{hostAddMatch && ( {hostAddMatch && (
<InventoryLookup <FormGroup
value={inventory} label={i18n._(t`Inventory`)}
onBlur={() => inventoryHelpers.setTouched()} isRequired
tooltip={i18n._( fieldId="inventory-lookup"
t`Select the inventory that this host will belong to.`
)}
isValid={!inventoryMeta.touched || !inventoryMeta.error} isValid={!inventoryMeta.touched || !inventoryMeta.error}
helperTextInvalid={inventoryMeta.error} helperTextInvalid={inventoryMeta.error}
onChange={value => { >
inventoryHelpers.setValuealue(value.id); <FieldTooltip
setInventory(value); content={i18n._(
}} t`Select the inventory that this host will belong to.`
required )}
touched={inventoryMeta.touched} />
error={inventoryMeta.error} <InventoryLookup
/> value={inventory}
onBlur={() => inventoryHelpers.setTouched()}
tooltip={i18n._(
t`Select the inventory that this host will belong to.`
)}
isValid={!inventoryMeta.touched || !inventoryMeta.error}
helperTextInvalid={inventoryMeta.error}
onChange={value => {
inventoryHelpers.setValue(value.id);
setInventory(value);
}}
required
touched={inventoryMeta.touched}
error={inventoryMeta.error}
/>
</FormGroup>
)} )}
<FormFullWidthLayout> <FormFullWidthLayout>
<VariablesField <VariablesField
@@ -122,139 +138,3 @@ HostForm.defaultProps = {
export { HostForm as _HostForm }; export { HostForm as _HostForm };
export default withI18n()(HostForm); export default withI18n()(HostForm);
/*
import React, { useState } from 'react';
import { func, shape } from 'prop-types';
import { useRouteMatch } from 'react-router-dom';
import { Formik, Field } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Form, FormGroup } from '@patternfly/react-core';
import FormRow from '@components/FormRow';
import FormField, {
FormSubmitError,
FieldTooltip,
} from '@components/FormField';
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import { VariablesField } from '@components/CodeMirrorInput';
import { required } from '@util/validators';
import { InventoryLookup } from '@components/Lookup';
function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
const [inventory, setInventory] = useState(
host ? host.summary_fields.inventory : ''
);
const hostAddMatch = useRouteMatch('/hosts/add');
return (
<Formik
initialValues={{
name: host.name,
description: host.description,
inventory: host.inventory || '',
variables: host.variables,
}}
onSubmit={handleSubmit}
>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow>
<FormField
id="host-name"
name="name"
type="text"
label={i18n._(t`Name`)}
validate={required(null, i18n)}
isRequired
/>
<FormField
id="host-description"
name="description"
type="text"
label={i18n._(t`Description`)}
/>
{hostAddMatch && (
<Field
name="inventory"
validate={required(
i18n._(t`Select a value for this field`),
i18n
)}
>
{({ form }) => (
<FormGroup
label={i18n._(t`Inventory`)}
isRequired
fieldId="inventory-lookup"
isValid={!form.touched.inventory || !form.errors.inventory}
helperTextInvalid={form.errors.inventory}
>
<FieldTooltip
content={i18n._(
t`Select the inventory that this host will belong to.`
)}
/>
<InventoryLookup
value={inventory}
onBlur={() => form.setFieldTouched('inventory')}
onChange={value => {
form.setFieldValue('inventory', value.id);
setInventory(value);
}}
required
touched={form.touched.inventory}
error={form.errors.inventory}
/>
</FormGroup>
)}
</Field>
)}
</FormRow>
<FormRow>
<VariablesField
id="host-variables"
name="variables"
label={i18n._(t`Variables`)}
/>
</FormRow>
<FormSubmitError error={submitError} />
<FormActionGroup
onCancel={handleCancel}
onSubmit={formik.handleSubmit}
/>
</Form>
)}
</Formik>
);
}
HostForm.propTypes = {
handleSubmit: func.isRequired,
handleCancel: func.isRequired,
host: shape({}),
submitError: shape({}),
};
HostForm.defaultProps = {
host: {
name: '',
description: '',
inventory: undefined,
variables: '---\n',
summary_fields: {
inventory: null,
},
},
submitError: null,
};
export { HostForm as _HostForm };
export default withI18n()(HostForm);
*/

File diff suppressed because it is too large Load Diff