app/javascript/components/ansible-playbook-edit-catalog-form/helper.js
import { useFieldApi, useFormApi } from '@@ddf';
import React from 'react';
import { Button, TextInput, Checkbox } from 'carbon-components-react';
import { TrashCan32 } from '@carbon/icons-react';
import PropTypes from 'prop-types';
import { TreeViewRedux } from '../tree-view';
/** Helper function to convert object into array used for extra vars restructuring */
const convertObject = (inputObj) => {
const outputArray = Object.entries(inputObj).map(([key, value]) => ({ key, value: value.default }));
return outputArray;
};
/** Helper function used for restructuring extra vars */
const refactorExtraVars = (catalogData) => {
if (Object.prototype.hasOwnProperty.call(catalogData.config_info.provision, 'extra_vars')) {
catalogData.config_info.provision.extra_vars = convertObject(catalogData.config_info.provision.extra_vars);
}
if (Object.prototype.hasOwnProperty.call(catalogData.config_info.retirement, 'extra_vars')) {
catalogData.config_info.retirement.extra_vars = convertObject(catalogData.config_info.retirement.extra_vars);
}
return catalogData;
};
/** Helper function to convert array into object used for extra vars restructuring */
const convertArrayToObject = (arr) => arr.reduce((acc, curr) => {
acc[curr.key] = { default: curr.value };
return acc;
}, {});
/** get tenantId from key */
const getTenantId = (key) => {
if (key.startsWith('tn')) {
return key.split('-')[1];
}
return undefined;
};
const getSortedHash = (inputHash) => {
const sortedHash = Object.keys(inputHash)
.map((key) => ({ k: key, v: inputHash[key] }))
.sort((a, b) => a.v.localeCompare(b.v))
.reduce((o, e) => {
o[e.k] = e.v;
return o;
}, {});
return sortedHash;
};
/** Helper function to get various log types */
export const getLogOutputTypes = () => ({
on_error: __('On Error'),
always: __('Always'),
never: __('Never'),
});
/** Helper function to get various verbose types */
export const getVerbosityTypes = () => ({
0: '0 (Normal)',
1: '1 (Verbose)',
2: '2 (More Verbose)',
3: '3 (Debug)',
4: '4 (Connection Debug)',
5: '5 (WinRM Debug)',
});
/** Helper function to get list of cloud types */
export const formCloudTypes = (data) => {
const cloudTypes = {};
const embeddedAnsibleCredentialTypes = data.data.credential_types.embedded_ansible_credential_types;
Object.keys(embeddedAnsibleCredentialTypes).forEach((credType) => {
const credObject = embeddedAnsibleCredentialTypes[credType];
if (credObject.type === 'cloud') {
cloudTypes[credType] = credObject.label;
}
});
return getSortedHash(cloudTypes);
};
/** Helper function to append tenant in front of catalog name */
const formOptsCatalogTenants = (catalogs) => catalogs.map((catalog) => ({
name: catalog[0],
id: catalog[1].toString(),
}));
/** edit the name of each catalog to get all tenant ancestors in the name if they exist */
export const restructureAvailableCatalogs = (availableCatalogs, allCatalogs) => {
const availableCatalogsAfterRestructure = availableCatalogs.map((catalog) => ({
...catalog,
name: _.find(formOptsCatalogTenants(allCatalogs), { id: catalog.id }).name,
}));
return availableCatalogsAfterRestructure;
};
/** button component used as a mapper to copy the provision details into retirement */
export const CopyFromProvisonButton = (props) => {
const { label, copyFrom, copyTo } = useFieldApi(props);
const formOptions = useFormApi();
const handleClick = () => {
copyFrom.forEach((fromItem, i) => {
const destination = `config_info.retirement.${copyTo[i]}`;
formOptions.change(destination, formOptions.getState().values.config_info.provision[fromItem]);
});
};
return (
<div>
<Button kind="secondary" onClick={handleClick}>{label}</Button>
</div>
);
};
/** conditional checkbox mapper component to render escalation privilege field */
export const conditionalCheckbox = (props) => {
const {
input, label, id, display,
} = useFieldApi(props);
const formOptions = useFormApi();
const isChecked = (!!input.value);
const onChange = (_evt) => {
formOptions.change(input.name, !input.value);
};
if (display) {
return (
<Checkbox
id={id}
name={id}
labelText={label}
checked={isChecked}
onChange={onChange}
/>
);
}
return <></>;
};
/** wrapper component to show the tenants tree structure */
export const TreeViewReduxWrapper = (props) => {
const propsData = useFieldApi(props);
if (props.roleAllows) {
return (
<div>
<label htmlFor={propsData.input.name} className="bx--label">{propsData.label}</label>
<br />
<TreeViewRedux {...propsData} />
</div>
);
}
return (<></>);
};
TreeViewReduxWrapper.propTypes = {
roleAllows: PropTypes.bool.isRequired,
};
/** component used as a mapper to include the key value pairs ofr extra vars */
export const KeyValueListComponent = (props) => {
const {
input, label, keyLabel, valueLabel,
} = useFieldApi(props);
const formOptions = useFormApi();
const addPair = () => {
const newPairs = [...input.value, { key: '', value: '' }];
formOptions.change(input.name, newPairs);
};
const deletePair = (index) => {
const newPairs = [...input.value];
newPairs.splice(index, 1);
formOptions.change(input.name, newPairs);
};
const updatePair = (index, key, value) => {
const newPairs = [...input.value];
newPairs[index] = { key, value };
formOptions.change(input.name, newPairs);
};
return (
<div className="key-value-list-component-wrapper">
<label htmlFor={input.name} className="bx--label">{label}</label>
<br />
{input.value && input.value.map((pair, index) => (
// eslint-disable-next-line react/no-array-index-key
<div key={index} className="key-value-list-pair">
<TextInput
id={`${input.name}.${index}.key`}
labelText={keyLabel}
value={pair.key}
onChange={(event) => updatePair(index, event.target.value, pair.value)}
/>
<TextInput
id={`${input.name}.${index}.value`}
labelText={valueLabel}
value={pair.value}
onChange={(event) => updatePair(index, pair.key, event.target.value)}
/>
<Button
hasIconOnly
kind="danger"
className="key-value-delete"
renderIcon={TrashCan32}
iconDescription="Delete Key-Value Pair"
onClick={() => deletePair(index)}
/>
</div>
))}
<Button kind="secondary" onClick={addPair}>{__('Add Key-Value Pair')}</Button>
</div>
);
};
/** Helper function to prepare the request object for both edit and create */
export const prepareRequestObject = (values, formId) => {
const requestObject = { ...values };
// if price property is not there add price property if its present convert the value into string
if (!Object.prototype.hasOwnProperty.call(requestObject, 'price')) {
requestObject.price = '';
} else {
requestObject.price = requestObject.price != null ? requestObject.price.toString() : '';
}
// add currency_id field if its not empty and also make the price as "" when currency_id field is not present
if (!Object.prototype.hasOwnProperty.call(requestObject, 'currency_id')) {
requestObject.currency_id = '';
requestObject.price = '';
}
// adding addition_tennant_ids after fetching from tree redux
// eslint-disable-next-line no-undef
const newIds = miqGetSelectedKeys(ManageIQ.redux.store.getState().tenants_tree).map(getTenantId);
if (newIds) {
requestObject.additional_tenant_ids = newIds.sort();
}
// adding the correct hosts value and delete the specify_host_type field
if (requestObject.config_info.provision.specify_host_type === 'localhost') {
requestObject.config_info.provision.hosts = 'localhost';
}
if (requestObject.config_info.retirement.specify_host_type === 'localhost') {
requestObject.config_info.retirement.hosts = 'localhost';
}
delete requestObject.config_info.provision.specify_host_type;
delete requestObject.config_info.retirement.specify_host_type;
// restructuring the extra_vars to convert it from array to object with key value pairs
if (requestObject.config_info.provision.extra_vars) {
requestObject.config_info.provision.extra_vars = convertArrayToObject(requestObject.config_info.provision.extra_vars);
}
if (requestObject.config_info.retirement.extra_vars) {
requestObject.config_info.retirement.extra_vars = convertArrayToObject(requestObject.config_info.retirement.extra_vars);
}
// delete the dialog_id_type field
delete requestObject.config_info.provision.dialog_type;
// delete cloud_type field for both provision and retirement
delete requestObject.config_info.provision.cloud_type;
delete requestObject.config_info.retirement.cloud_type;
// refactor the remove_resources filed
if (!Object.prototype.hasOwnProperty.call(requestObject.config_info.retirement, 'repository_id')) {
requestObject.config_info.retirement.remove_resources = requestObject.config_info.retirement.remove_resources_with_no_repistory_id;
delete requestObject.config_info.retirement.remove_resources_with_no_repistory_id;
}
if (formId === 'new') {
return { ...requestObject, type: 'ServiceTemplateAnsiblePlaybook', prov_type: 'generic_ansible_playbook' };
}
return requestObject;
};
/** Helper function to prepare the restructure data to show fields in DDF form */
export const restructureCatalogData = function(catalogData, provisionCloudType, retirementCloudType) {
let restructuredCatalogData = { ...catalogData };
// adding specify_host_type field for both provision and retirement data
if (Object.prototype.hasOwnProperty.call(restructuredCatalogData.config_info.provision, 'hosts')) {
restructuredCatalogData.config_info.provision.specify_host_type = 'localhost';
if (restructuredCatalogData.config_info.provision.hosts !== 'localhost') {
restructuredCatalogData.config_info.provision.specify_host_type = 'specify';
}
if (restructuredCatalogData.config_info.provision.hosts === 'localhost') {
restructuredCatalogData.config_info.provision.hosts = '';
}
}
if (Object.prototype.hasOwnProperty.call(restructuredCatalogData.config_info.retirement, 'hosts')) {
restructuredCatalogData.config_info.retirement.specify_host_type = 'localhost';
if (restructuredCatalogData.config_info.retirement.hosts !== 'localhost') {
restructuredCatalogData.config_info.retirement.specify_host_type = 'specify';
}
if (restructuredCatalogData.config_info.retirement.hosts === 'localhost') {
restructuredCatalogData.config_info.retirement.hosts = '';
}
}
// adding dialog_type field for provision data
if (Object.prototype.hasOwnProperty.call(restructuredCatalogData.config_info.provision, 'dialog_id')) {
restructuredCatalogData.config_info.provision.dialog_type = 'useExisting';
}
// adding cloud_type field for both provision and retirement
restructuredCatalogData.config_info.provision.cloud_type = provisionCloudType;
restructuredCatalogData.config_info.retirement.cloud_type = retirementCloudType;
// refactor the extra vars field for provision and retirement
restructuredCatalogData = refactorExtraVars(restructuredCatalogData);
// add config_info.retirement.remove_resources_with_no_repistory_id if repiostry_id is empty
// also copy the config_info.retirement.remove_resources into the newly added field of above
if (!Object.prototype.hasOwnProperty.call(restructuredCatalogData.config_info.retirement, 'repository_id')) {
// eslint-disable-next-line max-len
restructuredCatalogData.config_info.retirement.remove_resources_with_no_repistory_id = restructuredCatalogData.config_info.retirement.remove_resources;
restructuredCatalogData.config_info.retirement.remove_resources = 'no_with_playbook';
} else {
restructuredCatalogData.config_info.retirement.remove_resources_with_no_repistory_id = 'no_without_playbook';
}
return restructuredCatalogData;
};
/** Helper function to prepare the data to show fields in DDF form for create */
export const formCatalogData = () => {
const catalogData = {
name: '',
description: '',
service_template_id: '',
display: false,
service_template_catalog_id: '',
long_description: '',
zone_id: '',
currency_id: '',
price: '',
config_info: {
provision: {
repository_id: '',
hosts: '',
verbosity: '0',
log_output: 'on_error',
extra_vars: [],
execution_ttl: '',
become_enabled: false,
dialog_id: '',
specify_host_type: 'localhost',
dialog_type: 'useExisting',
},
retirement: {
remove_resources: 'no_with_playbook',
verbosity: '0',
log_output: 'on_error',
cloud_type: '',
remove_resources_with_no_repistory_id: 'yes_without_playbook',
hosts: '',
specify_host_type: 'localhost',
},
},
};
return catalogData;
};