Add Organization Details view and test

This commit is contained in:
Marliana Lara 2019-02-01 09:16:49 -05:00
parent a2007b8e0c
commit 8846e1427e
No known key found for this signature in database
GPG Key ID: 38C73B40DFA809EE
7 changed files with 285 additions and 25 deletions

View File

@ -5,16 +5,114 @@ import { I18nProvider } from '@lingui/react';
import OrganizationDetail from '../../../../../src/pages/Organizations/screens/Organization/OrganizationDetail';
describe('<OrganizationDetail />', () => {
const mockDetails = {
name: 'Foo',
description: 'Bar',
custom_virtualenv: 'Fizz',
created: 'Bat',
modified: 'Boo'
};
test('initially renders succesfully', () => {
mount(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{ url: '/organizations/1' }}
organization={{ name: 'Default' }}
match={{ params: { id: '1' } }}
organization={mockDetails}
/>
</MemoryRouter>
</I18nProvider>
);
});
test('should request instance groups from api', () => {
const getOrganizationInstanceGroups = jest.fn();
mount(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{ params: { id: '1' } }}
api={{
getOrganizationInstanceGroups
}}
organization={mockDetails}
/>
</MemoryRouter>
</I18nProvider>
).find('OrganizationDetail');
expect(getOrganizationInstanceGroups).toHaveBeenCalledTimes(1);
});
test('should handle setting instance groups to state', async () => {
const mockInstanceGroups = [
{ name: 'One', id: 1 },
{ name: 'Two', id: 2 }
];
const getOrganizationInstanceGroups = jest.fn(() => (
Promise.resolve({ data: { results: mockInstanceGroups } })
));
const wrapper = mount(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{
path: '/organizations/:id',
url: '/organizations/1',
params: { id: '1' }
}}
organization={mockDetails}
api={{
getOrganizationInstanceGroups
}}
/>
</MemoryRouter>
</I18nProvider>
).find('OrganizationDetail');
await getOrganizationInstanceGroups();
expect(wrapper.state().instanceGroups).toEqual(mockInstanceGroups);
});
test('should render Details', async () => {
const wrapper = mount(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{
path: '/organizations/:id',
url: '/organizations/1',
params: { id: '1' }
}}
organization={mockDetails}
/>
</MemoryRouter>
</I18nProvider>
);
const detailWrapper = wrapper.find('Detail');
expect(detailWrapper.length).toBe(5);
const nameDetail = detailWrapper.findWhere(node => node.props().label === 'Name');
const descriptionDetail = detailWrapper.findWhere(node => node.props().label === 'Description');
const custom_virtualenvDetail = detailWrapper.findWhere(node => node.props().label === 'Ansible Environment');
const createdDetail = detailWrapper.findWhere(node => node.props().label === 'Created');
const modifiedDetail = detailWrapper.findWhere(node => node.props().label === 'Last Modified');
expect(nameDetail.find('h6').text()).toBe('Name');
expect(nameDetail.find('p').text()).toBe('Foo');
expect(descriptionDetail.find('h6').text()).toBe('Description');
expect(descriptionDetail.find('p').text()).toBe('Bar');
expect(custom_virtualenvDetail.find('h6').text()).toBe('Ansible Environment');
expect(custom_virtualenvDetail.find('p').text()).toBe('Fizz');
expect(createdDetail.find('h6').text()).toBe('Created');
expect(createdDetail.find('p').text()).toBe('Bat');
expect(modifiedDetail.find('h6').text()).toBe('Last Modified');
expect(modifiedDetail.find('p').text()).toBe('Boo');
});
});

View File

@ -70,6 +70,12 @@ class APIClient {
return this.http.get(endpoint);
}
getOrganizationInstanceGroups (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/instance_groups/`;
return this.http.get(endpoint, { params });
}
getOrganizationNotifications (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;

View File

@ -0,0 +1,13 @@
import React from 'react';
import { Chip } from '@patternfly/react-core';
import './basicChip.scss';
const BasicChip = ({ text }) => (
<Chip
className="awx-c-chip--basic"
>
{text}
</Chip>
);
export default BasicChip;

View File

@ -0,0 +1,10 @@
.awx-c-chip--basic {
padding: 6px 8px;
height: 30px;
margin-right: 10px;
margin-bottom: 10px;
.pf-c-button {
display: none;
}
}

View File

@ -1,6 +1,8 @@
.pf-c-card__header {
--pf-c-card__header--PaddingBottom: 0;
--pf-c-card__header--PaddingX: 0;
--pf-c-card__header--PaddingRight: 0;
--pf-c-card__header--PaddingLeft: 0;
--pf-c-card__header--PaddingTop: 0;
}

View File

@ -127,16 +127,18 @@ class Organization extends Component {
/>
)}
/>
<Route
path="/organizations/:id/details"
render={() => (
<OrganizationDetail
organization={organization}
match={match}
location={location}
/>
)}
/>
{organization && (
<Route
path="/organizations/:id/details"
render={() => (
<OrganizationDetail
api={api}
match={match}
organization={organization}
/>
)}
/>
)}
<Route
path="/organizations/:id/access"
render={() => <CardBody><h1><Trans>Access</Trans></h1></CardBody>}

View File

@ -1,15 +1,144 @@
import React from 'react';
import { withRouter, Link } from 'react-router-dom';
import { Trans } from '@lingui/macro';
import { CardBody } from '@patternfly/react-core';
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
CardBody,
Button,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import BasicChip from '../../../../components/BasicChip/BasicChip';
const OrganizationDetail = ({ match, organization }) => (
<CardBody>
<h1><Trans>{`${organization && organization.name} Detail View`}</Trans></h1>
<Link to={`/organizations/${match.params.id}/edit`}>
<Trans>Edit Details</Trans>
</Link>
</CardBody>
);
const detailWrapperStyle = {
display: 'flex'
};
export default withRouter(OrganizationDetail);
const detailLabelStyle = {
minWidth: '150px',
marginRight: '20px',
textAlign: 'right'
};
const Detail = ({ label, value }) => {
let detail = null;
if (value) {
detail = (
<TextContent style={detailWrapperStyle}>
<Text component={TextVariants.h6} style={detailLabelStyle}>{ label }</Text>
<Text component={TextVariants.p}>{ value }</Text>
</TextContent>
);
}
return detail;
};
class OrganizationDetail extends Component {
constructor (props) {
super(props);
this.state = {
instanceGroups: [],
error: false
};
this.loadInstanceGroups = this.loadInstanceGroups.bind(this);
}
componentDidMount () {
this.loadInstanceGroups();
}
async loadInstanceGroups () {
const {
api,
match
} = this.props;
try {
const {
data
} = await api.getOrganizationInstanceGroups(match.params.id);
this.setState({
instanceGroups: [...data.results]
});
} catch (err) {
this.setState({ error: true });
}
}
render () {
const {
error,
instanceGroups,
} = this.state;
const {
organization: {
name,
description,
custom_virtualenv,
created,
modified
},
match
} = this.props;
return (
<I18n>
{({ i18n }) => (
<CardBody className="pf-u-pt-lg">
<div className="pf-l-grid pf-m-gutter pf-m-all-12-col-on-md pf-m-all-6-col-on-lg pf-m-all-4-col-on-xl">
<Detail
label={i18n._(t`Name`)}
value={name}
/>
<Detail
label={i18n._(t`Description`)}
value={description}
/>
{(instanceGroups && instanceGroups.length > 0) && (
<TextContent style={detailWrapperStyle}>
<Text
component={TextVariants.h6}
style={detailLabelStyle}
>
<Trans>Instance Groups</Trans>
</Text>
<div>
{instanceGroups.map(instanceGroup => (
<BasicChip
key={instanceGroup.id}
text={instanceGroup.name}
/>
))}
</div>
</TextContent>
)}
<Detail
label={i18n._(t`Ansible Environment`)}
value={custom_virtualenv}
/>
<Detail
label={i18n._(t`Created`)}
value={created}
/>
<Detail
label={i18n._(t`Last Modified`)}
value={modified}
/>
</div>
<div className="pf-u-display-flex pf-u-flex-direction-row-reverse pf-u-ml-auto pf-u-mt-md">
<Link to={`/organizations/${match.params.id}/edit`}>
<Button><Trans>Edit</Trans></Button>
</Link>
</div>
{error ? 'error!' : ''}
</CardBody>
)}
</I18n>
);
}
}
export default OrganizationDetail;