/* eslint-disable max-lines */
/* eslint-disable camelcase */
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { Wizard } from '@patternfly/react-core';
import { get } from 'foremanReact/redux/API';
import history from 'foremanReact/history';
import {
useForemanOrganization,
useForemanLocation,
} from 'foremanReact/Root/Context/ForemanContext';
import CategoryAndTemplate from './steps/CategoryAndTemplate/';
import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
import {
JOB_TEMPLATE,
WIZARD_TITLES,
SCHEDULE_TYPES,
initialScheduleState,
} from './JobWizardConstants';
import {
selectTemplateError,
selectJobTemplate,
selectIsSubmitting,
selectRouterSearch,
selectIsLoading,
selectJobCategoriesResponse,
} from './JobWizardSelectors';
import { ScheduleType } from './steps/Schedule/ScheduleType';
import { ScheduleFuture } from './steps/Schedule/ScheduleFuture';
import { ScheduleRecurring } from './steps/Schedule/ScheduleRecurring';
import HostsAndInputs from './steps/HostsAndInputs/';
import ReviewDetails from './steps/ReviewDetails/';
import { useValidation } from './validation';
import { useAutoFill } from './autofill';
import { submit } from './submit';
import { generateDefaultDescription } from './JobWizardHelpers';
import './JobWizard.scss';
export const JobWizard = ({ rerunData }) => {
const jobCategoriesResponse = useSelector(selectJobCategoriesResponse);
const [jobTemplateID, setJobTemplateID] = useState(
rerunData?.template_invocations?.[0]?.template_id ||
jobCategoriesResponse?.default_template
);
const [category, setCategory] = useState(
rerunData?.job_category || jobCategoriesResponse?.default_category || ''
);
const [advancedValues, setAdvancedValues] = useState({ templateValues: {} });
const [templateValues, setTemplateValues] = useState({}); // TODO use templateValues in advanced fields - description https://github.com/theforeman/foreman_remote_execution/pull/605
const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
const [selectedTargets, setSelectedTargets] = useState({
hosts: [],
hostCollections: [],
hostGroups: [],
});
const [hostsSearchQuery, setHostsSearchQuery] = useState('');
const routerSearch = useSelector(selectRouterSearch);
const [fills, setFills] = useState(
rerunData
? {
search: rerunData?.targeting?.search_query,
...rerunData.inputs,
...routerSearch,
}
: routerSearch
);
const dispatch = useDispatch();
const setDefaults = useCallback(
({
data: {
template_inputs = [],
advanced_template_inputs = [],
effective_user,
job_template: {
name,
execution_timeout_interval,
time_to_pickup,
description_format,
job_category,
},
randomized_ordering,
ssh_user,
concurrency_control = {},
},
}) => {
if (category !== job_category) {
setCategory(job_category);
}
const advancedTemplateValues = {};
const defaultTemplateValues = {};
const inputs = template_inputs;
const advancedInputs = advanced_template_inputs;
if (inputs) {
setTemplateValues(prev => {
inputs.forEach(input => {
defaultTemplateValues[input.name] =
prev[input.name] || input?.default || '';
});
return defaultTemplateValues;
});
}
setAdvancedValues(currentAdvancedValues => {
if (advancedInputs) {
advancedInputs.forEach(input => {
advancedTemplateValues[input.name] =
currentAdvancedValues[input.name] || input?.default || '';
});
}
return {
...currentAdvancedValues,
effectiveUserValue: effective_user?.value || '',
timeoutToKill: execution_timeout_interval || '',
timeToPickup: time_to_pickup || '',
templateValues: advancedTemplateValues,
description:
generateDefaultDescription({
description_format,
advancedInputs,
inputs,
name,
}) || '',
isRandomizedOrdering: randomized_ordering,
sshUser: ssh_user || '',
timeSpan: concurrency_control.time_span || '',
concurrencyLevel: concurrency_control.level || '',
};
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[category.length]
);
useEffect(() => {
if (rerunData) {
setDefaults({
data: {
effective_user: {
value: rerunData.template_invocations[0].effective_user,
},
job_template: {
execution_timeout_interval: rerunData.execution_timeout_interval,
description_format: rerunData.description_format,
job_category: rerunData.job_category,
time_to_pickup: rerunData.time_to_pickup,
},
randomized_ordering: rerunData.targeting.randomized_ordering,
ssh_user: rerunData.ssh_user,
concurrency_control: rerunData.concurrency_control,
},
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rerunData]);
useEffect(() => {
if (jobTemplateID) {
dispatch(
get({
key: JOB_TEMPLATE,
url: `/ui_job_wizard/template/${jobTemplateID}`,
handleSuccess: rerunData
? ({
data: {
template_inputs = [],
advanced_template_inputs = [],
job_template: { name, description_format },
},
}) => {
setAdvancedValues(currentAdvancedValues => ({
...currentAdvancedValues,
description:
generateDefaultDescription({
description_format,
advancedInputs: advanced_template_inputs,
inputs: template_inputs,
name,
}) || '',
}));
}
: setDefaults,
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rerunData, jobTemplateID, dispatch]);
const [valid, setValid] = useValidation({
advancedValues,
templateValues,
});
useAutoFill({
fills,
setFills,
setSelectedTargets,
setHostsSearchQuery,
setJobTemplateID,
setTemplateValues,
setAdvancedValues,
});
const templateError = !!useSelector(selectTemplateError);
const templateResponse = useSelector(selectJobTemplate);
const isSubmitting = useSelector(selectIsSubmitting);
const isTemplatesLoading = useSelector(state =>
selectIsLoading(state, JOB_TEMPLATE)
);
const isTemplate =
!isTemplatesLoading &&
!templateError &&
!!jobTemplateID &&
templateResponse.job_template;
const steps = [
{
name: WIZARD_TITLES.categoryAndTemplate,
component: (
),
enableNext: isTemplate,
},
{
name: WIZARD_TITLES.hostsAndInputs,
component: (
),
canJumpTo: isTemplate,
enableNext: isTemplate && valid.hostsAndInputs,
},
{
name: WIZARD_TITLES.advanced,
component: (
{
setAdvancedValues(currentAdvancedValues => ({
...currentAdvancedValues,
...newValues,
}));
}}
templateValues={templateValues}
/>
),
canJumpTo: isTemplate && valid.hostsAndInputs,
enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
},
{
name: WIZARD_TITLES.schedule,
canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
enableNext:
isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
steps: [
{
name: WIZARD_TITLES.typeOfExecution,
component: (
{
setValid(currentValid => ({
...currentValid,
schedule: newValue,
}));
}}
/>
),
canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
},
...(scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE
? [
{
name: SCHEDULE_TYPES.FUTURE,
component: (
{
setValid(currentValid => ({
...currentValid,
schedule: newValue,
}));
}}
/>
),
canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
enableNext:
isTemplate &&
valid.hostsAndInputs &&
valid.advanced &&
valid.schedule,
},
]
: []),
...(scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING
? [
{
name: SCHEDULE_TYPES.RECURRING,
component: (
{
setValid(currentValid => ({
...currentValid,
schedule: newValue,
}));
}}
/>
),
canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
enableNext:
isTemplate &&
valid.hostsAndInputs &&
valid.advanced &&
valid.schedule,
},
]
: []),
],
},
{
name: WIZARD_TITLES.review,
component: (
),
nextButtonText: 'Run',
canJumpTo:
isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
enableNext:
isTemplate &&
valid.hostsAndInputs &&
valid.advanced &&
valid.schedule &&
!isSubmitting,
},
];
const location = useForemanLocation();
const organization = useForemanOrganization();
return (
history.goBack()}
navAriaLabel="Run Job steps"
steps={steps}
height="100%"
className="job-wizard"
onSave={() => {
submit({
jobTemplateID,
templateValues,
advancedValues,
scheduleValue,
dispatch,
selectedTargets,
hostsSearchQuery,
location,
organization,
});
}}
/>
);
};
JobWizard.propTypes = {
rerunData: PropTypes.shape({
job_category: PropTypes.string,
targeting: PropTypes.shape({
search_query: PropTypes.string,
targeting_type: PropTypes.string,
randomized_ordering: PropTypes.bool,
}),
triggering: PropTypes.shape({
mode: PropTypes.string,
start_at: PropTypes.string,
start_before: PropTypes.string,
}),
ssh_user: PropTypes.string,
concurrency_control: PropTypes.shape({
level: PropTypes.number,
time_span: PropTypes.number,
}),
execution_timeout_interval: PropTypes.number,
time_to_pickup: PropTypes.number,
remote_execution_feature_id: PropTypes.string,
template_invocations: PropTypes.arrayOf(
PropTypes.shape({
template_id: PropTypes.number,
effective_user: PropTypes.string,
input_values: PropTypes.array,
})
),
inputs: PropTypes.object,
description_format: PropTypes.string,
}),
};
JobWizard.defaultProps = {
rerunData: null,
};
export default JobWizard;