mirror of
https://github.com/ansible/awx.git
synced 2026-02-24 22:46:01 -03:30
Merge pull request #9188 from nixocio/ui_issue_8707
Allow user to remove organization from credentials Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -29,6 +29,7 @@ function OrganizationLookup({
|
|||||||
value,
|
value,
|
||||||
history,
|
history,
|
||||||
autoPopulate,
|
autoPopulate,
|
||||||
|
isDisabled,
|
||||||
}) {
|
}) {
|
||||||
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ function OrganizationLookup({
|
|||||||
label={i18n._(t`Organization`)}
|
label={i18n._(t`Organization`)}
|
||||||
>
|
>
|
||||||
<Lookup
|
<Lookup
|
||||||
|
isDisabled={isDisabled}
|
||||||
id="organization"
|
id="organization"
|
||||||
header={i18n._(t`Organization`)}
|
header={i18n._(t`Organization`)}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -139,6 +141,7 @@ OrganizationLookup.propTypes = {
|
|||||||
required: bool,
|
required: bool,
|
||||||
value: Organization,
|
value: Organization,
|
||||||
autoPopulate: bool,
|
autoPopulate: bool,
|
||||||
|
isDisabled: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
OrganizationLookup.defaultProps = {
|
OrganizationLookup.defaultProps = {
|
||||||
@@ -148,6 +151,7 @@ OrganizationLookup.defaultProps = {
|
|||||||
required: false,
|
required: false,
|
||||||
value: null,
|
value: null,
|
||||||
autoPopulate: false,
|
autoPopulate: false,
|
||||||
|
isDisabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OrganizationLookup as _OrganizationLookup };
|
export { OrganizationLookup as _OrganizationLookup };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { object } from 'prop-types';
|
import { object } from 'prop-types';
|
||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
@@ -6,15 +6,20 @@ import {
|
|||||||
CredentialsAPI,
|
CredentialsAPI,
|
||||||
CredentialInputSourcesAPI,
|
CredentialInputSourcesAPI,
|
||||||
CredentialTypesAPI,
|
CredentialTypesAPI,
|
||||||
|
OrganizationsAPI,
|
||||||
|
UsersAPI,
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
import ContentError from '../../../components/ContentError';
|
import ContentError from '../../../components/ContentError';
|
||||||
import ContentLoading from '../../../components/ContentLoading';
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
import CredentialForm from '../shared/CredentialForm';
|
import CredentialForm from '../shared/CredentialForm';
|
||||||
import useRequest from '../../../util/useRequest';
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import { useConfig } from '../../../contexts/Config';
|
||||||
|
|
||||||
function CredentialEdit({ credential, me }) {
|
function CredentialEdit({ credential }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id: credId } = useParams();
|
const { id: credId } = useParams();
|
||||||
|
const { me = {} } = useConfig();
|
||||||
|
const [isOrgLookupDisabled, setIsOrgLookupDisabled] = useState(false);
|
||||||
|
|
||||||
const { error: submitError, request: submitRequest, result } = useRequest(
|
const { error: submitError, request: submitRequest, result } = useRequest(
|
||||||
useCallback(
|
useCallback(
|
||||||
@@ -81,8 +86,11 @@ function CredentialEdit({ credential, me }) {
|
|||||||
// can send only one of org, user, team
|
// can send only one of org, user, team
|
||||||
if (organization?.id) {
|
if (organization?.id) {
|
||||||
modifiedData.organization = organization.id;
|
modifiedData.organization = organization.id;
|
||||||
} else if (me?.id) {
|
} else {
|
||||||
modifiedData.user = me.id;
|
modifiedData.organization = null;
|
||||||
|
if (me?.id) {
|
||||||
|
modifiedData.user = me.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const [{ data }] = await Promise.all([
|
const [{ data }] = await Promise.all([
|
||||||
CredentialsAPI.update(credId, modifiedData),
|
CredentialsAPI.update(credId, modifiedData),
|
||||||
@@ -114,10 +122,22 @@ function CredentialEdit({ credential, me }) {
|
|||||||
{
|
{
|
||||||
data: { results },
|
data: { results },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: { count: adminOrgCount },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { count: credentialAdminCount },
|
||||||
|
},
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
CredentialTypesAPI.read({ page_size: 200 }),
|
CredentialTypesAPI.read({ page_size: 200 }),
|
||||||
CredentialsAPI.readInputSources(credId),
|
CredentialsAPI.readInputSources(credId),
|
||||||
|
UsersAPI.readAdminOfOrganizations(me.id),
|
||||||
|
OrganizationsAPI.read({
|
||||||
|
page_size: 1,
|
||||||
|
role_level: 'credential_admin_role',
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
setIsOrgLookupDisabled(!(adminOrgCount || credentialAdminCount));
|
||||||
const credTypes = data.results;
|
const credTypes = data.results;
|
||||||
if (data.next && data.next.includes('page=2')) {
|
if (data.next && data.next.includes('page=2')) {
|
||||||
const {
|
const {
|
||||||
@@ -137,7 +157,7 @@ function CredentialEdit({ credential, me }) {
|
|||||||
return inputSourcesMap;
|
return inputSourcesMap;
|
||||||
}, {});
|
}, {});
|
||||||
return { credentialTypes: creds, loadedInputSources: inputSources };
|
return { credentialTypes: creds, loadedInputSources: inputSources };
|
||||||
}, [credId]),
|
}, [credId, me.id]),
|
||||||
{ credentialTypes: {}, loadedInputSources: {} }
|
{ credentialTypes: {}, loadedInputSources: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,6 +191,7 @@ function CredentialEdit({ credential, me }) {
|
|||||||
credentialTypes={credentialTypes}
|
credentialTypes={credentialTypes}
|
||||||
inputSources={loadedInputSources}
|
inputSources={loadedInputSources}
|
||||||
submitError={submitError}
|
submitError={submitError}
|
||||||
|
isOrgLookupDisabled={isOrgLookupDisabled}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
CredentialsAPI,
|
CredentialsAPI,
|
||||||
CredentialInputSourcesAPI,
|
CredentialInputSourcesAPI,
|
||||||
CredentialTypesAPI,
|
CredentialTypesAPI,
|
||||||
|
OrganizationsAPI,
|
||||||
|
UsersAPI,
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
import CredentialEdit from './CredentialEdit';
|
import CredentialEdit from './CredentialEdit';
|
||||||
|
|
||||||
@@ -114,6 +116,25 @@ const mockCredential = {
|
|||||||
kubernetes: false,
|
kubernetes: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UsersAPI.readAdminOfOrganizations.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'org',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [{ id: 1 }],
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: [
|
results: [
|
||||||
@@ -310,7 +331,12 @@ describe('<CredentialEdit />', () => {
|
|||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<CredentialEdit credential={mockCredential} />,
|
<CredentialEdit credential={mockCredential} />,
|
||||||
{
|
{
|
||||||
context: { router: { history } },
|
context: {
|
||||||
|
router: { history },
|
||||||
|
me: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -377,6 +403,7 @@ describe('<CredentialEdit />', () => {
|
|||||||
expect(CredentialsAPI.update).toHaveBeenCalledWith(3, {
|
expect(CredentialsAPI.update).toHaveBeenCalledWith(3, {
|
||||||
user: 1,
|
user: 1,
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
|
organization: null,
|
||||||
description: 'bar',
|
description: 'bar',
|
||||||
credential_type: '1',
|
credential_type: '1',
|
||||||
inputs: {
|
inputs: {
|
||||||
@@ -439,6 +466,7 @@ describe('<CredentialEdit />', () => {
|
|||||||
).toBe('$encrypted$');
|
).toBe('$encrypted$');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Initial GET request fails', () => {
|
describe('Initial GET request fails', () => {
|
||||||
test('shows error when initial GET request fails', async () => {
|
test('shows error when initial GET request fails', async () => {
|
||||||
CredentialTypesAPI.read.mockRejectedValue(new Error());
|
CredentialTypesAPI.read.mockRejectedValue(new Error());
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { func, shape } from 'prop-types';
|
||||||
import { Formik, useField, useFormikContext } from 'formik';
|
import { Formik, useField, useFormikContext } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { arrayOf, func, object, shape } from 'prop-types';
|
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
Button,
|
Button,
|
||||||
@@ -136,6 +136,7 @@ function CredentialFormFields({ i18n, credentialTypes }) {
|
|||||||
touched={orgMeta.touched}
|
touched={orgMeta.touched}
|
||||||
error={orgMeta.error}
|
error={orgMeta.error}
|
||||||
required={isGalaxyCredential}
|
required={isGalaxyCredential}
|
||||||
|
isDisabled={initialValues.isOrgLookupDisabled}
|
||||||
/>
|
/>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="credential-Type"
|
fieldId="credential-Type"
|
||||||
@@ -189,6 +190,7 @@ function CredentialForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitError,
|
submitError,
|
||||||
|
isOrgLookupDisabled,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const [showExternalTestModal, setShowExternalTestModal] = useState(false);
|
const [showExternalTestModal, setShowExternalTestModal] = useState(false);
|
||||||
@@ -199,6 +201,7 @@ function CredentialForm({
|
|||||||
credential_type: credential?.credential_type || '',
|
credential_type: credential?.credential_type || '',
|
||||||
inputs: credential?.inputs || {},
|
inputs: credential?.inputs || {},
|
||||||
passwordPrompts: {},
|
passwordPrompts: {},
|
||||||
|
isOrgLookupDisabled: isOrgLookupDisabled || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.values(credentialTypes).forEach(credentialType => {
|
Object.values(credentialTypes).forEach(credentialType => {
|
||||||
@@ -311,18 +314,18 @@ function CredentialForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialForm.proptype = {
|
CredentialForm.propTypes = {
|
||||||
handleSubmit: func.isRequired,
|
handleSubmit: func.isRequired,
|
||||||
handleCancel: func.isRequired,
|
handleCancel: func.isRequired,
|
||||||
credentialTypes: shape({}).isRequired,
|
credentialTypes: shape({}).isRequired,
|
||||||
credential: shape({}),
|
credential: shape({}),
|
||||||
inputSources: arrayOf(object),
|
inputSources: shape({}),
|
||||||
submitError: shape({}),
|
submitError: shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
CredentialForm.defaultProps = {
|
CredentialForm.defaultProps = {
|
||||||
credential: {},
|
credential: {},
|
||||||
inputSources: [],
|
inputSources: {},
|
||||||
submitError: null,
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -327,6 +327,27 @@ describe('<CredentialForm />', () => {
|
|||||||
machineFieldExpects();
|
machineFieldExpects();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('organization lookup should be disabled', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<CredentialForm
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
credential={machineCredential}
|
||||||
|
credentialTypes={credentialTypes}
|
||||||
|
isOrgLookupDisabled
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('CredentialFormFields')
|
||||||
|
.find('OrganizationLookup')
|
||||||
|
.prop('isDisabled')
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('should display form fields for source control credential properly', async () => {
|
test('should display form fields for source control credential properly', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ function ExternalTestModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalTestModal.proptype = {
|
ExternalTestModal.propType = {
|
||||||
credential: shape({}),
|
credential: shape({}),
|
||||||
credentialType: shape({}).isRequired,
|
credentialType: shape({}).isRequired,
|
||||||
credentialFormValues: shape({}).isRequired,
|
credentialFormValues: shape({}).isRequired,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function InventoryEdit({ inventory }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InventoryEdit.proptype = {
|
InventoryEdit.propType = {
|
||||||
inventory: object.isRequired,
|
inventory: object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ function InventoryForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InventoryForm.proptype = {
|
InventoryForm.propType = {
|
||||||
handleSubmit: func.isRequired,
|
handleSubmit: func.isRequired,
|
||||||
handleCancel: func.isRequired,
|
handleCancel: func.isRequired,
|
||||||
instanceGroups: shape(),
|
instanceGroups: shape(),
|
||||||
|
|||||||
Reference in New Issue
Block a user