Refactors EE Lookup to support prompting. Adds prompting for EE to JT form

Adds prompt on launch buttons to labels, forks, job slicing, timeout, and instance groups

Adds prompting for labels on workflow job template

Updates flags that denote when prompting is necessary in various places

Adds prompting support for timeout, job slicing, forks, labels, instance groups and execution environments to the prompt details

Show prompted ee, forks, job slice and labels on schedule details

Adds support for ee, labels, forks, job slicing and timeout prompting to the node view modal

Add default values when prompting for ee's, forks, job slicing and timeout

Adds launch prompt step for execution environments

Adds fields for timeout, job slicing and forks to other prompts step of launch
This commit is contained in:
mabashian
2022-08-04 11:32:17 -04:00
committed by Alan Rominger
parent d67aef9d8e
commit 04d0e3915c
16 changed files with 604 additions and 97 deletions

View File

@@ -24,6 +24,12 @@ function canLaunchWithoutPrompt(launchData) {
!launchData.ask_variables_on_launch && !launchData.ask_variables_on_launch &&
!launchData.ask_limit_on_launch && !launchData.ask_limit_on_launch &&
!launchData.ask_scm_branch_on_launch && !launchData.ask_scm_branch_on_launch &&
!launchData.ask_execution_environment_on_launch &&
!launchData.ask_labels_on_launch &&
!launchData.ask_forks_on_launch &&
!launchData.ask_job_slicing_on_launch &&
!launchData.ask_timeout_on_launch &&
!launchData.ask_instance_groups_on_launch &&
!launchData.survey_enabled && !launchData.survey_enabled &&
(!launchData.passwords_needed_to_start || (!launchData.passwords_needed_to_start ||
launchData.passwords_needed_to_start.length === 0) && launchData.passwords_needed_to_start.length === 0) &&

View File

@@ -0,0 +1,116 @@
import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import { ExecutionEnvironmentsAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import OptionsList from '../../OptionsList';
import ContentLoading from '../../ContentLoading';
import ContentError from '../../ContentError';
const QS_CONFIG = getQSConfig('execution_environment', {
page: 1,
page_size: 5,
});
function ExecutionEnvironmentStep() {
const [field, , helpers] = useField('execution_environment');
const history = useHistory();
const {
isLoading,
error,
result: {
execution_environments,
count,
relatedSearchableKeys,
searchableKeys,
},
request: fetchExecutionEnvironments,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search);
const [{ data }, actionsResponse] = await Promise.all([
ExecutionEnvironmentsAPI.read(params),
ExecutionEnvironmentsAPI.readOptions(),
]);
return {
execution_environments: data.results,
count: data.count,
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location]),
{
count: 0,
execution_environments: [],
relatedSearchableKeys: [],
searchableKeys: [],
}
);
useEffect(() => {
fetchExecutionEnvironments();
}, [fetchExecutionEnvironments]);
if (isLoading) {
return <ContentLoading />;
}
if (error) {
return <ContentError error={error} />;
}
return (
<OptionsList
value={field.value ? [field.value] : []}
options={execution_environments}
optionCount={count}
columns={[
{
name: t`Name`,
key: 'name',
},
{
name: t`Image`,
key: 'image',
},
]}
searchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Image`,
key: 'image__icontains',
},
]}
sortColumns={[
{
name: t`Name`,
key: 'name',
},
{
name: t`Image`,
key: 'image',
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
header={t`Execution Environments`}
name="execution_environment"
qsConfig={QS_CONFIG}
readOnly
selectItem={helpers.setValue}
deselectItem={() => field.onChange(null)}
/>
);
}
export default ExecutionEnvironmentStep;

View File

@@ -29,6 +29,23 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
}} }}
> >
{launchConfig.ask_job_type_on_launch && <JobTypeField />} {launchConfig.ask_job_type_on_launch && <JobTypeField />}
{launchConfig.ask_scm_branch_on_launch && (
<FormField
id="prompt-scm-branch"
name="scm_branch"
label={t`Source Control Branch`}
tooltip={t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch`}
/>
)}
{launchConfig.ask_forks_on_launch && (
<FormField
id="prompt-forks"
name="forks"
label={t`Forks`}
type="number"
min="0"
/>
)}
{launchConfig.ask_limit_on_launch && ( {launchConfig.ask_limit_on_launch && (
<FormField <FormField
id="prompt-limit" id="prompt-limit"
@@ -40,15 +57,25 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
information and examples on patterns.`} information and examples on patterns.`}
/> />
)} )}
{launchConfig.ask_scm_branch_on_launch && ( {launchConfig.ask_verbosity_on_launch && <VerbosityField />}
{launchConfig.ask_job_slicing_on_launch && (
<FormField <FormField
id="prompt-scm-branch" id="prompt-job-slicing"
name="scm_branch" name="job_slice_count"
label={t`Source Control Branch`} label={t`Job Slicing`}
tooltip={t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch`} type="number"
min="1"
/>
)}
{launchConfig.ask_timeout_on_launch && (
<FormField
id="prompt-timeout"
name="timeout"
label={t`Timeout`}
type="number"
min="0"
/> />
)} )}
{launchConfig.ask_verbosity_on_launch && <VerbosityField />}
{launchConfig.ask_diff_mode_on_launch && <ShowChangesToggle />} {launchConfig.ask_diff_mode_on_launch && <ShowChangesToggle />}
{launchConfig.ask_tags_on_launch && ( {launchConfig.ask_tags_on_launch && (
<TagField <TagField

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { t } from '@lingui/macro';
import ExecutionEnvironmentStep from './ExecutionEnvironmentStep';
import StepName from './StepName';
const STEP_ID = 'executionEnvironment';
export default function useExecutionEnvironmentStep(launchConfig, resource) {
return {
step: getStep(launchConfig, resource),
initialValues: getInitialValues(launchConfig, resource),
isReady: true,
contentError: null,
hasError: false,
setTouched: (setFieldTouched) => {
setFieldTouched('execution_environment', true, false);
},
validate: () => {},
};
}
function getStep(launchConfig) {
if (!launchConfig.ask_inventory_on_launch) {
return null;
}
return {
id: STEP_ID,
name: (
<StepName id="execution-environment-step">
{t`Execution Environment`}
</StepName>
),
component: <ExecutionEnvironmentStep />,
enableNext: true,
};
}
function getInitialValues(launchConfig, resource) {
if (!launchConfig.ask_execution_environment_on_launch) {
return {};
}
return {
inventory: resource?.summary_fields?.execution_environment || null,
};
}

View File

@@ -27,6 +27,10 @@ const FIELD_NAMES = [
'job_tags', 'job_tags',
'skip_tags', 'skip_tags',
'extra_vars', 'extra_vars',
'labels',
'timeout',
'job_slice_count',
'forks',
]; ];
export default function useOtherPromptsStep(launchConfig, resource) { export default function useOtherPromptsStep(launchConfig, resource) {
@@ -105,7 +109,11 @@ function shouldShowPrompt(launchConfig) {
launchConfig.ask_skip_tags_on_launch || launchConfig.ask_skip_tags_on_launch ||
launchConfig.ask_variables_on_launch || launchConfig.ask_variables_on_launch ||
launchConfig.ask_scm_branch_on_launch || launchConfig.ask_scm_branch_on_launch ||
launchConfig.ask_diff_mode_on_launch launchConfig.ask_diff_mode_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_timeout_on_launch
); );
} }
@@ -140,5 +148,14 @@ function getInitialValues(launchConfig, resource) {
if (launchConfig.ask_diff_mode_on_launch) { if (launchConfig.ask_diff_mode_on_launch) {
initialValues.diff_mode = resource?.diff_mode || false; initialValues.diff_mode = resource?.diff_mode || false;
} }
if (launchConfig.ask_forks_on_launch) {
initialValues.forks = resource?.forks || 0;
}
if (launchConfig.ask_job_slicing_on_launch) {
initialValues.job_slice_count = resource?.job_slice_count || 1;
}
if (launchConfig.ask_timeout_on_launch) {
initialValues.timeout = resource?.timeout || 0;
}
return initialValues; return initialValues;
} }

View File

@@ -3,6 +3,7 @@ import { useFormikContext } from 'formik';
import useInventoryStep from './steps/useInventoryStep'; import useInventoryStep from './steps/useInventoryStep';
import useCredentialsStep from './steps/useCredentialsStep'; import useCredentialsStep from './steps/useCredentialsStep';
import useCredentialPasswordsStep from './steps/useCredentialPasswordsStep'; import useCredentialPasswordsStep from './steps/useCredentialPasswordsStep';
import useExecutionEnvironmentStep from './steps/useExecutionEnvironmentStep';
import useOtherPromptsStep from './steps/useOtherPromptsStep'; import useOtherPromptsStep from './steps/useOtherPromptsStep';
import useSurveyStep from './steps/useSurveyStep'; import useSurveyStep from './steps/useSurveyStep';
import usePreviewStep from './steps/usePreviewStep'; import usePreviewStep from './steps/usePreviewStep';
@@ -56,6 +57,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
showCredentialPasswordsStep(launchConfig, formikValues.credentials), showCredentialPasswordsStep(launchConfig, formikValues.credentials),
visited visited
), ),
useExecutionEnvironmentStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource), useOtherPromptsStep(launchConfig, resource),
useSurveyStep(launchConfig, surveyConfig, resource, visited), useSurveyStep(launchConfig, surveyConfig, resource, visited),
]; ];
@@ -143,6 +145,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
inventory: true, inventory: true,
credentials: true, credentials: true,
credentialPasswords: true, credentialPasswords: true,
executionEnvironment: true,
other: true, other: true,
survey: true, survey: true,
preview: true, preview: true,

View File

@@ -10,9 +10,9 @@ import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
import useRequest from 'hooks/useRequest'; import useRequest from 'hooks/useRequest';
import Popover from '../Popover'; import Popover from '../Popover';
import OptionsList from '../OptionsList'; import OptionsList from '../OptionsList';
import Lookup from './Lookup'; import Lookup from './Lookup';
import LookupErrorMessage from './shared/LookupErrorMessage'; import LookupErrorMessage from './shared/LookupErrorMessage';
import FieldWithPrompt from '../FieldWithPrompt';
const QS_CONFIG = getQSConfig('execution_environments', { const QS_CONFIG = getQSConfig('execution_environments', {
page: 1, page: 1,
@@ -36,6 +36,9 @@ function ExecutionEnvironmentLookup({
value, value,
fieldName, fieldName,
overrideLabel, overrideLabel,
isPromptableField,
promptId,
promptName,
}) { }) {
const location = useLocation(); const location = useLocation();
const { const {
@@ -150,49 +153,52 @@ function ExecutionEnvironmentLookup({
}, [fetchExecutionEnvironments]); }, [fetchExecutionEnvironments]);
const renderLookup = () => ( const renderLookup = () => (
<Lookup <>
id={id} <Lookup
header={t`Execution Environment`} id={id}
value={value} header={t`Execution Environment`}
onBlur={onBlur} value={value}
onChange={onChange} onBlur={onBlur}
onUpdate={fetchExecutionEnvironments} onChange={onChange}
onDebounce={checkExecutionEnvironmentName} onUpdate={fetchExecutionEnvironments}
fieldName={fieldName} onDebounce={checkExecutionEnvironmentName}
validate={validate} fieldName={fieldName}
qsConfig={QS_CONFIG} validate={validate}
isLoading={isLoading || isProjectLoading} qsConfig={QS_CONFIG}
isDisabled={isDisabled} isLoading={isLoading || isProjectLoading}
renderOptionsList={({ state, dispatch, canDelete }) => ( isDisabled={isDisabled}
<OptionsList renderOptionsList={({ state, dispatch, canDelete }) => (
value={state.selectedItems} <OptionsList
options={executionEnvironments} value={state.selectedItems}
optionCount={count} options={executionEnvironments}
searchColumns={[ optionCount={count}
{ searchColumns={[
name: t`Name`, {
key: 'name__icontains', name: t`Name`,
isDefault: true, key: 'name__icontains',
}, isDefault: true,
]} },
sortColumns={[ ]}
{ sortColumns={[
name: t`Name`, {
key: 'name', name: t`Name`,
}, key: 'name',
]} },
searchableKeys={searchableKeys} ]}
relatedSearchableKeys={relatedSearchableKeys} searchableKeys={searchableKeys}
multiple={state.multiple} relatedSearchableKeys={relatedSearchableKeys}
header={t`Execution Environment`} multiple={state.multiple}
name="executionEnvironments" header={t`Execution Environment`}
qsConfig={QS_CONFIG} name="executionEnvironments"
readOnly={!canDelete} qsConfig={QS_CONFIG}
selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })} readOnly={!canDelete}
deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
/> deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
)} />
/> )}
/>
<LookupErrorMessage error={error || fetchProjectError} />
</>
); );
const renderLabel = () => { const renderLabel = () => {
@@ -202,7 +208,21 @@ function ExecutionEnvironmentLookup({
return t`Execution Environment`; return t`Execution Environment`;
}; };
return ( return isPromptableField ? (
<FieldWithPrompt
fieldId={id}
label={renderLabel()}
promptId={promptId}
promptName={promptName}
tooltip={popoverContent}
>
{tooltip && isDisabled ? (
<Tooltip content={tooltip}>{renderLookup()}</Tooltip>
) : (
renderLookup()
)}
</FieldWithPrompt>
) : (
<FormGroup <FormGroup
fieldId={id} fieldId={id}
label={renderLabel()} label={renderLabel()}

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { arrayOf, string, func, bool } from 'prop-types'; import { arrayOf, string, func, bool } from 'prop-types';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { t, Trans } from '@lingui/macro'; import { t, Trans } from '@lingui/macro';
import { FormGroup } from '@patternfly/react-core'; import { FormGroup } from '@patternfly/react-core';
import { InstanceGroupsAPI } from 'api'; import { InstanceGroupsAPI } from 'api';
@@ -13,6 +12,7 @@ import Popover from '../Popover';
import OptionsList from '../OptionsList'; import OptionsList from '../OptionsList';
import Lookup from './Lookup'; import Lookup from './Lookup';
import LookupErrorMessage from './shared/LookupErrorMessage'; import LookupErrorMessage from './shared/LookupErrorMessage';
import FieldWithPrompt from '../FieldWithPrompt';
const QS_CONFIG = getQSConfig('instance-groups', { const QS_CONFIG = getQSConfig('instance-groups', {
page: 1, page: 1,
@@ -21,6 +21,7 @@ const QS_CONFIG = getQSConfig('instance-groups', {
}); });
function InstanceGroupsLookup({ function InstanceGroupsLookup({
id,
value, value,
onChange, onChange,
tooltip, tooltip,
@@ -29,6 +30,9 @@ function InstanceGroupsLookup({
history, history,
fieldName, fieldName,
validate, validate,
isPromptableField,
promptId,
promptName,
}) { }) {
const { const {
result: { instanceGroups, count, relatedSearchableKeys, searchableKeys }, result: { instanceGroups, count, relatedSearchableKeys, searchableKeys },
@@ -63,13 +67,8 @@ function InstanceGroupsLookup({
fetchInstanceGroups(); fetchInstanceGroups();
}, [fetchInstanceGroups]); }, [fetchInstanceGroups]);
return ( const renderLookup = () => (
<FormGroup <>
className={className}
label={t`Instance Groups`}
labelIcon={tooltip && <Popover content={tooltip} />}
fieldId="org-instance-groups"
>
<Lookup <Lookup
id="org-instance-groups" id="org-instance-groups"
header={t`Instance Groups`} header={t`Instance Groups`}
@@ -133,11 +132,33 @@ function InstanceGroupsLookup({
)} )}
/> />
<LookupErrorMessage error={error} /> <LookupErrorMessage error={error} />
</>
);
return isPromptableField ? (
<FieldWithPrompt
fieldId={id}
label={t`Instance Groups`}
promptId={promptId}
promptName={promptName}
tooltip={tooltip}
>
{renderLookup()}
</FieldWithPrompt>
) : (
<FormGroup
className={className}
label={t`Instance Groups`}
labelIcon={tooltip && <Popover content={tooltip} />}
fieldId={id}
>
{renderLookup()}
</FormGroup> </FormGroup>
); );
} }
InstanceGroupsLookup.propTypes = { InstanceGroupsLookup.propTypes = {
id: string,
value: arrayOf(InstanceGroup).isRequired, value: arrayOf(InstanceGroup).isRequired,
tooltip: string, tooltip: string,
onChange: func.isRequired, onChange: func.isRequired,
@@ -148,6 +169,7 @@ InstanceGroupsLookup.propTypes = {
}; };
InstanceGroupsLookup.defaultProps = { InstanceGroupsLookup.defaultProps = {
id: 'org-instance-groups',
tooltip: '', tooltip: '',
className: '', className: '',
required: false, required: false,

View File

@@ -71,7 +71,13 @@ function hasPromptData(launchData) {
launchData.ask_skip_tags_on_launch || launchData.ask_skip_tags_on_launch ||
launchData.ask_tags_on_launch || launchData.ask_tags_on_launch ||
launchData.ask_variables_on_launch || launchData.ask_variables_on_launch ||
launchData.ask_verbosity_on_launch launchData.ask_verbosity_on_launch ||
launchData.ask_execution_environment_on_launch ||
launchData.ask_labels_on_launch ||
launchData.ask_forks_on_launch ||
launchData.ask_job_slicing_on_launch ||
launchData.ask_timeout_on_launch ||
launchData.ask_instance_groups_on_launch
); );
} }
@@ -206,6 +212,36 @@ function PromptDetail({
value={overrides.inventory?.name} value={overrides.inventory?.name}
/> />
)} )}
{launchConfig.ask_execution_environment_on_launch && (
<Detail
label={t`Execution Environment`}
value={overrides.execution_environment?.name}
/>
)}
{launchConfig.ask_instance_groups_on_launch && (
<Detail
fullWidth
label={t`Instance Groups`}
rows={4}
value={
<ChipGroup
numChips={5}
totalChips={overrides.instance_groups.length}
ouiaId="prompt-instance-groups-chips"
>
{overrides.instance_groups.map((instance_group) => (
<Chip
key={instance_group.id}
ouiaId={`instance-group-${instance_group.id}-chip`}
isReadOnly
>
{instance_group.name}
</Chip>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_scm_branch_on_launch && ( {launchConfig.ask_scm_branch_on_launch && (
<Detail <Detail
label={t`Source Control Branch`} label={t`Source Control Branch`}
@@ -278,6 +314,42 @@ function PromptDetail({
} }
/> />
)} )}
{launchConfig.ask_labels_on_launch && (
<Detail
fullWidth
label={t`Labels`}
value={
<ChipGroup
numChips={5}
totalChips={overrides.labels.length}
ouiaId="prompt-label-chips"
>
{overrides.labels.map((label) => (
<Chip
key={label.id}
ouiaId={`label-${label.id}-chip`}
isReadOnly
>
{label.name}
</Chip>
))}
</ChipGroup>
}
isEmpty={overrides.labels.length === 0}
/>
)}
{launchConfig.ask_forks_on_launch && (
<Detail label={t`Forks`} value={overrides.forks} />
)}
{launchConfig.ask_job_slicing_on_launch && (
<Detail
label={t`Job Slicing`}
value={overrides.job_slice_count}
/>
)}
{launchConfig.ask_timeout_on_launch && (
<Detail label={t`Timeout`} value={overrides.timeout} />
)}
{launchConfig.ask_diff_mode_on_launch && ( {launchConfig.ask_diff_mode_on_launch && (
<Detail <Detail
label={t`Show Changes`} label={t`Show Changes`}

View File

@@ -73,10 +73,14 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
diff_mode, diff_mode,
dtend, dtend,
dtstart, dtstart,
execution_environment,
extra_data, extra_data,
forks,
inventory, inventory,
job_slice_count,
job_tags, job_tags,
job_type, job_type,
labels,
limit, limit,
modified, modified,
name, name,
@@ -85,6 +89,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
scm_branch, scm_branch,
skip_tags, skip_tags,
summary_fields, summary_fields,
timeout,
timezone, timezone,
verbosity, verbosity,
} = schedule; } = schedule;
@@ -185,6 +190,11 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
ask_tags_on_launch, ask_tags_on_launch,
ask_variables_on_launch, ask_variables_on_launch,
ask_verbosity_on_launch, ask_verbosity_on_launch,
ask_execution_environment_on_launch,
ask_labels_on_launch,
ask_forks_on_launch,
ask_job_slicing_on_launch,
ask_timeout_on_launch,
survey_enabled, survey_enabled,
} = launchData || {}; } = launchData || {};
@@ -239,6 +249,12 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
const showJobTypeDetail = ask_job_type_on_launch && job_type; const showJobTypeDetail = ask_job_type_on_launch && job_type;
const showSCMBranchDetail = ask_scm_branch_on_launch && scm_branch; const showSCMBranchDetail = ask_scm_branch_on_launch && scm_branch;
const showVerbosityDetail = ask_verbosity_on_launch && VERBOSITY()[verbosity]; const showVerbosityDetail = ask_verbosity_on_launch && VERBOSITY()[verbosity];
const showExecutionEnvironmentDetail =
ask_execution_environment_on_launch && execution_environment;
const showLabelsDetail = ask_labels_on_launch && labels && labels.length > 0;
const showForksDetail = ask_forks_on_launch;
const showJobSlicingDetail = ask_job_slicing_on_launch;
const showTimeoutDetail = ask_timeout_on_launch;
const showPromptedFields = const showPromptedFields =
showCredentialsDetail || showCredentialsDetail ||
@@ -250,7 +266,12 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
showSkipTagsDetail || showSkipTagsDetail ||
showTagsDetail || showTagsDetail ||
showVerbosityDetail || showVerbosityDetail ||
showVariablesDetail; showVariablesDetail ||
showExecutionEnvironmentDetail ||
showLabelsDetail ||
showForksDetail ||
showJobSlicingDetail ||
showTimeoutDetail;
if (isLoading) { if (isLoading) {
return <ContentLoading />; return <ContentLoading />;
@@ -402,11 +423,20 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
dataCy="schedule-inventory" dataCy="schedule-inventory"
/> />
)} )}
{ask_verbosity_on_launch && ( {showExecutionEnvironmentDetail && (
<Detail <Detail
label={t`Verbosity`} label={t`Execution Environment`}
value={VERBOSITY()[verbosity]} value={
dataCy="schedule-verbosity" summary_fields?.execution_environment ? (
<Link
to={`/execution_environments/${summary_fields?.execution_environment?.id}/details`}
>
{summary_fields?.execution_environment?.name}
</Link>
) : (
' '
)
}
/> />
)} )}
{ask_scm_branch_on_launch && ( {ask_scm_branch_on_launch && (
@@ -419,6 +449,18 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
{ask_limit_on_launch && ( {ask_limit_on_launch && (
<Detail label={t`Limit`} value={limit} dataCy="schedule-limit" /> <Detail label={t`Limit`} value={limit} dataCy="schedule-limit" />
)} )}
{ask_forks_on_launch && <Detail label={t`Forks`} value={forks} />}
{ask_limit_on_launch && <Detail label={t`Limit`} value={limit} />}
{ask_verbosity_on_launch && (
<Detail
label={t`Verbosity`}
value={VERBOSITY()[verbosity]}
dataCy="schedule-verbosity"
/>
)}
{ask_timeout_on_launch && (
<Detail label={t`Timeout`} value={timeout} />
)}
{showDiffModeDetail && ( {showDiffModeDetail && (
<Detail <Detail
label={t`Show Changes`} label={t`Show Changes`}
@@ -426,6 +468,9 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
dataCy="schedule-show-changes" dataCy="schedule-show-changes"
/> />
)} )}
{ask_job_slicing_on_launch && (
<Detail label={t`Job Slicing`} value={job_slice_count} />
)}
{showCredentialsDetail && ( {showCredentialsDetail && (
<Detail <Detail
fullWidth fullWidth
@@ -449,6 +494,26 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
dataCy="schedule-credentials" dataCy="schedule-credentials"
/> />
)} )}
{showLabelsDetail && (
<Detail
fullWidth
label={t`Labels`}
value={
<ChipGroup
numChips={5}
totalChips={summary_fields.labels.results.length}
ouiaId="schedule-label-chips"
>
{summary_fields.labels.results.map((l) => (
<Chip key={l.id} ouiaId={`label-${l.id}-chip`} isReadOnly>
{l.name}
</Chip>
))}
</ChipGroup>
}
isEmpty={summary_fields.labels.results.length === 0}
/>
)}
{showTagsDetail && ( {showTagsDetail && (
<Detail <Detail
fullWidth fullWidth

View File

@@ -225,6 +225,12 @@ function ScheduleForm({
launchConfig.ask_scm_branch_on_launch || launchConfig.ask_scm_branch_on_launch ||
launchConfig.ask_tags_on_launch || launchConfig.ask_tags_on_launch ||
launchConfig.ask_skip_tags_on_launch || launchConfig.ask_skip_tags_on_launch ||
launchConfig.ask_execution_environment_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_timeout_on_launch ||
launchConfig.ask_instance_groups_on_launch ||
launchConfig.survey_enabled || launchConfig.survey_enabled ||
launchConfig.inventory_needed_to_start || launchConfig.inventory_needed_to_start ||
launchConfig.variables_needed_to_start?.length > 0) launchConfig.variables_needed_to_start?.length > 0)

View File

@@ -1,12 +1,10 @@
import React, { useContext, useEffect, useCallback } from 'react'; import React, { useContext, useEffect, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, Modal } from '@patternfly/react-core'; import { Button, Modal } from '@patternfly/react-core';
import { import {
WorkflowDispatchContext, WorkflowDispatchContext,
WorkflowStateContext, WorkflowStateContext,
} from 'contexts/Workflow'; } from 'contexts/Workflow';
import ContentError from 'components/ContentError'; import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading'; import ContentLoading from 'components/ContentLoading';
import PromptDetail from 'components/PromptDetail'; import PromptDetail from 'components/PromptDetail';
@@ -157,6 +155,22 @@ function NodeViewModal({ readOnly }) {
if (launchConfig.ask_inventory_on_launch) { if (launchConfig.ask_inventory_on_launch) {
overrides.inventory = originalNodeObject.summary_fields.inventory; overrides.inventory = originalNodeObject.summary_fields.inventory;
} }
if (launchConfig.ask_execution_environment_on_launch) {
overrides.execution_environment =
originalNodeObject.summary_fields.execution_environment;
}
if (launchConfig.ask_labels_on_launch) {
overrides.labels = originalNodeObject.labels;
}
if (launchConfig.ask_forks_on_launch) {
overrides.forks = originalNodeObject.forks;
}
if (launchConfig.ask_job_slicing_on_launch) {
overrides.job_slice_count = originalNodeObject.job_slice_count;
}
if (launchConfig.ask_timeout_on_launch) {
overrides.timeout = originalNodeObject.timeout;
}
if (launchConfig.ask_scm_branch_on_launch) { if (launchConfig.ask_scm_branch_on_launch) {
overrides.scm_branch = originalNodeObject.scm_branch; overrides.scm_branch = originalNodeObject.scm_branch;
} }

View File

@@ -3,6 +3,7 @@ import { useFormikContext } from 'formik';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import useInventoryStep from 'components/LaunchPrompt/steps/useInventoryStep'; import useInventoryStep from 'components/LaunchPrompt/steps/useInventoryStep';
import useCredentialsStep from 'components/LaunchPrompt/steps/useCredentialsStep'; import useCredentialsStep from 'components/LaunchPrompt/steps/useCredentialsStep';
import useExecutionEnvironmentStep from 'components/LaunchPrompt/steps/useExecutionEnvironmentStep';
import useOtherPromptsStep from 'components/LaunchPrompt/steps/useOtherPromptsStep'; import useOtherPromptsStep from 'components/LaunchPrompt/steps/useOtherPromptsStep';
import useSurveyStep from 'components/LaunchPrompt/steps/useSurveyStep'; import useSurveyStep from 'components/LaunchPrompt/steps/useSurveyStep';
import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep'; import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep';
@@ -26,6 +27,12 @@ function showPreviewStep(nodeType, launchConfig) {
launchConfig.ask_variables_on_launch || launchConfig.ask_variables_on_launch ||
launchConfig.ask_limit_on_launch || launchConfig.ask_limit_on_launch ||
launchConfig.ask_scm_branch_on_launch || launchConfig.ask_scm_branch_on_launch ||
launchConfig.ask_execution_environment_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_timeout_on_launch ||
launchConfig.ask_instance_groups_on_launch ||
launchConfig.survey_enabled || launchConfig.survey_enabled ||
(launchConfig.variables_needed_to_start && (launchConfig.variables_needed_to_start &&
launchConfig.variables_needed_to_start.length > 0) launchConfig.variables_needed_to_start.length > 0)
@@ -129,6 +136,20 @@ const getNodeToEditDefaultValues = (
} }
} }
if (launchConfig.ask_execution_environment_on_launch) {
if (nodeToEdit?.promptValues) {
initialValues.execution_environment =
nodeToEdit?.promptValues?.execution_environment;
} else if (
nodeToEdit?.originalNodeObject?.summary_fields?.execution_environment
) {
initialValues.execution_environment =
nodeToEdit?.originalNodeObject?.summary_fields?.execution_environment;
} else {
initialValues.execution_environment = null;
}
}
if (launchConfig.ask_credential_on_launch) { if (launchConfig.ask_credential_on_launch) {
if (nodeToEdit?.promptValues?.credentials) { if (nodeToEdit?.promptValues?.credentials) {
initialValues.credentials = nodeToEdit?.promptValues?.credentials; initialValues.credentials = nodeToEdit?.promptValues?.credentials;
@@ -197,6 +218,15 @@ const getNodeToEditDefaultValues = (
if (launchConfig.ask_diff_mode_on_launch) { if (launchConfig.ask_diff_mode_on_launch) {
initialValues.diff_mode = sourceOfValues?.diff_mode || false; initialValues.diff_mode = sourceOfValues?.diff_mode || false;
} }
if (launchConfig.ask_forks_on_launch) {
initialValues.forks = sourceOfValues?.forks || 0;
}
if (launchConfig.ask_job_slicing_on_launch) {
initialValues.job_slice_count = sourceOfValues?.job_slice_count || 1;
}
if (launchConfig.ask_timeout_on_launch) {
initialValues.timeout = sourceOfValues?.timeout || 0;
}
if (launchConfig.ask_variables_on_launch) { if (launchConfig.ask_variables_on_launch) {
const newExtraData = { ...sourceOfValues.extra_data }; const newExtraData = { ...sourceOfValues.extra_data };
@@ -258,6 +288,7 @@ export default function useWorkflowNodeSteps(
useDaysToKeepStep(), useDaysToKeepStep(),
useInventoryStep(launchConfig, resource, visited), useInventoryStep(launchConfig, resource, visited),
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials), useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
useExecutionEnvironmentStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource), useOtherPromptsStep(launchConfig, resource),
useSurveyStep(launchConfig, surveyConfig, resource, visited), useSurveyStep(launchConfig, surveyConfig, resource, visited),
]; ];
@@ -348,6 +379,7 @@ export default function useWorkflowNodeSteps(
setVisited({ setVisited({
inventory: true, inventory: true,
credentials: true, credentials: true,
executionEnvironment: true,
other: true, other: true,
survey: true, survey: true,
preview: true, preview: true,

View File

@@ -6,7 +6,7 @@ const jtHelpTextStrings = () => ({
jobType: t`For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook.`, jobType: t`For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook.`,
inventory: t`Select the inventory containing the hosts you want this job to manage.`, inventory: t`Select the inventory containing the hosts you want this job to manage.`,
project: t`Select the project containing the playbook you want this job to execute.`, project: t`Select the project containing the playbook you want this job to execute.`,
executionEnvironmentForm: t`Select the execution environment for this job template.`, executionEnvironmentForm: t`The container image to be used for execution.`,
executionEnvironmentDetail: t`The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template.`, executionEnvironmentDetail: t`The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template.`,
playbook: t`Select the playbook to be executed by this job.`, playbook: t`Select the playbook to be executed by this job.`,
credentials: t`Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`, credentials: t`Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`,
@@ -24,7 +24,7 @@ const jtHelpTextStrings = () => ({
webhookURL: t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`, webhookURL: t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`,
webhookKey: t`Webhook services can use this as a shared secret.`, webhookKey: t`Webhook services can use this as a shared secret.`,
webhookCredential: t`Optionally select the credential to use to send status updates back to the webhook service.`, webhookCredential: t`Optionally select the credential to use to send status updates back to the webhook service.`,
sourceControlBranch: t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`, sourceControlBranch: t`Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true.`,
provisioningCallbacks: (brandName = '') => provisioningCallbacks: (brandName = '') =>
t`Enables creation of a provisioning callback URL. Using the URL a host can contact ${brandName} and request a configuration update using this job template.`, t`Enables creation of a provisioning callback URL. Using the URL a host can contact ${brandName} and request a configuration update using this job template.`,
privilegeEscalation: t`If enabled, run this playbook as an administrator.`, privilegeEscalation: t`If enabled, run this playbook as an administrator.`,

View File

@@ -1,6 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withFormik, useField } from 'formik'; import { withFormik, useField } from 'formik';
import { import {
@@ -87,6 +86,10 @@ function JobTemplateForm({
const [credentialField, , credentialHelpers] = useField('credentials'); const [credentialField, , credentialHelpers] = useField('credentials');
const [labelsField, , labelsHelpers] = useField('labels'); const [labelsField, , labelsHelpers] = useField('labels');
const [limitField, limitMeta, limitHelpers] = useField('limit'); const [limitField, limitMeta, limitHelpers] = useField('limit');
const [forksField, forksMeta, forksHelpers] = useField('forks');
const [jobSliceCountField, jobSliceCountMeta, jobSliceCountHelpers] =
useField('job_slice_count');
const [timeoutField, timeoutMeta, timeoutHelpers] = useField('timeout');
const [diffModeField, , diffModeHelpers] = useField('diff_mode'); const [diffModeField, , diffModeHelpers] = useField('diff_mode');
const [instanceGroupsField, , instanceGroupsHelpers] = const [instanceGroupsField, , instanceGroupsHelpers] =
useField('instanceGroups'); useField('instanceGroups');
@@ -321,6 +324,9 @@ function JobTemplateForm({
globallyAvailable globallyAvailable
isDisabled={!projectField.value?.id} isDisabled={!projectField.value?.id}
projectId={projectField.value?.id} projectId={projectField.value?.id}
promptId="template-ask-execution-environment-on-launch"
promptName="ask_execution_environment_on_launch"
isPromptableField
/> />
{projectField.value?.allow_override && ( {projectField.value?.allow_override && (
@@ -376,10 +382,12 @@ function JobTemplateForm({
onError={setContentError} onError={setContentError}
/> />
</FieldWithPrompt> </FieldWithPrompt>
<FormGroup <FieldWithPrompt
label={t`Labels`}
labelIcon={<Popover content={helpText.labels} />}
fieldId="template-labels" fieldId="template-labels"
label={t`Labels`}
promptId="template-ask-labels-on-launch"
promptName="ask_labels_on_launch"
tooltip={helpText.labels}
> >
<LabelSelect <LabelSelect
value={labelsField.value} value={labelsField.value}
@@ -387,7 +395,7 @@ function JobTemplateForm({
onError={setContentError} onError={setContentError}
createText={t`Create`} createText={t`Create`}
/> />
</FormGroup> </FieldWithPrompt>
<VariablesField <VariablesField
id="template-variables" id="template-variables"
name="extra_vars" name="extra_vars"
@@ -396,14 +404,26 @@ function JobTemplateForm({
tooltip={helpText.variables} tooltip={helpText.variables}
/> />
<FormColumnLayout> <FormColumnLayout>
<FormField <FieldWithPrompt
id="template-forks" fieldId="template-forks"
name="forks"
type="number"
min="0"
label={t`Forks`} label={t`Forks`}
promptId="template-ask-forks-on-launch"
promptName="ask_forks_on_launch"
tooltip={helpText.forks} tooltip={helpText.forks}
/> >
<TextInput
id="template-forks"
{...forksField}
validated={
!forksMeta.touched || !forksMeta.error ? 'default' : 'error'
}
onChange={(value) => {
forksHelpers.setValue(value);
}}
type="number"
min="0"
/>
</FieldWithPrompt>
<FieldWithPrompt <FieldWithPrompt
fieldId="template-limit" fieldId="template-limit"
label={t`Limit`} label={t`Limit`}
@@ -428,22 +448,50 @@ function JobTemplateForm({
promptName="ask_verbosity_on_launch" promptName="ask_verbosity_on_launch"
tooltip={helpText.verbosity} tooltip={helpText.verbosity}
/> />
<FormField <FieldWithPrompt
id="template-job-slicing" fieldId="template-job-slicing"
name="job_slice_count"
type="number"
min="1"
label={t`Job Slicing`} label={t`Job Slicing`}
promptId="template-ask-job-slicing-on-launch"
promptName="ask_job_slicing_on_launch"
tooltip={helpText.jobSlicing} tooltip={helpText.jobSlicing}
/> >
<FormField <TextInput
id="template-timeout" id="template-job-slicing"
name="timeout" {...jobSliceCountField}
type="number" validated={
min="0" !jobSliceCountMeta.touched || !jobSliceCountMeta.error
? 'default'
: 'error'
}
onChange={(value) => {
jobSliceCountHelpers.setValue(value);
}}
type="number"
min="1"
/>
</FieldWithPrompt>
<FieldWithPrompt
fieldId="template-timeout"
label={t`Timeout`} label={t`Timeout`}
promptId="template-ask-timeout-on-launch"
promptName="ask_timeout_on_launch"
tooltip={helpText.timeout} tooltip={helpText.timeout}
/> >
<TextInput
id="template-timeout"
{...timeoutField}
validated={
!timeoutMeta.touched || !timeoutMeta.error
? 'default'
: 'error'
}
onChange={(value) => {
timeoutHelpers.setValue(value);
}}
type="number"
min="0"
/>
</FieldWithPrompt>
<FieldWithPrompt <FieldWithPrompt
fieldId="template-diff-mode" fieldId="template-diff-mode"
label={t`Show Changes`} label={t`Show Changes`}
@@ -464,6 +512,9 @@ function JobTemplateForm({
onChange={(value) => instanceGroupsHelpers.setValue(value)} onChange={(value) => instanceGroupsHelpers.setValue(value)}
tooltip={helpText.instanceGroups} tooltip={helpText.instanceGroups}
fieldName="instanceGroups" fieldName="instanceGroups"
promptId="template-ask-instance-groups-on-launch"
promptName="ask_instance_groups_on_launch"
isPromptableField
/> />
<FieldWithPrompt <FieldWithPrompt
fieldId="template-tags" fieldId="template-tags"
@@ -646,12 +697,20 @@ const FormikApp = withFormik({
allow_simultaneous: template.allow_simultaneous || false, allow_simultaneous: template.allow_simultaneous || false,
ask_credential_on_launch: template.ask_credential_on_launch || false, ask_credential_on_launch: template.ask_credential_on_launch || false,
ask_diff_mode_on_launch: template.ask_diff_mode_on_launch || false, ask_diff_mode_on_launch: template.ask_diff_mode_on_launch || false,
ask_execution_environment_on_launch:
template.ask_execution_environment_on_launch || false,
ask_forks_on_launch: template.ask_forks_on_launch || false,
ask_instance_groups_on_launch:
template.ask_instance_groups_on_launch || false,
ask_inventory_on_launch: template.ask_inventory_on_launch || false, ask_inventory_on_launch: template.ask_inventory_on_launch || false,
ask_job_slicing_on_launch: template.ask_job_slicing_on_launch || false,
ask_job_type_on_launch: template.ask_job_type_on_launch || false, ask_job_type_on_launch: template.ask_job_type_on_launch || false,
ask_labels_on_launch: template.ask_labels_on_launch || false,
ask_limit_on_launch: template.ask_limit_on_launch || false, ask_limit_on_launch: template.ask_limit_on_launch || false,
ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false, ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false,
ask_skip_tags_on_launch: template.ask_skip_tags_on_launch || false, ask_skip_tags_on_launch: template.ask_skip_tags_on_launch || false,
ask_tags_on_launch: template.ask_tags_on_launch || false, ask_tags_on_launch: template.ask_tags_on_launch || false,
ask_timeout_on_launch: template.ask_timeout_on_launch || false,
ask_variables_on_launch: template.ask_variables_on_launch || false, ask_variables_on_launch: template.ask_variables_on_launch || false,
ask_verbosity_on_launch: template.ask_verbosity_on_launch || false, ask_verbosity_on_launch: template.ask_verbosity_on_launch || false,
become_enabled: template.become_enabled || false, become_enabled: template.become_enabled || false,

View File

@@ -186,10 +186,12 @@ function WorkflowJobTemplateForm({
</FieldWithPrompt> </FieldWithPrompt>
</FormColumnLayout> </FormColumnLayout>
<FormFullWidthLayout> <FormFullWidthLayout>
<FormGroup <FieldWithPrompt
label={t`Labels`}
labelIcon={<Popover content={helpText.labels} />}
fieldId="template-labels" fieldId="template-labels"
label={t`Labels`}
promptId="template-ask-labels-on-launch"
promptName="ask_labels_on_launch"
tooltip={helpText.labels}
> >
<LabelSelect <LabelSelect
value={labelsField.value} value={labelsField.value}
@@ -197,7 +199,7 @@ function WorkflowJobTemplateForm({
onError={setContentError} onError={setContentError}
createText={t`Create`} createText={t`Create`}
/> />
</FormGroup> </FieldWithPrompt>
</FormFullWidthLayout> </FormFullWidthLayout>
<FormFullWidthLayout> <FormFullWidthLayout>
<VariablesField <VariablesField
@@ -283,6 +285,7 @@ const FormikApp = withFormik({
allow_simultaneous: template.allow_simultaneous || false, allow_simultaneous: template.allow_simultaneous || false,
webhook_credential: template?.summary_fields?.webhook_credential || null, webhook_credential: template?.summary_fields?.webhook_credential || null,
webhook_service: template.webhook_service || '', webhook_service: template.webhook_service || '',
ask_labels_on_launch: template.ask_labels_on_launch || false,
ask_limit_on_launch: template.ask_limit_on_launch || false, ask_limit_on_launch: template.ask_limit_on_launch || false,
ask_inventory_on_launch: template.ask_inventory_on_launch || false, ask_inventory_on_launch: template.ask_inventory_on_launch || false,
ask_variables_on_launch: template.ask_variables_on_launch || false, ask_variables_on_launch: template.ask_variables_on_launch || false,