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
8 changed files with 90 additions and 13 deletions

View File

@@ -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 };

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 { 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>
); );

View File

@@ -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());

View File

@@ -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,
}; };

View File

@@ -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(

View File

@@ -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,

View File

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

View File

@@ -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(),