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:
softwarefactory-project-zuul[bot] 2021-02-18 13:51:39 +00:00 committed by GitHub
commit efbbbc0bdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 13 deletions

View File

@ -29,6 +29,7 @@ function OrganizationLookup({
value,
history,
autoPopulate,
isDisabled,
}) {
const autoPopulateLookup = useAutoPopulateLookup(onChange);
@ -80,6 +81,7 @@ function OrganizationLookup({
label={i18n._(t`Organization`)}
>
<Lookup
isDisabled={isDisabled}
id="organization"
header={i18n._(t`Organization`)}
value={value}
@ -139,6 +141,7 @@ OrganizationLookup.propTypes = {
required: bool,
value: Organization,
autoPopulate: bool,
isDisabled: bool,
};
OrganizationLookup.defaultProps = {
@ -148,6 +151,7 @@ OrganizationLookup.defaultProps = {
required: false,
value: null,
autoPopulate: false,
isDisabled: false,
};
export { OrganizationLookup as _OrganizationLookup };

View File

@ -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 { object } from 'prop-types';
import { CardBody } from '../../../components/Card';
@ -6,15 +6,20 @@ import {
CredentialsAPI,
CredentialInputSourcesAPI,
CredentialTypesAPI,
OrganizationsAPI,
UsersAPI,
} from '../../../api';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import CredentialForm from '../shared/CredentialForm';
import useRequest from '../../../util/useRequest';
import { useConfig } from '../../../contexts/Config';
function CredentialEdit({ credential, me }) {
function CredentialEdit({ credential }) {
const history = useHistory();
const { id: credId } = useParams();
const { me = {} } = useConfig();
const [isOrgLookupDisabled, setIsOrgLookupDisabled] = useState(false);
const { error: submitError, request: submitRequest, result } = useRequest(
useCallback(
@ -81,8 +86,11 @@ function CredentialEdit({ credential, me }) {
// can send only one of org, user, team
if (organization?.id) {
modifiedData.organization = organization.id;
} else if (me?.id) {
modifiedData.user = me.id;
} else {
modifiedData.organization = null;
if (me?.id) {
modifiedData.user = me.id;
}
}
const [{ data }] = await Promise.all([
CredentialsAPI.update(credId, modifiedData),
@ -114,10 +122,22 @@ function CredentialEdit({ credential, me }) {
{
data: { results },
},
{
data: { count: adminOrgCount },
},
{
data: { count: credentialAdminCount },
},
] = await Promise.all([
CredentialTypesAPI.read({ page_size: 200 }),
CredentialsAPI.readInputSources(credId),
UsersAPI.readAdminOfOrganizations(me.id),
OrganizationsAPI.read({
page_size: 1,
role_level: 'credential_admin_role',
}),
]);
setIsOrgLookupDisabled(!(adminOrgCount || credentialAdminCount));
const credTypes = data.results;
if (data.next && data.next.includes('page=2')) {
const {
@ -137,7 +157,7 @@ function CredentialEdit({ credential, me }) {
return inputSourcesMap;
}, {});
return { credentialTypes: creds, loadedInputSources: inputSources };
}, [credId]),
}, [credId, me.id]),
{ credentialTypes: {}, loadedInputSources: {} }
);
@ -171,6 +191,7 @@ function CredentialEdit({ credential, me }) {
credentialTypes={credentialTypes}
inputSources={loadedInputSources}
submitError={submitError}
isOrgLookupDisabled={isOrgLookupDisabled}
/>
</CardBody>
);

View File

@ -10,6 +10,8 @@ import {
CredentialsAPI,
CredentialInputSourcesAPI,
CredentialTypesAPI,
OrganizationsAPI,
UsersAPI,
} from '../../../api';
import CredentialEdit from './CredentialEdit';
@ -114,6 +116,25 @@ const mockCredential = {
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({
data: {
results: [
@ -310,7 +331,12 @@ describe('<CredentialEdit />', () => {
wrapper = mountWithContexts(
<CredentialEdit credential={mockCredential} />,
{
context: { router: { history } },
context: {
router: { history },
me: {
id: 1,
},
},
}
);
});
@ -377,6 +403,7 @@ describe('<CredentialEdit />', () => {
expect(CredentialsAPI.update).toHaveBeenCalledWith(3, {
user: 1,
name: 'foo',
organization: null,
description: 'bar',
credential_type: '1',
inputs: {
@ -439,6 +466,7 @@ describe('<CredentialEdit />', () => {
).toBe('$encrypted$');
});
});
describe('Initial GET request fails', () => {
test('shows error when initial GET request fails', async () => {
CredentialTypesAPI.read.mockRejectedValue(new Error());

View File

@ -1,8 +1,8 @@
import React, { useCallback, useState } from 'react';
import { func, shape } from 'prop-types';
import { Formik, useField, useFormikContext } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { arrayOf, func, object, shape } from 'prop-types';
import {
ActionGroup,
Button,
@ -136,6 +136,7 @@ function CredentialFormFields({ i18n, credentialTypes }) {
touched={orgMeta.touched}
error={orgMeta.error}
required={isGalaxyCredential}
isDisabled={initialValues.isOrgLookupDisabled}
/>
<FormGroup
fieldId="credential-Type"
@ -189,6 +190,7 @@ function CredentialForm({
onSubmit,
onCancel,
submitError,
isOrgLookupDisabled,
...rest
}) {
const [showExternalTestModal, setShowExternalTestModal] = useState(false);
@ -199,6 +201,7 @@ function CredentialForm({
credential_type: credential?.credential_type || '',
inputs: credential?.inputs || {},
passwordPrompts: {},
isOrgLookupDisabled: isOrgLookupDisabled || false,
};
Object.values(credentialTypes).forEach(credentialType => {
@ -311,18 +314,18 @@ function CredentialForm({
);
}
CredentialForm.proptype = {
CredentialForm.propTypes = {
handleSubmit: func.isRequired,
handleCancel: func.isRequired,
credentialTypes: shape({}).isRequired,
credential: shape({}),
inputSources: arrayOf(object),
inputSources: shape({}),
submitError: shape({}),
};
CredentialForm.defaultProps = {
credential: {},
inputSources: [],
inputSources: {},
submitError: null,
};

View File

@ -327,6 +327,27 @@ describe('<CredentialForm />', () => {
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 () => {
await act(async () => {
wrapper = mountWithContexts(

View File

@ -168,7 +168,7 @@ function ExternalTestModal({
);
}
ExternalTestModal.proptype = {
ExternalTestModal.propType = {
credential: shape({}),
credentialType: shape({}).isRequired,
credentialFormValues: shape({}).isRequired,

View File

@ -118,7 +118,7 @@ function InventoryEdit({ inventory }) {
);
}
InventoryEdit.proptype = {
InventoryEdit.propType = {
inventory: object.isRequired,
};

View File

@ -138,7 +138,7 @@ function InventoryForm({
);
}
InventoryForm.proptype = {
InventoryForm.propType = {
handleSubmit: func.isRequired,
handleCancel: func.isRequired,
instanceGroups: shape(),