Show client id/secret in modal after successful application add

This commit is contained in:
mabashian 2020-11-24 16:09:32 -05:00
parent be08e0ce69
commit 957ab9bf7c
7 changed files with 131 additions and 44 deletions

View File

@ -8,7 +8,7 @@ import ApplicationForm from '../shared/ApplicationForm';
import { ApplicationsAPI } from '../../../api';
import { CardBody } from '../../../components/Card';
function ApplicationAdd() {
function ApplicationAdd({ onSuccessfulAdd }) {
const history = useHistory();
const [submitError, setSubmitError] = useState(null);
@ -53,10 +53,9 @@ function ApplicationAdd() {
const handleSubmit = async ({ ...values }) => {
values.organization = values.organization.id;
try {
const {
data: { id },
} = await ApplicationsAPI.create(values);
history.push(`/applications/${id}/details`);
const { data } = await ApplicationsAPI.create(values);
onSuccessfulAdd(data);
history.push(`/applications/${data.id}/details`);
} catch (err) {
setSubmitError(err);
}

View File

@ -39,12 +39,16 @@ const options = {
},
};
const onSuccessfulAdd = jest.fn();
describe('<ApplicationAdd/>', () => {
let wrapper;
test('should render properly', async () => {
ApplicationsAPI.readOptions.mockResolvedValue(options);
await act(async () => {
wrapper = mountWithContexts(<ApplicationAdd />);
wrapper = mountWithContexts(
<ApplicationAdd onSuccessfulAdd={onSuccessfulAdd} />
);
});
expect(wrapper.find('ApplicationAdd').length).toBe(1);
expect(wrapper.find('ApplicationForm').length).toBe(1);
@ -59,9 +63,12 @@ describe('<ApplicationAdd/>', () => {
ApplicationsAPI.create.mockResolvedValue({ data: { id: 8 } });
await act(async () => {
wrapper = mountWithContexts(<ApplicationAdd />, {
context: { router: { history } },
});
wrapper = mountWithContexts(
<ApplicationAdd onSuccessfulAdd={onSuccessfulAdd} />,
{
context: { router: { history } },
}
);
});
await act(async () => {
@ -124,6 +131,7 @@ describe('<ApplicationAdd/>', () => {
redirect_uris: 'http://www.google.com',
});
expect(history.location.pathname).toBe('/applications/8/details');
expect(onSuccessfulAdd).toHaveBeenCalledWith({ id: 8 });
});
test('should cancel form properly', async () => {
@ -134,9 +142,12 @@ describe('<ApplicationAdd/>', () => {
ApplicationsAPI.create.mockResolvedValue({ data: { id: 8 } });
await act(async () => {
wrapper = mountWithContexts(<ApplicationAdd />, {
context: { router: { history } },
});
wrapper = mountWithContexts(
<ApplicationAdd onSuccessfulAdd={onSuccessfulAdd} />,
{
context: { router: { history } },
}
);
});
await act(async () => {
wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
@ -157,7 +168,9 @@ describe('<ApplicationAdd/>', () => {
ApplicationsAPI.create.mockRejectedValue(error);
ApplicationsAPI.readOptions.mockResolvedValue(options);
await act(async () => {
wrapper = mountWithContexts(<ApplicationAdd />);
wrapper = mountWithContexts(
<ApplicationAdd onSuccessfulAdd={onSuccessfulAdd} />
);
});
await act(async () => {
wrapper.find('Formik').prop('onSubmit')({
@ -181,7 +194,9 @@ describe('<ApplicationAdd/>', () => {
})
);
await act(async () => {
wrapper = mountWithContexts(<ApplicationAdd />);
wrapper = mountWithContexts(
<ApplicationAdd onSuccessfulAdd={onSuccessfulAdd} />
);
});
wrapper.update();

View File

@ -80,6 +80,7 @@ function ApplicationDetails({
application.authorization_grant_type
)}
/>
<Detail label={i18n._(t`Client ID`)} value={application.client_id} />
<Detail
label={i18n._(t`Redirect uris`)}
value={application.redirect_uris}

View File

@ -120,6 +120,9 @@ describe('<ApplicationDetails/>', () => {
expect(wrapper.find('Detail[label="Client type"]').prop('value')).toBe(
'Confidential'
);
expect(wrapper.find('Detail[label="Client ID"]').prop('value')).toBe(
'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4'
);
expect(wrapper.find('Button[aria-label="Edit"]').prop('to')).toBe(
'/applications/10/edit'
);

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { Card, PageSection } from '@patternfly/react-core';
import { Card } from '@patternfly/react-core';
import ApplicationForm from '../shared/ApplicationForm';
import { ApplicationsAPI } from '../../../api';
import { CardBody } from '../../../components/Card';
@ -29,22 +29,18 @@ function ApplicationEdit({
history.push(`/applications/${id}/details`);
};
return (
<>
<PageSection>
<Card>
<CardBody>
<ApplicationForm
onSubmit={handleSubmit}
application={application}
onCancel={handleCancel}
authorizationOptions={authorizationOptions}
clientTypeOptions={clientTypeOptions}
submitError={submitError}
/>
</CardBody>
</Card>
</PageSection>
</>
<Card>
<CardBody>
<ApplicationForm
onSubmit={handleSubmit}
application={application}
onCancel={handleCancel}
authorizationOptions={authorizationOptions}
clientTypeOptions={clientTypeOptions}
submitError={submitError}
/>
</CardBody>
</Card>
);
}
export default ApplicationEdit;

View File

@ -2,13 +2,19 @@ import React, { useState, useCallback } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom';
import {
ClipboardCopy,
ClipboardCopyVariant,
Modal,
} from '@patternfly/react-core';
import ApplicationsList from './ApplicationsList';
import ApplicationAdd from './ApplicationAdd';
import Application from './Application';
import Breadcrumbs from '../../components/Breadcrumbs';
import { Detail, DetailList } from '../../components/DetailList';
function Applications({ i18n }) {
const [applicationModalSource, setApplicationModalSource] = useState(null);
const [breadcrumbConfig, setBreadcrumbConfig] = useState({
'/applications': i18n._(t`Applications`),
'/applications/add': i18n._(t`Create New Application`),
@ -36,7 +42,9 @@ function Applications({ i18n }) {
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
<Switch>
<Route path="/applications/add">
<ApplicationAdd />
<ApplicationAdd
onSuccessfulAdd={app => setApplicationModalSource(app)}
/>
</Route>
<Route path="/applications/:id">
<Application setBreadcrumb={buildBreadcrumbConfig} />
@ -45,6 +53,48 @@ function Applications({ i18n }) {
<ApplicationsList />
</Route>
</Switch>
{applicationModalSource && (
<Modal
aria-label={i18n._(t`Application information`)}
isOpen
variant="medium"
title={i18n._(t`Application information`)}
onClose={() => setApplicationModalSource(null)}
>
<DetailList stacked>
<Detail
label={i18n._(t`Name`)}
value={applicationModalSource.name}
/>
{applicationModalSource.client_id && (
<Detail
label={i18n._(t`Client ID`)}
value={
<ClipboardCopy
isReadOnly
variant={ClipboardCopyVariant.expansion}
>
{applicationModalSource.client_id}
</ClipboardCopy>
}
/>
)}
{applicationModalSource.client_secret && (
<Detail
label={i18n._(t`Client secret`)}
value={
<ClipboardCopy
isReadOnly
variant={ClipboardCopyVariant.expansion}
>
{applicationModalSource.client_secret}
</ClipboardCopy>
}
/>
)}
</DetailList>
</Modal>
)}
</>
);
}

View File

@ -1,25 +1,48 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Applications from './Applications';
describe('<Applications />', () => {
let pageWrapper;
let pageSections;
beforeEach(() => {
pageWrapper = mountWithContexts(<Applications />);
pageSections = pageWrapper.find('PageSection');
});
let wrapper;
afterEach(() => {
pageWrapper.unmount();
wrapper.unmount();
});
test('initially renders without crashing', () => {
expect(pageWrapper.length).toBe(1);
test('renders successfully', () => {
wrapper = mountWithContexts(<Applications />);
const pageSections = wrapper.find('PageSection');
expect(wrapper.length).toBe(1);
expect(pageSections.length).toBe(1);
expect(pageSections.first().props().variant).toBe('light');
});
test('shows Application information modal after successful creation', async () => {
const history = createMemoryHistory({
initialEntries: ['/applications/add'],
});
wrapper = mountWithContexts(<Applications />, {
context: { router: { history } },
});
expect(wrapper.find('Modal[title="Application information"]').length).toBe(
0
);
await act(async () => {
wrapper
.find('ApplicationAdd')
.props()
.onSuccessfulAdd({
name: 'test',
client_id: 'foobar',
client_secret: 'aaaaaaaaaaaaaaaaaaaaaaaaaa',
});
});
wrapper.update();
expect(wrapper.find('Modal[title="Application information"]').length).toBe(
1
);
});
});