diff --git a/frontend/awx/interfaces/LaunchConfiguration.ts b/frontend/awx/interfaces/LaunchConfiguration.ts index c1a38551b2..9aa6960370 100644 --- a/frontend/awx/interfaces/LaunchConfiguration.ts +++ b/frontend/awx/interfaces/LaunchConfiguration.ts @@ -4,6 +4,7 @@ export interface LaunchConfiguration { can_start_without_user_input: boolean; passwords_needed_to_start: string[]; ask_scm_branch_on_launch: boolean; + ask_nodes_job_type_on_launch: boolean; ask_variables_on_launch: boolean; ask_tags_on_launch: boolean; ask_diff_mode_on_launch: boolean; @@ -39,6 +40,7 @@ export interface LaunchConfiguration { limit: string; labels: { id: number; name: string }[]; scm_branch: string; + nodes_job_type: 'run' | 'check'; job_tags: string; skip_tags: string; extra_vars: string; diff --git a/frontend/awx/interfaces/WorkflowJobLaunch.ts b/frontend/awx/interfaces/WorkflowJobLaunch.ts index 60ca26d4ac..97510bbcbe 100644 --- a/frontend/awx/interfaces/WorkflowJobLaunch.ts +++ b/frontend/awx/interfaces/WorkflowJobLaunch.ts @@ -9,6 +9,7 @@ export interface WorkflowJobLaunch { ask_limit_on_launch: boolean; ask_scm_branch_on_launch: boolean; ask_skip_tags_on_launch: boolean; + ask_nodes_job_type_on_launch: boolean; ask_tags_on_launch: boolean; ask_variables_on_launch: boolean; ask_verbosity_on_launch: boolean; @@ -41,6 +42,7 @@ export interface WorkflowJobLaunch { }[]; limit: string; scm_branch: string; + nodes_job_type: string; skip_tags: string; job_type: string; verbosity: number; diff --git a/frontend/awx/interfaces/WorkflowJobTemplate.ts b/frontend/awx/interfaces/WorkflowJobTemplate.ts index 975b9dabaf..2f332673e7 100644 --- a/frontend/awx/interfaces/WorkflowJobTemplate.ts +++ b/frontend/awx/interfaces/WorkflowJobTemplate.ts @@ -27,6 +27,7 @@ export interface WorkflowJobTemplate type: 'workflow_job_template'; created: string; allow_simultaneous: boolean; + ask_nodes_job_type_on_launch: boolean; modified: string; webhook_service: string; skip_tags: string; @@ -120,6 +121,7 @@ export interface WorkflowJobTemplateForm | 'webhook_credential' | 'skip_tags' | 'job_tags' + | 'nodes_job_type' | 'type' | 'execution_environment' | 'id' @@ -159,6 +161,7 @@ export interface WorkflowJobTemplateCreate | 'summary_fields' | 'related' | 'job_type' + | 'nodes_job_type' | 'type' | 'created' | 'modified' @@ -183,4 +186,5 @@ export interface WorkflowJobTemplateCreate webhook_credential: number | null; extra_vars: string; organization?: number; + ask_nodes_job_type_on_launch: boolean; } diff --git a/frontend/awx/resources/templates/TemplatePage/TemplateLaunchWizard.tsx b/frontend/awx/resources/templates/TemplatePage/TemplateLaunchWizard.tsx index 08b5fe85d4..634cbac0d9 100644 --- a/frontend/awx/resources/templates/TemplatePage/TemplateLaunchWizard.tsx +++ b/frontend/awx/resources/templates/TemplatePage/TemplateLaunchWizard.tsx @@ -49,6 +49,7 @@ export const formFieldToLaunchConfig = { timeout: 'ask_timeout_on_launch', extra_vars: 'ask_variables_on_launch', verbosity: 'ask_verbosity_on_launch', + nodes_job_type: 'ask_nodes_job_type_on_launch', }; export interface TemplateLaunch { @@ -77,6 +78,7 @@ interface LaunchPayload { skip_tags: string; timeout: number; verbosity: number; + nodes_job_type: string; } type LaunchPayloadProperty = keyof LaunchPayload; @@ -156,6 +158,7 @@ export function LaunchTemplate({ jobType }: { jobType: string }) { setValue('skip_tags', prompt.skip_tags?.map((tag) => tag.name).join(',')); setValue('timeout', prompt.timeout); setValue('verbosity', prompt.verbosity); + setValue('nodes_job_type', prompt.nodes_job_type); } if (labelPayload.length > 0) { setValue('labels', labelPayload); @@ -244,6 +247,7 @@ export function LaunchWizard({ skip_tags: parseStringToTagArray(defaults.skip_tags), timeout: defaults.timeout, verbosity: defaults.verbosity, + nodes_job_type: defaults.nodes_job_type, }, launch_config: config, survey: {}, diff --git a/frontend/awx/resources/templates/TemplatePage/steps/TemplateLaunchReviewStep.tsx b/frontend/awx/resources/templates/TemplatePage/steps/TemplateLaunchReviewStep.tsx index e76b070f5b..c50b484f39 100644 --- a/frontend/awx/resources/templates/TemplatePage/steps/TemplateLaunchReviewStep.tsx +++ b/frontend/awx/resources/templates/TemplatePage/steps/TemplateLaunchReviewStep.tsx @@ -197,6 +197,7 @@ export function TemplateLaunchReviewStep(props: { template: JobTemplate }) { {prompt?.skip_tags?.map(({ name }) => )} + {prompt?.nodes_job_type} ); diff --git a/frontend/awx/resources/templates/WorkflowJobTemplateForm.tsx b/frontend/awx/resources/templates/WorkflowJobTemplateForm.tsx index af39a869aa..c9bc437fe1 100644 --- a/frontend/awx/resources/templates/WorkflowJobTemplateForm.tsx +++ b/frontend/awx/resources/templates/WorkflowJobTemplateForm.tsx @@ -74,6 +74,7 @@ export function EditWorkflowJobTemplate() { ask_skip_tags_on_launch: workflowJobTemplate.ask_skip_tags_on_launch || false, ask_tags_on_launch: workflowJobTemplate.ask_tags_on_launch || false, ask_variables_on_launch: workflowJobTemplate.ask_variables_on_launch || false, + ask_nodes_job_type_on_launch: workflowJobTemplate.ask_nodes_job_type_on_launch || false, description: workflowJobTemplate.description || '', extra_vars: workflowJobTemplate.extra_vars || '---', inventory: workflowJobTemplate.summary_fields.inventory || null, diff --git a/frontend/awx/resources/templates/WorkflowJobTemplateInputs.tsx b/frontend/awx/resources/templates/WorkflowJobTemplateInputs.tsx index bda48ca96f..ee8012dca7 100644 --- a/frontend/awx/resources/templates/WorkflowJobTemplateInputs.tsx +++ b/frontend/awx/resources/templates/WorkflowJobTemplateInputs.tsx @@ -125,6 +125,14 @@ export function WorkflowJobTemplateInputs(props: { label={t('Enable concurrent jobs')} name="allow_simultaneous" /> + + labelHelpTitle={t('Nodes job type')} + labelHelp={t( + 'If enabled, a prompt will appear during workflow launch, allowing you to set the job type for each node. This feature prevents the creation of identical workflows with only different job types. NOTE: If your workflow contains nodes with mixed job_type values, enabling this feature will override all of them with a single job_type at execution time.' + )} + label={t('Enable prompt on launch for nodes job type')} + name="ask_nodes_job_type_on_launch" + /> {isWebhookEnabled ? : null} diff --git a/frontend/awx/resources/templates/WorkflowJobTemplatePage/WorkflowJobTemplateDetails.tsx b/frontend/awx/resources/templates/WorkflowJobTemplatePage/WorkflowJobTemplateDetails.tsx index cabcc7d5a0..f0fd6d7d33 100644 --- a/frontend/awx/resources/templates/WorkflowJobTemplatePage/WorkflowJobTemplateDetails.tsx +++ b/frontend/awx/resources/templates/WorkflowJobTemplatePage/WorkflowJobTemplateDetails.tsx @@ -39,7 +39,10 @@ export function WorkflowJobTemplateDetails(props: { const { summary_fields: summaryFields } = template; - const showOptionsField = template.allow_simultaneous || template.webhook_service; + const showOptionsField = + template.allow_simultaneous || + template.webhook_service || + template.ask_nodes_job_type_on_launch; const inventoryUrlPaths: { [key: string]: string } = { '': 'inventory', @@ -137,6 +140,11 @@ export function WorkflowJobTemplateDetails(props: { {template.webhook_service && ( {t`Webhooks`} )} + {template.ask_nodes_job_type_on_launch && ( + + {t`Prompt on launch for nodes job type`} + + )} diff --git a/frontend/awx/resources/templates/WorkflowVisualizer/types.ts b/frontend/awx/resources/templates/WorkflowVisualizer/types.ts index ecd6e63a77..bd413ee3b4 100644 --- a/frontend/awx/resources/templates/WorkflowVisualizer/types.ts +++ b/frontend/awx/resources/templates/WorkflowVisualizer/types.ts @@ -136,6 +136,7 @@ export interface PromptFormValues { labels: { name: string; id: number }[]; limit: string; scm_branch: string; + nodes_job_type: string; skip_tags: { name: string }[]; timeout: number; verbosity: 0 | 1 | 2 | 3 | 4 | 5; diff --git a/frontend/awx/resources/templates/WorkflowVisualizer/wizard/NodePromptsStep.tsx b/frontend/awx/resources/templates/WorkflowVisualizer/wizard/NodePromptsStep.tsx index e1e2008a7d..e83bc23760 100644 --- a/frontend/awx/resources/templates/WorkflowVisualizer/wizard/NodePromptsStep.tsx +++ b/frontend/awx/resources/templates/WorkflowVisualizer/wizard/NodePromptsStep.tsx @@ -66,6 +66,7 @@ export function NodePromptsStep() { limit: prompt?.limit ?? defaults.limit, organization: prompt?.organization ?? organizationId, scm_branch: prompt?.scm_branch ?? defaults.scm_branch, + nodes_job_type: prompt?.nodes_job_type ?? defaults.nodes_job_type, skip_tags: prompt?.skip_tags ?? parseStringToTagArray(defaults.skip_tags), timeout: prompt?.timeout ?? defaults.timeout, verbosity: prompt?.verbosity ?? defaults.verbosity, @@ -263,6 +264,23 @@ export function NodePromptsStep() { format="yaml" /> + + + isRequired + id="nodes_job_type" + label={t('Nodes job type')} + labelHelpTitle={t('Nodes job type')} + labelHelp={t( + 'Set the job type for each node. This feature prevents the creation of identical workflows with only different job types.' + )} + name="prompt.nodes_job_type" + options={[ + { label: t('Check'), value: 'check' }, + { label: t('Run'), value: 'run' }, + ]} + placeholderText={t('Select nodes job type')} + /> + ); } diff --git a/frontend/awx/resources/templates/WorkflowVisualizer/wizard/helpers.ts b/frontend/awx/resources/templates/WorkflowVisualizer/wizard/helpers.ts index f6b38f0783..240806c9ef 100644 --- a/frontend/awx/resources/templates/WorkflowVisualizer/wizard/helpers.ts +++ b/frontend/awx/resources/templates/WorkflowVisualizer/wizard/helpers.ts @@ -59,6 +59,7 @@ export function shouldHideOtherStep(launchData: LaunchConfiguration) { launchData.ask_tags_on_launch || launchData.ask_timeout_on_launch || launchData.ask_variables_on_launch || - launchData.ask_verbosity_on_launch + launchData.ask_verbosity_on_launch || + launchData.ask_nodes_job_type_on_launch ); } diff --git a/frontend/awx/resources/templates/hooks/useLaunchTemplate.tsx b/frontend/awx/resources/templates/hooks/useLaunchTemplate.tsx index e0142f9fe9..c52c840bae 100644 --- a/frontend/awx/resources/templates/hooks/useLaunchTemplate.tsx +++ b/frontend/awx/resources/templates/hooks/useLaunchTemplate.tsx @@ -77,7 +77,8 @@ export function canLaunchWithoutPrompt(launchData: TemplateLaunch) { launchData.can_start_without_user_input && !launchData.survey_enabled && (!launchData.passwords_needed_to_start || launchData.passwords_needed_to_start.length === 0) && - (!launchData.variables_needed_to_start || launchData.variables_needed_to_start.length === 0) + (!launchData.variables_needed_to_start || launchData.variables_needed_to_start.length === 0) && + !launchData.ask_nodes_job_type_on_launch ); }