diff --git a/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js b/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js index a19bc46a57..ab0c9e5018 100644 --- a/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js +++ b/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js @@ -67,27 +67,18 @@ function getInitialValues(launchConfig, surveyConfig, resource) { const values = {}; if (surveyConfig?.spec) { surveyConfig.spec.forEach((question) => { - if (question.type === 'multiselect') { + if (resource?.extra_data && resource?.extra_data[question.variable]) { + values[`survey_${question.variable}`] = + resource.extra_data[question.variable]; + } else if (question.type === 'multiselect') { values[`survey_${question.variable}`] = question.default ? question.default.split('\n') : []; } else { values[`survey_${question.variable}`] = question.default ?? ''; } - if (resource?.extra_data) { - Object.entries(resource.extra_data).forEach(([key, value]) => { - if (key === question.variable) { - if (question.type === 'multiselect') { - values[`survey_${question.variable}`] = value; - } else { - values[`survey_${question.variable}`] = value; - } - } - }); - } }); } - return values; } diff --git a/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.js b/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.js index 0ce554a297..55a1671f9b 100644 --- a/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.js +++ b/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.js @@ -13,6 +13,18 @@ import ScheduleForm from '../shared/ScheduleForm'; import buildRuleSet from '../shared/buildRuleSet'; import { CardBody } from '../../Card'; +function generateExtraData(extra_vars, surveyValues, surveyConfiguration) { + const extraVars = parseVariableField( + yaml.dump(mergeExtraVars(extra_vars, surveyValues)) + ); + surveyConfiguration.spec.forEach((q) => { + if (!surveyValues[q.variable]) { + delete extraVars[q.variable]; + } + }); + return extraVars; +} + function ScheduleEdit({ hasDaysToKeepField, schedule, @@ -33,10 +45,12 @@ function ScheduleEdit({ surveyConfiguration, originalInstanceGroups, originalLabels, - scheduleCredentials = [] + scheduleCredentials = [], + isPromptTouched = false ) => { const { execution_environment, + extra_vars = null, instance_groups, inventory, credentials = [], @@ -48,45 +62,54 @@ function ScheduleEdit({ labels, ...submitValues } = values; - let extraVars; + const surveyValues = getSurveyValues(values); if ( - !Object.values(surveyValues).length && - surveyConfiguration?.spec?.length + isPromptTouched && + surveyConfiguration?.spec && + launchConfiguration?.ask_variables_on_launch ) { - surveyConfiguration.spec.forEach((q) => { - surveyValues[q.variable] = q.default; - }); + submitValues.extra_data = generateExtraData( + extra_vars, + surveyValues, + surveyConfiguration + ); + } else if ( + isPromptTouched && + surveyConfiguration?.spec && + !launchConfiguration?.ask_variables_on_launch + ) { + submitValues.extra_data = generateExtraData( + schedule.extra_data, + surveyValues, + surveyConfiguration + ); + } else if ( + isPromptTouched && + launchConfiguration?.ask_variables_on_launch + ) { + submitValues.extra_data = parseVariableField(extra_vars); } - const initialExtraVars = - launchConfiguration?.ask_variables_on_launch && - (values.extra_vars || '---'); - if (surveyConfiguration?.spec) { - extraVars = yaml.dump(mergeExtraVars(initialExtraVars, surveyValues)); - } else { - extraVars = yaml.dump(mergeExtraVars(initialExtraVars, {})); - } - submitValues.extra_data = extraVars && parseVariableField(extraVars); - if ( - Object.keys(submitValues.extra_data).length === 0 && - Object.keys(schedule.extra_data).length > 0 + isPromptTouched && + launchConfiguration?.ask_inventory_on_launch && + inventory ) { - submitValues.extra_data = schedule.extra_data; - } - delete values.extra_vars; - if (inventory) { submitValues.inventory = inventory.id; } - if (execution_environment) { + if ( + isPromptTouched && + launchConfiguration?.ask_execution_environment_on_launch && + execution_environment + ) { submitValues.execution_environment = execution_environment.id; } try { - if (launchConfiguration?.ask_labels_on_launch) { + if (isPromptTouched && launchConfiguration?.ask_labels_on_launch) { const { labelIds, error } = createNewLabels( values.labels, resource.organization @@ -120,9 +143,16 @@ function ScheduleEdit({ } } + const cleanedRequestData = Object.keys(requestData) + .filter((key) => !key.startsWith('survey_')) + .reduce((acc, key) => { + acc[key] = requestData[key]; + return acc; + }, {}); + const { data: { id: scheduleId }, - } = await SchedulesAPI.update(schedule.id, requestData); + } = await SchedulesAPI.update(schedule.id, cleanedRequestData); const { added: addedCredentials, removed: removedCredentials } = getAddedAndRemoved( diff --git a/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.test.js b/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.test.js index f5c6eb5aec..7d74ebd232 100644 --- a/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.test.js +++ b/awx/ui/src/components/Schedule/ScheduleEdit/ScheduleEdit.test.js @@ -6,6 +6,7 @@ import { InventoriesAPI, CredentialsAPI, CredentialTypesAPI, + JobTemplatesAPI, } from 'api'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; import ScheduleEdit from './ScheduleEdit'; @@ -125,6 +126,7 @@ describe('', () => { id: 27, }, }); + await act(async () => { wrapper = mountWithContexts( ', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run once schedule', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200325T100000 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', }); @@ -233,7 +234,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run every 10 minutes 10 times', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200325T103000 RRULE:INTERVAL=10;FREQ=MINUTELY;COUNT=10', }); @@ -262,7 +262,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run every hour until date', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=HOURLY;UNTIL=20200326T144500Z', }); @@ -288,7 +287,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run daily', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=DAILY', }); @@ -316,7 +314,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run weekly on mon/wed/fri', - extra_data: {}, rrule: `DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=${RRule.MO},${RRule.WE},${RRule.FR}`, }); }); @@ -344,7 +341,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run on the first day of the month', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200401T104500 RRULE:INTERVAL=1;FREQ=MONTHLY;BYMONTHDAY=1', }); @@ -376,7 +372,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run monthly on the last Tuesday', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200331T110000 RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=-1;BYDAY=TU', }); @@ -406,7 +401,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Yearly on the first day of March', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200301T000000 RRULE:INTERVAL=1;FREQ=YEARLY;BYMONTH=3;BYMONTHDAY=1', }); @@ -437,7 +431,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Yearly on the second Friday in April', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200410T111500 RRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=2;BYDAY=FR;BYMONTH=4', }); @@ -468,7 +461,6 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Yearly on the first weekday in October', - extra_data: {}, rrule: 'DTSTART;TZID=America/New_York:20200410T111500 RRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=1;BYDAY=MO,TU,WE,TH,FR;BYMONTH=10', }); @@ -562,7 +554,6 @@ describe('', () => { wrapper.update(); expect(SchedulesAPI.update).toBeCalledWith(27, { - extra_data: {}, name: 'mock schedule', rrule: 'DTSTART;TZID=America/New_York:20210128T141500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', @@ -633,15 +624,13 @@ describe('', () => { endDateTime: undefined, startDateTime: undefined, description: '', - extra_data: {}, name: 'foo', - inventory: 702, rrule: 'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', }); }); - test('should submit survey with default values properly, without opening prompt wizard', async () => { + test('should submit update values properly when prompt is not opened', async () => { let scheduleSurveyWrapper; await act(async () => { scheduleSurveyWrapper = mountWithContexts( @@ -746,9 +735,195 @@ describe('', () => { expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { description: 'test description', name: 'Run once schedule', - extra_data: { mc: 'first', text: 'text variable' }, rrule: 'DTSTART;TZID=America/New_York:20200325T100000 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', }); }); + test('should submit update values properly when survey values change', async () => { + JobTemplatesAPI.readSurvey.mockResolvedValue({ + data: { + spec: [ + { + question_name: 'text', + question_description: '', + required: true, + type: 'text', + variable: 'text', + min: 0, + max: 1024, + default: 'text variable', + choices: '', + new_question: true, + }, + ], + }, + }); + + JobTemplatesAPI.readLaunch.mockResolvedValue({ + data: { + can_start_without_user_input: false, + passwords_needed_to_start: [], + ask_scm_branch_on_launch: false, + ask_variables_on_launch: false, + ask_tags_on_launch: false, + ask_diff_mode_on_launch: false, + ask_skip_tags_on_launch: false, + ask_job_type_on_launch: false, + ask_limit_on_launch: false, + ask_verbosity_on_launch: false, + ask_inventory_on_launch: true, + ask_credential_on_launch: true, + survey_enabled: true, + variables_needed_to_start: [], + credential_needed_to_start: true, + inventory_needed_to_start: true, + job_template_data: { + name: 'Demo Job Template', + id: 7, + description: '', + }, + defaults: { + extra_vars: '---', + diff_mode: false, + limit: '', + job_tags: '', + skip_tags: '', + job_type: 'run', + verbosity: 0, + inventory: { + name: null, + id: null, + }, + scm_branch: '', + credentials: [], + }, + }, + }); + + let scheduleSurveyWrapper; + await act(async () => { + scheduleSurveyWrapper = mountWithContexts( + + ); + }); + scheduleSurveyWrapper.update(); + + await act(async () => + scheduleSurveyWrapper + .find('Button[aria-label="Prompt"]') + .prop('onClick')() + ); + scheduleSurveyWrapper.update(); + expect(scheduleSurveyWrapper.find('WizardNavItem').length).toBe(4); + await act(async () => + scheduleSurveyWrapper.find('WizardFooterInternal').prop('onNext')() + ); + scheduleSurveyWrapper.update(); + await act(async () => + scheduleSurveyWrapper.find('WizardFooterInternal').prop('onNext')() + ); + scheduleSurveyWrapper.update(); + await act(async () => + scheduleSurveyWrapper + .find('input#survey-question-text') + .simulate('change', { + target: { value: 'foo', name: 'survey_text' }, + }) + ); + scheduleSurveyWrapper.update(); + await act(async () => + scheduleSurveyWrapper.find('WizardFooterInternal').prop('onNext')() + ); + scheduleSurveyWrapper.update(); + await act(async () => + scheduleSurveyWrapper.find('WizardFooterInternal').prop('onNext')() + ); + scheduleSurveyWrapper.update(); + + expect(scheduleSurveyWrapper.find('Wizard').length).toBe(0); + + await act(async () => + scheduleSurveyWrapper.find('Button[aria-label="Save"]').prop('onClick')() + ); + + expect(SchedulesAPI.update).toHaveBeenCalledWith(27, { + description: '', + name: 'mock schedule', + inventory: 702, + extra_data: { + text: 'foo', + }, + rrule: + 'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', + }); + }); }); diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.js index 8fbf83791c..15c972d5d4 100644 --- a/awx/ui/src/components/Schedule/shared/ScheduleForm.js +++ b/awx/ui/src/components/Schedule/shared/ScheduleForm.js @@ -40,6 +40,7 @@ function ScheduleForm({ resourceDefaultCredentials, }) { const [isWizardOpen, setIsWizardOpen] = useState(false); + const [isPromptTouched, setIsPromptTouched] = useState(false); const [isSaveDisabled, setIsSaveDisabled] = useState(false); const originalLabels = useRef([]); const originalInstanceGroups = useRef([]); @@ -492,7 +493,8 @@ function ScheduleForm({ surveyConfig, originalInstanceGroups.current, originalLabels.current, - credentials + credentials, + isPromptTouched ); }} validate={validate} @@ -518,6 +520,7 @@ function ScheduleForm({ onSave={() => { setIsWizardOpen(false); setIsSaveDisabled(false); + setIsPromptTouched(true); }} resourceDefaultCredentials={resourceDefaultCredentials} labels={originalLabels.current} diff --git a/awx/ui/src/util/prompt/getSurveyValues.js b/awx/ui/src/util/prompt/getSurveyValues.js index 306cb5eee3..dedf6585d4 100644 --- a/awx/ui/src/util/prompt/getSurveyValues.js +++ b/awx/ui/src/util/prompt/getSurveyValues.js @@ -1,7 +1,7 @@ export default function getSurveyValues(values) { const surveyValues = {}; Object.keys(values).forEach((key) => { - if (key.startsWith('survey_') && values[key] !== []) { + if (key.startsWith('survey_')) { if (Array.isArray(values[key]) && values[key].length === 0) { return; } diff --git a/awx/ui/src/util/prompt/mergeExtraVars.js b/awx/ui/src/util/prompt/mergeExtraVars.js index a5a0e64a12..d23bd48447 100644 --- a/awx/ui/src/util/prompt/mergeExtraVars.js +++ b/awx/ui/src/util/prompt/mergeExtraVars.js @@ -1,7 +1,12 @@ import yaml from 'js-yaml'; export default function mergeExtraVars(extraVars = '', survey = {}) { - const vars = yaml.load(extraVars) || {}; + let vars = {}; + if (typeof extraVars === 'string') { + vars = yaml.load(extraVars); + } else if (typeof extraVars === 'object') { + vars = extraVars; + } return { ...vars, ...survey,