diff --git a/awx/ui_next/src/api/models/CredentialTypes.js b/awx/ui_next/src/api/models/CredentialTypes.js
index 65906cdcbd..d2d993091c 100644
--- a/awx/ui_next/src/api/models/CredentialTypes.js
+++ b/awx/ui_next/src/api/models/CredentialTypes.js
@@ -5,6 +5,28 @@ class CredentialTypes extends Base {
super(http);
this.baseUrl = '/api/v2/credential_types/';
}
+
+ async loadAllTypes(
+ acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault']
+ ) {
+ const pageSize = 200;
+ // The number of credential types a user can have is unlimited. In practice, it is unlikely for
+ // users to have more than a page at the maximum request size.
+ const {
+ data: { next, results },
+ } = await this.read({ page_size: pageSize });
+ let nextResults = [];
+ if (next) {
+ const { data } = await this.read({
+ page_size: pageSize,
+ page: 2,
+ });
+ nextResults = data.results;
+ }
+ return results
+ .concat(nextResults)
+ .filter(type => acceptableKinds.includes(type.kind));
+ }
}
export default CredentialTypes;
diff --git a/awx/ui_next/src/api/models/CredentialTypes.test.js b/awx/ui_next/src/api/models/CredentialTypes.test.js
new file mode 100644
index 0000000000..5cb038912e
--- /dev/null
+++ b/awx/ui_next/src/api/models/CredentialTypes.test.js
@@ -0,0 +1,65 @@
+import CredentialTypes from './CredentialTypes';
+
+const typesData = [{ id: 1, kind: 'machine' }, { id: 2, kind: 'cloud' }];
+
+describe('CredentialTypesAPI', () => {
+ test('should load all types', async () => {
+ const getPromise = () =>
+ Promise.resolve({
+ data: {
+ results: typesData,
+ },
+ });
+ const mockHttp = { get: jest.fn(getPromise) };
+ const CredentialTypesAPI = new CredentialTypes(mockHttp);
+
+ const types = await CredentialTypesAPI.loadAllTypes();
+
+ expect(mockHttp.get).toHaveBeenCalledTimes(1);
+ expect(mockHttp.get.mock.calls[0]).toEqual([
+ `/api/v2/credential_types/`,
+ { params: { page_size: 200 } },
+ ]);
+ expect(types).toEqual(typesData);
+ });
+
+ test('should load all types (2 pages)', async () => {
+ const getPromise = () =>
+ Promise.resolve({
+ data: {
+ results: typesData,
+ next: 2,
+ },
+ });
+ const mockHttp = { get: jest.fn(getPromise) };
+ const CredentialTypesAPI = new CredentialTypes(mockHttp);
+
+ const types = await CredentialTypesAPI.loadAllTypes();
+
+ expect(mockHttp.get).toHaveBeenCalledTimes(2);
+ expect(mockHttp.get.mock.calls[0]).toEqual([
+ `/api/v2/credential_types/`,
+ { params: { page_size: 200 } },
+ ]);
+ expect(mockHttp.get.mock.calls[1]).toEqual([
+ `/api/v2/credential_types/`,
+ { params: { page_size: 200, page: 2 } },
+ ]);
+ expect(types).toHaveLength(4);
+ });
+
+ test('should filter by acceptable kinds', async () => {
+ const getPromise = () =>
+ Promise.resolve({
+ data: {
+ results: typesData,
+ },
+ });
+ const mockHttp = { get: jest.fn(getPromise) };
+ const CredentialTypesAPI = new CredentialTypes(mockHttp);
+
+ const types = await CredentialTypesAPI.loadAllTypes(['machine']);
+
+ expect(types).toEqual([typesData[0]]);
+ });
+});
diff --git a/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx b/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx
index 1d232f0a87..7d48a7bba9 100644
--- a/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx
+++ b/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx
@@ -88,8 +88,8 @@ class LaunchButton extends React.Component {
const { history, resource } = this.props;
const jobPromise =
resource.type === 'workflow_job_template'
- ? WorkflowJobTemplatesAPI.launch(resource.id, params)
- : JobTemplatesAPI.launch(resource.id, params);
+ ? WorkflowJobTemplatesAPI.launch(resource.id, params || {})
+ : JobTemplatesAPI.launch(resource.id, params || {});
const { data: job } = await jobPromise;
history.push(
diff --git a/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.jsx
index 10872e311c..a389db0cff 100644
--- a/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.jsx
@@ -1,7 +1,171 @@
-import React from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
+import { useHistory } from 'react-router-dom';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { useField } from 'formik';
+import { ToolbarItem } from '@patternfly/react-core';
+import { CredentialsAPI, CredentialTypesAPI } from '@api';
+import AnsibleSelect from '@components/AnsibleSelect';
+import OptionsList from '@components/OptionsList';
+import ContentLoading from '@components/ContentLoading';
+import CredentialChip from '@components/CredentialChip';
+import ContentError from '@components/ContentError';
+import { getQSConfig, parseQueryString } from '@util/qs';
+import useRequest from '@util/useRequest';
-function CredentialsStep() {
- return
;
+const QS_CONFIG = getQSConfig('inventory', {
+ page: 1,
+ page_size: 5,
+ order_by: 'name',
+});
+
+function CredentialsStep({ i18n }) {
+ const [field, , helpers] = useField('credentials');
+ const [selectedType, setSelectedType] = useState(null);
+ const history = useHistory();
+
+ const {
+ result: types,
+ error: typesError,
+ isLoading: isTypesLoading,
+ request: fetchTypes,
+ } = useRequest(
+ useCallback(async () => {
+ const loadedTypes = await CredentialTypesAPI.loadAllTypes();
+ if (loadedTypes.length) {
+ const match =
+ loadedTypes.find(type => type.kind === 'ssh') || loadedTypes[0];
+ setSelectedType(match);
+ }
+ return loadedTypes;
+ }, []),
+ []
+ );
+
+ useEffect(() => {
+ fetchTypes();
+ }, [fetchTypes]);
+
+ const {
+ result: { credentials, count },
+ error: credentialsError,
+ isLoading: isCredentialsLoading,
+ request: fetchCredentials,
+ } = useRequest(
+ useCallback(async () => {
+ if (!selectedType) {
+ return { credentials: [], count: 0 };
+ }
+ const params = parseQueryString(QS_CONFIG, history.location.search);
+ const { data } = await CredentialsAPI.read({
+ ...params,
+ credential_type: selectedType.id,
+ });
+ return {
+ credentials: data.results,
+ count: data.count,
+ };
+ }, [selectedType, history.location.search]),
+ { credentials: [], count: 0 }
+ );
+
+ useEffect(() => {
+ fetchCredentials();
+ }, [fetchCredentials]);
+
+ if (isTypesLoading) {
+ return ;
+ }
+
+ if (typesError || credentialsError) {
+ return ;
+ }
+
+ const isVault = selectedType?.kind === 'vault';
+
+ const renderChip = ({ item, removeItem, canDelete }) => (
+ removeItem(item)}
+ isReadOnly={!canDelete}
+ credential={item}
+ />
+ );
+
+ return (
+ <>
+ {types && types.length > 0 && (
+
+
+ {i18n._(t`Selected Category`)}
+
+ ({
+ key: type.id,
+ value: type.id,
+ label: type.name,
+ isDisabled: false,
+ }))}
+ value={selectedType && selectedType.id}
+ onChange={(e, id) => {
+ setSelectedType(types.find(o => o.id === parseInt(id, 10)));
+ }}
+ />
+
+ )}
+ {!isCredentialsLoading && (
+ {
+ const hasSameVaultID = val =>
+ val?.inputs?.vault_id !== undefined &&
+ val?.inputs?.vault_id === item?.inputs?.vault_id;
+ const hasSameKind = val => val.kind === item.kind;
+ const newItems = field.value.filter(i =>
+ isVault ? !hasSameVaultID(i) : !hasSameKind(i)
+ );
+ newItems.push(item);
+ helpers.setValue(newItems);
+ }}
+ deselectItem={item => {
+ helpers.setValue(field.value.filter(i => i.id !== item.id));
+ }}
+ renderItemChip={renderChip}
+ />
+ )}
+ >
+ );
}
-export default CredentialsStep;
+export default withI18n()(CredentialsStep);
diff --git a/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.test.jsx b/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.test.jsx
new file mode 100644
index 0000000000..5038482125
--- /dev/null
+++ b/awx/ui_next/src/components/LaunchPrompt/CredentialsStep.test.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { Formik } from 'formik';
+import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import CredentialsStep from './CredentialsStep';
+import { CredentialsAPI, CredentialTypesAPI } from '@api';
+
+jest.mock('@api/models/CredentialTypes');
+jest.mock('@api/models/Credentials');
+
+const types = [
+ { id: 1, kind: 'ssh', name: 'SSH' },
+ { id: 2, kind: 'cloud', name: 'Ansible Tower' },
+ { id: 3, kind: 'vault', name: 'Vault' },
+];
+
+const credentials = [
+ { id: 1, kind: 'cloud', name: 'Cred 1', url: 'www.google.com' },
+ { id: 2, kind: 'ssh', name: 'Cred 2', url: 'www.google.com' },
+ { id: 3, kind: 'Ansible', name: 'Cred 3', url: 'www.google.com' },
+ { id: 4, kind: 'Machine', name: 'Cred 4', url: 'www.google.com' },
+ { id: 5, kind: 'Machine', name: 'Cred 5', url: 'www.google.com' },
+];
+
+describe('CredentialsStep', () => {
+ beforeEach(() => {
+ CredentialTypesAPI.loadAllTypes.mockResolvedValue(types);
+ CredentialsAPI.read.mockResolvedValue({
+ data: {
+ results: credentials,
+ count: 5,
+ },
+ });
+ });
+
+ test('should load credentials', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+ wrapper.update();
+
+ expect(CredentialsAPI.read).toHaveBeenCalled();
+ expect(wrapper.find('OptionsList').prop('options')).toEqual(credentials);
+ });
+
+ test('should load credentials for selected type', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+ wrapper.update();
+
+ expect(CredentialsAPI.read).toHaveBeenCalledWith({
+ credential_type: 1,
+ order_by: 'name',
+ page: 1,
+ page_size: 5,
+ });
+
+ await act(async () => {
+ wrapper.find('AnsibleSelect').invoke('onChange')({}, 2);
+ });
+ expect(CredentialsAPI.read).toHaveBeenCalledWith({
+ credential_type: 2,
+ order_by: 'name',
+ page: 1,
+ page_size: 5,
+ });
+ });
+});
diff --git a/awx/ui_next/src/components/LaunchPrompt/InventoryStep.jsx b/awx/ui_next/src/components/LaunchPrompt/InventoryStep.jsx
index 696dc445bc..e34dc40664 100644
--- a/awx/ui_next/src/components/LaunchPrompt/InventoryStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/InventoryStep.jsx
@@ -8,6 +8,7 @@ import { getQSConfig, parseQueryString } from '@util/qs';
import useRequest from '@util/useRequest';
import OptionsList from '@components/OptionsList';
import ContentLoading from '@components/ContentLoading';
+import ContentError from '@components/ContentError';
const QS_CONFIG = getQSConfig('inventory', {
page: 1,
@@ -16,12 +17,12 @@ const QS_CONFIG = getQSConfig('inventory', {
});
function InventoryStep({ i18n }) {
- const history = useHistory();
const [field, , helpers] = useField('inventory');
+ const history = useHistory();
const {
isLoading,
- // error,
+ error,
result: { inventories, count },
request: fetchInventories,
} = useRequest(
@@ -46,6 +47,9 @@ function InventoryStep({ i18n }) {
if (isLoading) {
return ;
}
+ if (error) {
+ return ;
+ }
return (
,
});
}
- // TODO: match old UI Logic:
- // if (vm.promptDataClone.launchConf.ask_credential_on_launch ||
- // (_.has(vm, 'promptDataClone.prompts.credentials.passwords.vault') &&
- // vm.promptDataClone.prompts.credentials.passwords.vault.length > 0) ||
- // _.has(vm, 'promptDataClone.prompts.credentials.passwords.ssh_key_unlock') ||
- // _.has(vm, 'promptDataClone.prompts.credentials.passwords.become_password') ||
- // _.has(vm, 'promptDataClone.prompts.credentials.passwords.ssh_password')
- // ) {
if (config.ask_credential_on_launch) {
initialValues.credentials = resource?.summary_fields?.credentials || [];
steps.push({
- name: i18n._(t`Credential`),
+ name: i18n._(t`Credentials`),
component: ,
});
}
+
+ // TODO: Add Credential Passwords step
+
+ if (config.ask_job_type_on_launch) {
+ initialValues.job_type = resource.job_type || '';
+ }
+ if (config.ask_limit_on_launch) {
+ initialValues.limit = resource.limit || '';
+ }
+ if (config.ask_verbosity_on_launch) {
+ initialValues.verbosity = resource.verbosity || 0;
+ }
+ if (config.ask_tags_on_launch) {
+ initialValues.job_tags = resource.job_tags || '';
+ }
+ if (config.ask_skip_tags_on_launch) {
+ initialValues.skip_tags = resource.skip_tags || '';
+ }
+ if (config.ask_variables_on_launch) {
+ initialValues.extra_vars = resource.extra_vars || '---';
+ }
+ if (config.ask_scm_branch_on_launch) {
+ initialValues.scm_branch = resource.scm_branch || '';
+ }
+ if (config.ask_diff_mode_on_launch) {
+ initialValues.diff_mode = resource.diff_mode || false;
+ }
if (
- config.ask_scm_branch_on_launch ||
- (config.ask_variables_on_launch && !config.ignore_ask_variables) ||
- config.ask_tags_on_launch ||
- config.ask_diff_mode_on_launch ||
- config.ask_skip_tags_on_launch ||
config.ask_job_type_on_launch ||
config.ask_limit_on_launch ||
- config.ask_verbosity_on_launch
+ config.ask_verbosity_on_launch ||
+ config.ask_tags_on_launch ||
+ config.ask_skip_tags_on_launch ||
+ config.ask_variables_on_launch ||
+ config.ask_scm_branch_on_launch ||
+ config.ask_diff_mode_on_launch
) {
steps.push({
name: i18n._(t`Other Prompts`),
@@ -63,9 +82,18 @@ function LaunchPrompt({ config, resource, onLaunch, onCancel, i18n }) {
const submit = values => {
const postValues = {};
- if (values.inventory) {
- postValues.inventory_id = values.inventory.id;
- }
+ const setValue = (key, value) => {
+ if (typeof value !== 'undefined' && value !== null) {
+ postValues[key] = value;
+ }
+ };
+ setValue('inventory_id', values.inventory?.id);
+ setValue('credentials', values.credentials?.map(c => c.id));
+ setValue('job_type', values.job_type);
+ setValue('limit', values.limit);
+ setValue('job_tags', values.job_tags);
+ setValue('skip_tags', values.skip_tags);
+ setValue('extra_vars', values.extra_vars);
onLaunch(postValues);
};
diff --git a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.test.jsx b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.test.jsx
index edbb0cd7dc..3a490db6bc 100644
--- a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.test.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.test.jsx
@@ -3,6 +3,8 @@ import { act, isElementOfType } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import LaunchPrompt from './LaunchPrompt';
import InventoryStep from './InventoryStep';
+import CredentialsStep from './CredentialsStep';
+import OtherPromptsStep from './OtherPromptsStep';
import PreviewStep from './PreviewStep';
import { InventoriesAPI } from '@api';
@@ -69,7 +71,7 @@ describe('LaunchPrompt', () => {
expect(steps).toHaveLength(5);
expect(steps[0].name).toEqual('Inventory');
- expect(steps[1].name).toEqual('Credential');
+ expect(steps[1].name).toEqual('Credentials');
expect(steps[2].name).toEqual('Other Prompts');
expect(steps[3].name).toEqual('Survey');
expect(steps[4].name).toEqual('Preview');
@@ -97,4 +99,50 @@ describe('LaunchPrompt', () => {
expect(isElementOfType(steps[0].component, InventoryStep)).toEqual(true);
expect(isElementOfType(steps[1].component, PreviewStep)).toEqual(true);
});
+
+ test('should add credentials step', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ const steps = wrapper.find('Wizard').prop('steps');
+
+ expect(steps).toHaveLength(2);
+ expect(steps[0].name).toEqual('Credentials');
+ expect(isElementOfType(steps[0].component, CredentialsStep)).toEqual(true);
+ expect(isElementOfType(steps[1].component, PreviewStep)).toEqual(true);
+ });
+
+ test('should add other prompts step', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ const steps = wrapper.find('Wizard').prop('steps');
+
+ expect(steps).toHaveLength(2);
+ expect(steps[0].name).toEqual('Other Prompts');
+ expect(isElementOfType(steps[0].component, OtherPromptsStep)).toEqual(true);
+ expect(isElementOfType(steps[1].component, PreviewStep)).toEqual(true);
+ });
});
diff --git a/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.jsx
index c0b8e6ec14..0989368652 100644
--- a/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.jsx
@@ -1,7 +1,180 @@
import React from 'react';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { useField } from 'formik';
+import { Form, FormGroup, Switch } from '@patternfly/react-core';
+import FormField, { FieldTooltip } from '@components/FormField';
+import { TagMultiSelect } from '@components/MultiSelect';
+import AnsibleSelect from '@components/AnsibleSelect';
+import { VariablesField } from '@components/CodeMirrorInput';
+import styled from 'styled-components';
-function InventoryStep() {
- return ;
+const FieldHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding-bottom: var(--pf-c-form__label--PaddingBottom);
+
+ label {
+ --pf-c-form__label--PaddingBottom: 0px;
+ }
+`;
+
+function OtherPromptsStep({ config, i18n }) {
+ return (
+
+ );
}
-export default InventoryStep;
+function JobTypeField({ i18n }) {
+ const [field, meta, helpers] = useField('job_type');
+ const options = [
+ {
+ value: '',
+ key: '',
+ label: i18n._(t`Choose a job type`),
+ isDisabled: true,
+ },
+ { value: 'run', key: 'run', label: i18n._(t`Run`), isDisabled: false },
+ {
+ value: 'check',
+ key: 'check',
+ label: i18n._(t`Check`),
+ isDisabled: false,
+ },
+ ];
+ const isValid = !(meta.touched && meta.error);
+ return (
+
+
+ helpers.setValue(value)}
+ />
+
+ );
+}
+
+function VerbosityField({ i18n }) {
+ const [field, meta, helpers] = useField('verbosity');
+ const options = [
+ { value: '0', key: '0', label: i18n._(t`0 (Normal)`) },
+ { value: '1', key: '1', label: i18n._(t`1 (Verbose)`) },
+ { value: '2', key: '2', label: i18n._(t`2 (More Verbose)`) },
+ { value: '3', key: '3', label: i18n._(t`3 (Debug)`) },
+ { value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) },
+ ];
+ const isValid = !(meta.touched && meta.error);
+
+ return (
+
+
+ helpers.setValue(value)}
+ />
+
+ );
+}
+
+function ShowChangesToggle({ i18n }) {
+ const [field, , helpers] = useField('diff_mode');
+ return (
+
+
+ {' '}
+
+
+
+
+ );
+}
+
+function TagField({ id, name, label, tooltip }) {
+ const [field, , helpers] = useField(name);
+ return (
+
+
+
+
+ );
+}
+
+export default withI18n()(OtherPromptsStep);
diff --git a/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.test.jsx b/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.test.jsx
new file mode 100644
index 0000000000..58f0856131
--- /dev/null
+++ b/awx/ui_next/src/components/LaunchPrompt/OtherPromptsStep.test.jsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { Formik } from 'formik';
+import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import OtherPromptsStep from './OtherPromptsStep';
+
+describe('OtherPromptsStep', () => {
+ test('should render job type field', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ expect(wrapper.find('JobTypeField')).toHaveLength(1);
+ expect(
+ wrapper.find('JobTypeField AnsibleSelect').prop('data')
+ ).toHaveLength(3);
+ expect(wrapper.find('JobTypeField AnsibleSelect').prop('value')).toEqual(
+ 'run'
+ );
+ });
+
+ test('should render limit field', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ expect(wrapper.find('FormField#prompt-limit')).toHaveLength(1);
+ expect(wrapper.find('FormField#prompt-limit input').prop('name')).toEqual(
+ 'limit'
+ );
+ });
+
+ test('should render verbosity field', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ expect(wrapper.find('VerbosityField')).toHaveLength(1);
+ expect(
+ wrapper.find('VerbosityField AnsibleSelect').prop('data')
+ ).toHaveLength(5);
+ });
+
+ test('should render show changes toggle', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ expect(wrapper.find('ShowChangesToggle')).toHaveLength(1);
+ expect(wrapper.find('ShowChangesToggle Switch').prop('isChecked')).toEqual(
+ true
+ );
+ });
+});
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
index 58bdf359cb..c153b1d610 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
@@ -17,27 +17,6 @@ const QS_CONFIG = getQSConfig('credentials', {
order_by: 'name',
});
-async function loadCredentialTypes() {
- const pageSize = 200;
- const acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault'];
- // The number of credential types a user can have is unlimited. In practice, it is unlikely for
- // users to have more than a page at the maximum request size.
- const {
- data: { next, results },
- } = await CredentialTypesAPI.read({ page_size: pageSize });
- let nextResults = [];
- if (next) {
- const { data } = await CredentialTypesAPI.read({
- page_size: pageSize,
- page: 2,
- });
- nextResults = data.results;
- }
- return results
- .concat(nextResults)
- .filter(type => acceptableKinds.includes(type.kind));
-}
-
async function loadCredentials(params, selectedCredentialTypeId) {
params.credential_type = selectedCredentialTypeId || 1;
const { data } = await CredentialsAPI.read(params);
@@ -54,7 +33,7 @@ function MultiCredentialsLookup(props) {
useEffect(() => {
(async () => {
try {
- const types = await loadCredentialTypes();
+ const types = await CredentialTypesAPI.loadAllTypes();
setCredentialTypes(types);
const match = types.find(type => type.kind === 'ssh') || types[0];
setSelectedType(match);
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
index fa8eb5a15f..08fcf87b18 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
@@ -18,21 +18,16 @@ describe('', () => {
];
beforeEach(() => {
- CredentialTypesAPI.read.mockResolvedValueOnce({
- data: {
- results: [
- {
- id: 400,
- kind: 'ssh',
- namespace: 'biz',
- name: 'Amazon Web Services',
- },
- { id: 500, kind: 'vault', namespace: 'buzz', name: 'Vault' },
- { id: 600, kind: 'machine', namespace: 'fuzz', name: 'Machine' },
- ],
- count: 2,
+ CredentialTypesAPI.loadAllTypes.mockResolvedValueOnce([
+ {
+ id: 400,
+ kind: 'ssh',
+ namespace: 'biz',
+ name: 'Amazon Web Services',
},
- });
+ { id: 500, kind: 'vault', namespace: 'buzz', name: 'Vault' },
+ { id: 600, kind: 'machine', namespace: 'fuzz', name: 'Machine' },
+ ]);
CredentialsAPI.read.mockResolvedValueOnce({
data: {
results: [
@@ -52,7 +47,7 @@ describe('', () => {
wrapper.unmount();
});
- test('MultiCredentialsLookup renders properly', async () => {
+ test('should load credential types', async () => {
const onChange = jest.fn();
await act(async () => {
wrapper = mountWithContexts(
@@ -64,8 +59,9 @@ describe('', () => {
/>
);
});
+ wrapper.update();
expect(wrapper.find('MultiCredentialsLookup')).toHaveLength(1);
- expect(CredentialTypesAPI.read).toHaveBeenCalled();
+ expect(CredentialTypesAPI.loadAllTypes).toHaveBeenCalled();
});
test('onChange is called when you click to remove a credential from input', async () => {
@@ -118,12 +114,12 @@ describe('', () => {
count: 1,
},
});
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
+ expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
await act(async () => {
select.invoke('onChange')({}, 500);
});
wrapper.update();
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(3);
+ expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
expect(wrapper.find('OptionsList').prop('options')).toEqual([
{ id: 1, kind: 'cloud', name: 'New Cred', url: 'www.google.com' },
]);