diff --git a/awx/ui/src/components/LaunchButton/LaunchButton.js b/awx/ui/src/components/LaunchButton/LaunchButton.js index 909441b2c9..97cba6e67e 100644 --- a/awx/ui/src/components/LaunchButton/LaunchButton.js +++ b/awx/ui/src/components/LaunchButton/LaunchButton.js @@ -43,6 +43,7 @@ function LaunchButton({ resource, children }) { const [surveyConfig, setSurveyConfig] = useState(null); const [labels, setLabels] = useState([]); const [isLaunching, setIsLaunching] = useState(false); + const [resourceCredentials, setResourceCredentials] = useState([]); const [error, setError] = useState(null); const handleLaunch = async () => { @@ -83,6 +84,13 @@ function LaunchButton({ resource, children }) { setLabels(allLabels); } + if (launch.ask_credential_on_launch) { + const { + data: { results: templateCredentials }, + } = await JobTemplatesAPI.readCredentials(resource.id); + setResourceCredentials(templateCredentials); + } + if (canLaunchWithoutPrompt(launch)) { await launchWithParams({}); } else { @@ -208,6 +216,7 @@ function LaunchButton({ resource, children }) { labels={labels} onLaunch={launchWithParams} onCancel={() => setShowLaunchPrompt(false)} + resourceDefaultCredentials={resourceCredentials} /> )} diff --git a/awx/ui/src/components/LaunchButton/LaunchButton.test.js b/awx/ui/src/components/LaunchButton/LaunchButton.test.js index fcd7b155c9..9f3853429a 100644 --- a/awx/ui/src/components/LaunchButton/LaunchButton.test.js +++ b/awx/ui/src/components/LaunchButton/LaunchButton.test.js @@ -47,6 +47,12 @@ describe('LaunchButton', () => { variables_needed_to_start: [], }, }); + JobTemplatesAPI.readCredentials.mockResolvedValue({ + data: { + count: 0, + results: [], + }, + }); }); afterEach(() => jest.clearAllMocks()); diff --git a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.js b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.js index 3b7f9e9d35..dba9741bf3 100644 --- a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.js +++ b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.js @@ -19,6 +19,7 @@ function PromptModalForm({ labels, surveyConfig, instanceGroups, + resourceDefaultCredentials, }) { const { setFieldTouched, values } = useFormikContext(); const [showDescription, setShowDescription] = useState(false); @@ -35,9 +36,9 @@ function PromptModalForm({ surveyConfig, resource, labels, - instanceGroups + instanceGroups, + resourceDefaultCredentials ); - const handleSubmit = async () => { const postValues = {}; const setValue = (key, value) => { diff --git a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js index 07563e1a2b..567717b299 100644 --- a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js +++ b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js @@ -69,6 +69,20 @@ describe('LaunchPrompt', () => { spec: [{ type: 'text', variable: 'foo' }], }, }); + JobTemplatesAPI.readCredentials.mockResolvedValue({ + data: { + results: [ + { + id: 5, + name: 'cred that prompts', + credential_type: 1, + inputs: { + password: 'ASK', + }, + }, + ], + }, + }); InstanceGroupsAPI.read.mockResolvedValue({ data: { results: [ @@ -212,6 +226,16 @@ describe('LaunchPrompt', () => { ], }, }} + resourceDefaultCredentials={[ + { + id: 5, + name: 'cred that prompts', + credential_type: 1, + inputs: { + password: 'ASK', + }, + }, + ]} onLaunch={noop} onCancel={noop} surveyConfig={{ @@ -289,6 +313,16 @@ describe('LaunchPrompt', () => { resource={resource} onLaunch={noop} onCancel={noop} + resourceDefaultCredentials={[ + { + id: 5, + name: 'cred that prompts', + credential_type: 1, + inputs: { + password: 'ASK', + }, + }, + ]} /> ); }); diff --git a/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.js b/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.js index 35d9ad19ab..b076c64849 100644 --- a/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.js +++ b/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.js @@ -1,6 +1,6 @@ import 'styled-components/macro'; import React, { useState, useCallback, useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import { t } from '@lingui/macro'; import { useField } from 'formik'; @@ -8,7 +8,7 @@ import styled from 'styled-components'; import { Alert, ToolbarItem } from '@patternfly/react-core'; import { CredentialsAPI, CredentialTypesAPI } from 'api'; import { getSearchableKeys } from 'components/PaginatedTable'; -import { getQSConfig, parseQueryString } from 'util/qs'; +import { getQSConfig, parseQueryString, updateQueryString } from 'util/qs'; import useRequest from 'hooks/useRequest'; import AnsibleSelect from '../../AnsibleSelect'; import OptionsList from '../../OptionsList'; @@ -31,18 +31,18 @@ function CredentialsStep({ allowCredentialsWithPasswords, defaultCredentials = [], }) { + const history = useHistory(); + const location = useLocation(); const [field, meta, helpers] = useField({ name: 'credentials', validate: (val) => credentialsValidator( allowCredentialsWithPasswords, val, - defaultCredentials + defaultCredentials ?? [] ), }); const [selectedType, setSelectedType] = useState(null); - const history = useHistory(); - const { result: types, error: typesError, @@ -104,12 +104,32 @@ function CredentialsStep({ credentialsValidator( allowCredentialsWithPasswords, field.value, - defaultCredentials + defaultCredentials ?? [] ) ); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); + const removeAllSearchTerms = (qsConfig) => { + const oldParams = parseQueryString(qsConfig, location.search); + Object.keys(oldParams).forEach((key) => { + oldParams[key] = null; + }); + const defaultParams = { + ...oldParams, + page: 1, + page_size: 5, + order_by: 'name', + }; + const qs = updateQueryString(qsConfig, location.search, defaultParams); + pushHistoryState(qs); + }; + + const pushHistoryState = (qs) => { + const { pathname } = history.location; + history.push(qs ? `${pathname}?${qs}` : pathname); + }; + if (isTypesLoading) { return ; } @@ -154,9 +174,7 @@ function CredentialsStep({ value={selectedType && selectedType.id} onChange={(e, id) => { // Reset query params when the category of credentials is changed - history.replace({ - search: '', - }); + removeAllSearchTerms(QS_CONFIG); setSelectedType(types.find((o) => o.id === parseInt(id, 10))); }} /> diff --git a/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.test.js b/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.test.js index 8c17ce432d..31eeea9feb 100644 --- a/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.test.js +++ b/awx/ui/src/components/LaunchPrompt/steps/CredentialsStep.test.js @@ -168,7 +168,9 @@ describe('CredentialsStep', () => { test('should reset query params (credential.page) when selected credential type is changed', async () => { let wrapper; const history = createMemoryHistory({ - initialEntries: ['?credential.page=2'], + initialEntries: [ + '?credential.page=2&credential.page_size=5&credential.order_by=name', + ], }); await act(async () => { wrapper = mountWithContexts( diff --git a/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js b/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js index 7cbba9be8a..66a05c9fb0 100644 --- a/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js +++ b/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js @@ -46,7 +46,8 @@ export default function useLaunchSteps( surveyConfig, resource, labels, - instanceGroups + instanceGroups, + resourceDefaultCredentials ) { const [visited, setVisited] = useState({}); const [isReady, setIsReady] = useState(false); @@ -56,7 +57,7 @@ export default function useLaunchSteps( useCredentialsStep( launchConfig, resource, - resource.summary_fields.credentials || [], + resourceDefaultCredentials, true ), useCredentialPasswordsStep(