mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 04:17:36 -02:30
Add Organization Details view and test
This commit is contained in:
@@ -5,16 +5,114 @@ import { I18nProvider } from '@lingui/react';
|
|||||||
import OrganizationDetail from '../../../../../src/pages/Organizations/screens/Organization/OrganizationDetail';
|
import OrganizationDetail from '../../../../../src/pages/Organizations/screens/Organization/OrganizationDetail';
|
||||||
|
|
||||||
describe('<OrganizationDetail />', () => {
|
describe('<OrganizationDetail />', () => {
|
||||||
|
const mockDetails = {
|
||||||
|
name: 'Foo',
|
||||||
|
description: 'Bar',
|
||||||
|
custom_virtualenv: 'Fizz',
|
||||||
|
created: 'Bat',
|
||||||
|
modified: 'Boo'
|
||||||
|
};
|
||||||
|
|
||||||
test('initially renders succesfully', () => {
|
test('initially renders succesfully', () => {
|
||||||
mount(
|
mount(
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
|
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
|
||||||
<OrganizationDetail
|
<OrganizationDetail
|
||||||
match={{ url: '/organizations/1' }}
|
match={{ params: { id: '1' } }}
|
||||||
organization={{ name: 'Default' }}
|
organization={mockDetails}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</I18nProvider>
|
</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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ class APIClient {
|
|||||||
return this.http.get(endpoint);
|
return this.http.get(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrganizationInstanceGroups (id, params = {}) {
|
||||||
|
const endpoint = `${API_ORGANIZATIONS}${id}/instance_groups/`;
|
||||||
|
|
||||||
|
return this.http.get(endpoint, { params });
|
||||||
|
}
|
||||||
|
|
||||||
getOrganizationNotifications (id, params = {}) {
|
getOrganizationNotifications (id, params = {}) {
|
||||||
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
|
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
|
||||||
|
|
||||||
|
|||||||
13
src/components/BasicChip/BasicChip.jsx
Normal file
13
src/components/BasicChip/BasicChip.jsx
Normal 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;
|
||||||
10
src/components/BasicChip/basicChip.scss
Normal file
10
src/components/BasicChip/basicChip.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
.pf-c-card__header {
|
.pf-c-card__header {
|
||||||
--pf-c-card__header--PaddingBottom: 0;
|
--pf-c-card__header--PaddingBottom: 0;
|
||||||
--pf-c-card__header--PaddingX: 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;
|
--pf-c-card__header--PaddingTop: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,16 +127,18 @@ class Organization extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Route
|
{organization && (
|
||||||
path="/organizations/:id/details"
|
<Route
|
||||||
render={() => (
|
path="/organizations/:id/details"
|
||||||
<OrganizationDetail
|
render={() => (
|
||||||
organization={organization}
|
<OrganizationDetail
|
||||||
match={match}
|
api={api}
|
||||||
location={location}
|
match={match}
|
||||||
/>
|
organization={organization}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Route
|
<Route
|
||||||
path="/organizations/:id/access"
|
path="/organizations/:id/access"
|
||||||
render={() => <CardBody><h1><Trans>Access</Trans></h1></CardBody>}
|
render={() => <CardBody><h1><Trans>Access</Trans></h1></CardBody>}
|
||||||
|
|||||||
@@ -1,15 +1,144 @@
|
|||||||
import React from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withRouter, Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Trans } from '@lingui/macro';
|
import { I18n } from '@lingui/react';
|
||||||
import { CardBody } from '@patternfly/react-core';
|
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 }) => (
|
const detailWrapperStyle = {
|
||||||
<CardBody>
|
display: 'flex'
|
||||||
<h1><Trans>{`${organization && organization.name} Detail View`}</Trans></h1>
|
};
|
||||||
<Link to={`/organizations/${match.params.id}/edit`}>
|
|
||||||
<Trans>Edit Details</Trans>
|
|
||||||
</Link>
|
|
||||||
</CardBody>
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user