Update detail components to use ActionButtonWrapper

This commit is contained in:
Marliana Lara
2020-01-09 00:17:54 -05:00
parent 1c09114abd
commit 6ec96a8f4f
15 changed files with 162 additions and 245 deletions

View File

@@ -133,7 +133,7 @@ class Host extends Component {
{host && ( {host && (
<Route <Route
path="/hosts/:id/details" path="/hosts/:id/details"
render={() => <HostDetail match={match} host={host} />} render={() => <HostDetail host={host} />}
/> />
)} )}
{host && ( {host && (

View File

@@ -1,23 +1,13 @@
import React from 'react'; import React from 'react';
import { Link, withRouter } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components';
import { Host } from '@types'; import { Host } from '@types';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { VariablesDetail } from '@components/CodeMirrorInput'; import { VariablesDetail } from '@components/CodeMirrorInput';
const ActionButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 20px;
& > :not(:first-child) {
margin-left: 20px;
}
`;
function HostDetail({ host, i18n }) { function HostDetail({ host, i18n }) {
const { created, description, id, modified, name, summary_fields } = host; const { created, description, id, modified, name, summary_fields } = host;
@@ -58,7 +48,7 @@ function HostDetail({ host, i18n }) {
rows={6} rows={6}
/> />
</DetailList> </DetailList>
<ActionButtonWrapper> <CardActionsRow>
{summary_fields.user_capabilities && {summary_fields.user_capabilities &&
summary_fields.user_capabilities.edit && ( summary_fields.user_capabilities.edit && (
<Button <Button
@@ -69,7 +59,7 @@ function HostDetail({ host, i18n }) {
{i18n._(t`Edit`)} {i18n._(t`Edit`)}
</Button> </Button>
)} )}
</ActionButtonWrapper> </CardActionsRow>
</CardBody> </CardBody>
); );
} }
@@ -78,4 +68,4 @@ HostDetail.propTypes = {
host: Host.isRequired, host: Host.isRequired,
}; };
export default withI18n()(withRouter(HostDetail)); export default withI18n()(HostDetail);

View File

@@ -16,9 +16,11 @@ GroupsAPI.readDetail.mockResolvedValue({
variables: 'bizz: buzz', variables: 'bizz: buzz',
summary_fields: { summary_fields: {
inventory: { id: 1 }, inventory: { id: 1 },
created_by: { id: 1, name: 'Athena' }, created_by: { id: 1, username: 'Athena' },
modified_by: { id: 1, name: 'Apollo' }, modified_by: { id: 1, username: 'Apollo' },
}, },
created: '2020-04-25T01:23:45.678901Z',
modified: '2020-04-25T01:23:45.678901Z',
}, },
}); });
describe('<InventoryGroup />', () => { describe('<InventoryGroup />', () => {

View File

@@ -3,27 +3,16 @@ import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { withRouter } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import { VariablesDetail } from '@components/CodeMirrorInput'; import { VariablesDetail } from '@components/CodeMirrorInput';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
import AlertModal from '@components/AlertModal'; import AlertModal from '@components/AlertModal';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal'; import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
import { GroupsAPI, InventoriesAPI } from '@api'; import { GroupsAPI, InventoriesAPI } from '@api';
// TODO: extract this into a component for use in all relevant Detail views function InventoryGroupDetail({ i18n, inventoryGroup }) {
const ActionButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 20px;
& > :not(:first-child) {
margin-left: 20px;
}
`;
function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
const { const {
summary_fields: { created_by, modified_by }, summary_fields: { created_by, modified_by },
created, created,
@@ -34,15 +23,21 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
} = inventoryGroup; } = inventoryGroup;
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const history = useHistory();
const params = useParams();
const handleDelete = async option => { const handleDelete = async option => {
const inventoryId = parseInt(params.id, 10);
const groupId = parseInt(params.groupId, 10);
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
try { try {
if (option === 'delete') { if (option === 'delete') {
await GroupsAPI.destroy(inventoryGroup.id); await GroupsAPI.destroy(groupId);
} else { } else {
await InventoriesAPI.promoteGroup(match.params.id, inventoryGroup.id); await InventoriesAPI.promoteGroup(inventoryId, groupId);
} }
history.push(`/inventories/inventory/${match.params.id}/groups`); history.push(`/inventories/inventory/${inventoryId}/groups`);
} catch (err) { } catch (err) {
setError(err); setError(err);
} }
@@ -69,13 +64,13 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
user={modified_by} user={modified_by}
/> />
</DetailList> </DetailList>
<ActionButtonWrapper> <CardActionsRow>
<Button <Button
variant="primary" variant="primary"
aria-label={i18n._(t`Edit`)} aria-label={i18n._(t`Edit`)}
onClick={() => onClick={() =>
history.push( history.push(
`/inventories/inventory/${match.params.id}/groups/${inventoryGroup.id}/edit` `/inventories/inventory/${params.id}/groups/${params.groupId}/edit`
) )
} }
> >
@@ -88,7 +83,7 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
</Button> </Button>
</ActionButtonWrapper> </CardActionsRow>
{isDeleteModalOpen && ( {isDeleteModalOpen && (
<InventoryGroupsDeleteModal <InventoryGroupsDeleteModal
groups={[inventoryGroup]} groups={[inventoryGroup]}
@@ -111,4 +106,4 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
</CardBody> </CardBody>
); );
} }
export default withI18n()(withRouter(InventoryGroupDetail)); export default withI18n()(InventoryGroupDetail);

View File

@@ -2,13 +2,12 @@ import React from 'react';
import { GroupsAPI } from '@api'; import { GroupsAPI } from '@api';
import { Route } from 'react-router-dom'; import { Route } from 'react-router-dom';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import InventoryGroupDetail from './InventoryGroupDetail'; import InventoryGroupDetail from './InventoryGroupDetail';
jest.mock('@api'); jest.mock('@api');
const inventoryGroup = { const inventoryGroup = {
name: 'Foo', name: 'Foo',
description: 'Bar', description: 'Bar',
@@ -27,6 +26,7 @@ const inventoryGroup = {
}, },
}, },
}; };
describe('<InventoryGroupDetail />', () => { describe('<InventoryGroupDetail />', () => {
let wrapper; let wrapper;
let history; let history;
@@ -86,7 +86,7 @@ describe('<InventoryGroupDetail />', () => {
'/inventories/inventory/1/groups/1/edit' '/inventories/inventory/1/groups/1/edit'
); );
}); });
test('details shoudld render with the proper values', () => { test('details should render with the proper values', () => {
expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo'); expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo');
expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe( expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe(
'Bar' 'Bar'

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Link, withRouter } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
@@ -7,9 +7,10 @@ import styled from 'styled-components';
import AlertModal from '@components/AlertModal'; import AlertModal from '@components/AlertModal';
import { DetailList, Detail } from '@components/DetailList'; import { DetailList, Detail } from '@components/DetailList';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { ChipGroup, Chip, CredentialChip } from '@components/Chip'; import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput'; import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
import DeleteButton from '@components/DeleteButton';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
import { StatusIcon } from '@components/Sparkline'; import { StatusIcon } from '@components/Sparkline';
@@ -24,16 +25,6 @@ import {
InventoriesAPI, InventoriesAPI,
AdHocCommandsAPI, AdHocCommandsAPI,
} from '@api'; } from '@api';
import { JOB_TYPE_URL_SEGMENTS } from '../../../constants';
const ActionButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 20px;
& > :not(:first-child) {
margin-left: 20px;
}
`;
const VariablesInput = styled(_VariablesInput)` const VariablesInput = styled(_VariablesInput)`
.pf-c-form__label { .pf-c-form__label {
@@ -86,7 +77,7 @@ const getLaunchedByDetails = ({ summary_fields = {}, related = {} }) => {
return { link, value }; return { link, value };
}; };
function JobDetail({ job, i18n, history }) { function JobDetail({ job, i18n }) {
const { const {
credentials, credentials,
instance_group: instanceGroup, instance_group: instanceGroup,
@@ -95,8 +86,8 @@ function JobDetail({ job, i18n, history }) {
labels, labels,
project, project,
} = job.summary_fields; } = job.summary_fields;
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [errorMsg, setErrorMsg] = useState(); const [errorMsg, setErrorMsg] = useState();
const history = useHistory();
const { value: launchedByValue, link: launchedByLink } = const { value: launchedByValue, link: launchedByLink } =
getLaunchedByDetails(job) || {}; getLaunchedByDetails(job) || {};
@@ -125,7 +116,6 @@ function JobDetail({ job, i18n, history }) {
history.push('/jobs'); history.push('/jobs');
} catch (err) { } catch (err) {
setErrorMsg(err); setErrorMsg(err);
setIsDeleteModalOpen(false);
} }
}; };
@@ -262,7 +252,7 @@ function JobDetail({ job, i18n, history }) {
label={i18n._(t`Artifacts`)} label={i18n._(t`Artifacts`)}
/> />
)} )}
<ActionButtonWrapper> <CardActionsRow>
{job.type !== 'system_job' && {job.type !== 'system_job' &&
job.summary_fields.user_capabilities.start && ( job.summary_fields.user_capabilities.start && (
<LaunchButton resource={job} aria-label={i18n._(t`Relaunch`)}> <LaunchButton resource={job} aria-label={i18n._(t`Relaunch`)}>
@@ -274,45 +264,15 @@ function JobDetail({ job, i18n, history }) {
</LaunchButton> </LaunchButton>
)} )}
{job.summary_fields.user_capabilities.delete && ( {job.summary_fields.user_capabilities.delete && (
<Button <DeleteButton
variant="danger" name={job.name}
aria-label={i18n._(t`Delete`)} modalTitle={i18n._(t`Delete Job`)}
onClick={() => setIsDeleteModalOpen(true)} onConfirm={deleteJob}
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
</Button> </DeleteButton>
)} )}
</ActionButtonWrapper> </CardActionsRow>
{isDeleteModalOpen && (
<AlertModal
isOpen={isDeleteModalOpen}
title={i18n._(t`Delete Job`)}
variant="danger"
onClose={() => setIsDeleteModalOpen(false)}
>
{i18n._(t`Are you sure you want to delete:`)}
<br />
<strong>{job.name}</strong>
<ActionButtonWrapper>
<Button
variant="secondary"
aria-label={i18n._(t`Close`)}
component={Link}
to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}
>
{i18n._(t`Cancel`)}
</Button>
<Button
variant="danger"
aria-label={i18n._(t`Delete`)}
onClick={deleteJob}
>
{i18n._(t`Delete`)}
</Button>
</ActionButtonWrapper>
</AlertModal>
)}
{errorMsg && ( {errorMsg && (
<AlertModal <AlertModal
isOpen={errorMsg} isOpen={errorMsg}
@@ -330,4 +290,4 @@ JobDetail.propTypes = {
job: Job.isRequired, job: Job.isRequired,
}; };
export default withI18n()(withRouter(JobDetail)); export default withI18n()(JobDetail);

View File

@@ -5,7 +5,7 @@ import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { OrganizationsAPI } from '@api'; import { OrganizationsAPI } from '@api';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { ChipGroup, Chip } from '@components/Chip'; import { ChipGroup, Chip } from '@components/Chip';
import ContentError from '@components/ContentError'; import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading'; import ContentLoading from '@components/ContentLoading';
@@ -92,13 +92,13 @@ function OrganizationDetail({ i18n, organization }) {
/> />
)} )}
</DetailList> </DetailList>
{summary_fields.user_capabilities.edit && ( <CardActionsRow>
<div css="margin-top: 10px; text-align: right;"> {summary_fields.user_capabilities.edit && (
<Button component={Link} to={`/organizations/${id}/edit`}> <Button component={Link} to={`/organizations/${id}/edit`}>
{i18n._(t`Edit`)} {i18n._(t`Edit`)}
</Button> </Button>
</div> )}
)} </CardActionsRow>
</CardBody> </CardBody>
); );
} }

View File

@@ -205,7 +205,7 @@ class Project extends Component {
{project && ( {project && (
<Route <Route
path="/projects/:id/details" path="/projects/:id/details"
render={() => <ProjectDetail match={match} project={project} />} render={() => <ProjectDetail project={project} />}
/> />
)} )}
{project && ( {project && (

View File

@@ -1,25 +1,15 @@
import React from 'react'; import React from 'react';
import { Link, withRouter } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components';
import { Project } from '@types'; import { Project } from '@types';
import { Config } from '@contexts/Config'; import { Config } from '@contexts/Config';
import { Button, List, ListItem } from '@patternfly/react-core'; import { Button, List, ListItem } from '@patternfly/react-core';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { CredentialChip } from '@components/Chip'; import { CredentialChip } from '@components/Chip';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
const ActionButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 20px;
& > :not(:first-child) {
margin-left: 20px;
}
`;
function ProjectDetail({ project, i18n }) { function ProjectDetail({ project, i18n }) {
const { const {
allow_override, allow_override,
@@ -137,7 +127,7 @@ function ProjectDetail({ project, i18n }) {
user={summary_fields.modified_by} user={summary_fields.modified_by}
/> />
</DetailList> </DetailList>
<ActionButtonWrapper> <CardActionsRow>
{summary_fields.user_capabilities && {summary_fields.user_capabilities &&
summary_fields.user_capabilities.edit && ( summary_fields.user_capabilities.edit && (
<Button <Button
@@ -148,7 +138,7 @@ function ProjectDetail({ project, i18n }) {
{i18n._(t`Edit`)} {i18n._(t`Edit`)}
</Button> </Button>
)} )}
</ActionButtonWrapper> </CardActionsRow>
</CardBody> </CardBody>
); );
} }
@@ -157,4 +147,4 @@ ProjectDetail.propTypes = {
project: Project.isRequired, project: Project.isRequired,
}; };
export default withI18n()(withRouter(ProjectDetail)); export default withI18n()(ProjectDetail);

View File

@@ -125,7 +125,7 @@ class Team extends Component {
{team && ( {team && (
<Route <Route
path="/teams/:id/details" path="/teams/:id/details"
render={() => <TeamDetail match={match} team={team} />} render={() => <TeamDetail team={team} />}
/> />
)} )}
{team && ( {team && (

View File

@@ -1,57 +1,49 @@
import React, { Component } from 'react'; import React from 'react';
import { Link, withRouter } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { DetailList, Detail } from '@components/DetailList'; import { DetailList, Detail } from '@components/DetailList';
import { formatDateString } from '@util/dates'; import { formatDateString } from '@util/dates';
class TeamDetail extends Component { function TeamDetail({ team, i18n }) {
render() { const { name, description, created, modified, summary_fields } = team;
const { const { id } = useParams();
team: { name, description, created, modified, summary_fields },
match,
i18n,
} = this.props;
return ( return (
<CardBody> <CardBody>
<DetailList> <DetailList>
<Detail <Detail
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
value={name} value={name}
dataCy="team-detail-name" dataCy="team-detail-name"
/> />
<Detail label={i18n._(t`Description`)} value={description} /> <Detail label={i18n._(t`Description`)} value={description} />
<Detail <Detail
label={i18n._(t`Organization`)} label={i18n._(t`Organization`)}
value={ value={
<Link to={`/organizations/${summary_fields.organization.id}`}> <Link to={`/organizations/${summary_fields.organization.id}`}>
{summary_fields.organization.name} {summary_fields.organization.name}
</Link> </Link>
} }
/> />
<Detail <Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
label={i18n._(t`Created`)} <Detail
value={formatDateString(created)} label={i18n._(t`Last Modified`)}
/> value={formatDateString(modified)}
<Detail />
label={i18n._(t`Last Modified`)} </DetailList>
value={formatDateString(modified)} <CardActionsRow>
/>
</DetailList>
{summary_fields.user_capabilities.edit && ( {summary_fields.user_capabilities.edit && (
<div css="margin-top: 10px; text-align: right;"> <Button component={Link} to={`/teams/${id}/edit`}>
<Button component={Link} to={`/teams/${match.params.id}/edit`}> {i18n._(t`Edit`)}
{i18n._(t`Edit`)} </Button>
</Button>
</div>
)} )}
</CardBody> </CardActionsRow>
); </CardBody>
} );
} }
export default withI18n()(withRouter(TeamDetail)); export default withI18n()(TeamDetail);

View File

@@ -11,7 +11,7 @@ import {
import styled from 'styled-components'; import styled from 'styled-components';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import ContentError from '@components/ContentError'; import ContentError from '@components/ContentError';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
import ContentLoading from '@components/ContentLoading'; import ContentLoading from '@components/ContentLoading';
@@ -19,20 +19,12 @@ import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { JobTemplatesAPI } from '@api'; import { JobTemplatesAPI } from '@api';
const ButtonGroup = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 20px;
& > :not(:first-child) {
margin-left: 20px;
}
`;
const MissingDetail = styled(Detail)` const MissingDetail = styled(Detail)`
dd& { dd& {
color: red; color: red;
} }
`; `;
class JobTemplateDetail extends Component { class JobTemplateDetail extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -317,7 +309,7 @@ class JobTemplateDetail extends Component {
/> />
)} )}
</DetailList> </DetailList>
<ButtonGroup> <CardActionsRow>
{summary_fields.user_capabilities.edit && ( {summary_fields.user_capabilities.edit && (
<Button <Button
component={Link} component={Link}
@@ -340,7 +332,7 @@ class JobTemplateDetail extends Component {
)} )}
</LaunchButton> </LaunchButton>
)} )}
</ButtonGroup> </CardActionsRow>
</CardBody> </CardBody>
) )
); );

View File

@@ -19,8 +19,8 @@ describe('<JobTemplateDetail />', () => {
verbosity: 1, verbosity: 1,
summary_fields: { summary_fields: {
user_capabilities: { edit: true }, user_capabilities: { edit: true },
created_by: { username: 'Joe' }, created_by: { id: 1, username: 'Joe' },
modified_by: { username: 'Joe' }, modified_by: { id: 1, username: 'Joe' },
credentials: [ credentials: [
{ id: 1, kind: 'ssh', name: 'Credential 1' }, { id: 1, kind: 'ssh', name: 'Credential 1' },
{ id: 2, kind: 'awx', name: 'Credential 2' }, { id: 2, kind: 'awx', name: 'Credential 2' },
@@ -28,6 +28,8 @@ describe('<JobTemplateDetail />', () => {
inventory: { name: 'Inventory' }, inventory: { name: 'Inventory' },
project: { name: 'Project' }, project: { name: 'Project' },
}, },
created: '2020-04-25T01:23:45.678901Z',
modified: '2020-04-25T01:23:45.678901Z',
}; };
const mockInstanceGroups = { const mockInstanceGroups = {
@@ -100,12 +102,14 @@ describe('<JobTemplateDetail />', () => {
skip_tags: 'coffe,tea', skip_tags: 'coffe,tea',
summary_fields: { summary_fields: {
user_capabilities: { edit: false }, user_capabilities: { edit: false },
created_by: { username: 'Joe' }, created_by: { id: 1, username: 'Joe' },
modified_by: { username: 'Joe' }, modified_by: { id: 1, username: 'Joe' },
inventory: { name: 'Inventory' }, inventory: { name: 'Inventory' },
project: { name: 'Project' }, project: { name: 'Project' },
labels: { count: 1, results: [{ name: 'Label', id: 1 }] }, labels: { count: 1, results: [{ name: 'Label', id: 1 }] },
}, },
created: '2020-04-25T01:23:45.678901Z',
modified: '2020-04-25T01:23:45.678901Z',
}; };
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<JobTemplateDetail template={regularUser} /> <JobTemplateDetail template={regularUser} />

View File

@@ -134,7 +134,7 @@ class User extends Component {
{user && ( {user && (
<Route <Route
path="/users/:id/details" path="/users/:id/details"
render={() => <UserDetail match={match} user={user} />} render={() => <UserDetail user={user} />}
/> />
)} )}
<Route <Route

View File

@@ -1,77 +1,69 @@
import React, { Component } from 'react'; import React from 'react';
import { Link, withRouter } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { CardBody } from '@components/Card'; import { CardBody, CardActionsRow } from '@components/Card';
import { DetailList, Detail } from '@components/DetailList'; import { DetailList, Detail } from '@components/DetailList';
import { formatDateString } from '@util/dates'; import { formatDateString } from '@util/dates';
class UserDetail extends Component { function UserDetail({ user, i18n }) {
render() { const {
const { id,
user: { username,
id, email,
username, first_name,
email, last_name,
first_name, last_login,
last_name, created,
last_login, is_superuser,
created, is_system_auditor,
is_superuser, summary_fields,
is_system_auditor, } = user;
summary_fields,
},
i18n,
} = this.props;
let user_type; let user_type;
if (is_superuser) { if (is_superuser) {
user_type = i18n._(t`System Administrator`); user_type = i18n._(t`System Administrator`);
} else if (is_system_auditor) { } else if (is_system_auditor) {
user_type = i18n._(t`System Auditor`); user_type = i18n._(t`System Auditor`);
} else { } else {
user_type = i18n._(t`Normal User`); user_type = i18n._(t`Normal User`);
}
return (
<CardBody>
<DetailList>
<Detail
label={i18n._(t`Username`)}
value={username}
dataCy="user-detail-username"
/>
<Detail label={i18n._(t`Email`)} value={email} />
<Detail label={i18n._(t`First Name`)} value={`${first_name}`} />
<Detail label={i18n._(t`Last Name`)} value={`${last_name}`} />
<Detail label={i18n._(t`User Type`)} value={`${user_type}`} />
{last_login && (
<Detail
label={i18n._(t`Last Login`)}
value={formatDateString(last_login)}
/>
)}
<Detail
label={i18n._(t`Created`)}
value={formatDateString(created)}
/>
</DetailList>
{summary_fields.user_capabilities.edit && (
<div css="margin-top: 10px; text-align: right;">
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/users/${id}/edit`}
>
{i18n._(t`Edit`)}
</Button>
</div>
)}
</CardBody>
);
} }
return (
<CardBody>
<DetailList>
<Detail
label={i18n._(t`Username`)}
value={username}
dataCy="user-detail-username"
/>
<Detail label={i18n._(t`Email`)} value={email} />
<Detail label={i18n._(t`First Name`)} value={`${first_name}`} />
<Detail label={i18n._(t`Last Name`)} value={`${last_name}`} />
<Detail label={i18n._(t`User Type`)} value={`${user_type}`} />
{last_login && (
<Detail
label={i18n._(t`Last Login`)}
value={formatDateString(last_login)}
/>
)}
<Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
</DetailList>
<CardActionsRow>
{summary_fields.user_capabilities.edit && (
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/users/${id}/edit`}
>
{i18n._(t`Edit`)}
</Button>
)}
</CardActionsRow>
</CardBody>
);
} }
export default withI18n()(withRouter(UserDetail)); export default withI18n()(UserDetail);