mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 02:50:02 -03:30
Merge pull request #5616 from marshmalien/5541-reuse-ActionButtonWrapper
Update Detail views to use CardActionsRow Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
3e58ee068c
@ -133,7 +133,7 @@ class Host extends Component {
|
||||
{host && (
|
||||
<Route
|
||||
path="/hosts/:id/details"
|
||||
render={() => <HostDetail match={match} host={host} />}
|
||||
render={() => <HostDetail host={host} />}
|
||||
/>
|
||||
)}
|
||||
{host && (
|
||||
|
||||
@ -1,23 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
import { Host } from '@types';
|
||||
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 { 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 }) {
|
||||
const { created, description, id, modified, name, summary_fields } = host;
|
||||
|
||||
@ -58,7 +48,7 @@ function HostDetail({ host, i18n }) {
|
||||
rows={6}
|
||||
/>
|
||||
</DetailList>
|
||||
<ActionButtonWrapper>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities &&
|
||||
summary_fields.user_capabilities.edit && (
|
||||
<Button
|
||||
@ -69,7 +59,7 @@ function HostDetail({ host, i18n }) {
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
)}
|
||||
</ActionButtonWrapper>
|
||||
</CardActionsRow>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
@ -78,4 +68,4 @@ HostDetail.propTypes = {
|
||||
host: Host.isRequired,
|
||||
};
|
||||
|
||||
export default withI18n()(withRouter(HostDetail));
|
||||
export default withI18n()(HostDetail);
|
||||
|
||||
@ -16,9 +16,11 @@ GroupsAPI.readDetail.mockResolvedValue({
|
||||
variables: 'bizz: buzz',
|
||||
summary_fields: {
|
||||
inventory: { id: 1 },
|
||||
created_by: { id: 1, name: 'Athena' },
|
||||
modified_by: { id: 1, name: 'Apollo' },
|
||||
created_by: { id: 1, username: 'Athena' },
|
||||
modified_by: { id: 1, username: 'Apollo' },
|
||||
},
|
||||
created: '2020-04-25T01:23:45.678901Z',
|
||||
modified: '2020-04-25T01:23:45.678901Z',
|
||||
},
|
||||
});
|
||||
describe('<InventoryGroup />', () => {
|
||||
|
||||
@ -3,27 +3,16 @@ import { t } from '@lingui/macro';
|
||||
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { VariablesDetail } from '@components/CodeMirrorInput';
|
||||
import { CardBody } from '@components/Card';
|
||||
import { CardBody, CardActionsRow } from '@components/Card';
|
||||
import ErrorDetail from '@components/ErrorDetail';
|
||||
import AlertModal from '@components/AlertModal';
|
||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||
import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
|
||||
import { GroupsAPI, InventoriesAPI } from '@api';
|
||||
|
||||
// TODO: extract this into a component for use in all relevant Detail views
|
||||
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 }) {
|
||||
function InventoryGroupDetail({ i18n, inventoryGroup }) {
|
||||
const {
|
||||
summary_fields: { created_by, modified_by },
|
||||
created,
|
||||
@ -34,15 +23,21 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
||||
} = inventoryGroup;
|
||||
const [error, setError] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const history = useHistory();
|
||||
const params = useParams();
|
||||
|
||||
const handleDelete = async option => {
|
||||
const inventoryId = parseInt(params.id, 10);
|
||||
const groupId = parseInt(params.groupId, 10);
|
||||
setIsDeleteModalOpen(false);
|
||||
|
||||
try {
|
||||
if (option === 'delete') {
|
||||
await GroupsAPI.destroy(inventoryGroup.id);
|
||||
await GroupsAPI.destroy(groupId);
|
||||
} 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) {
|
||||
setError(err);
|
||||
}
|
||||
@ -69,13 +64,13 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
||||
user={modified_by}
|
||||
/>
|
||||
</DetailList>
|
||||
<ActionButtonWrapper>
|
||||
<CardActionsRow>
|
||||
<Button
|
||||
variant="primary"
|
||||
aria-label={i18n._(t`Edit`)}
|
||||
onClick={() =>
|
||||
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`)}
|
||||
</Button>
|
||||
</ActionButtonWrapper>
|
||||
</CardActionsRow>
|
||||
{isDeleteModalOpen && (
|
||||
<InventoryGroupsDeleteModal
|
||||
groups={[inventoryGroup]}
|
||||
@ -111,4 +106,4 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
export default withI18n()(withRouter(InventoryGroupDetail));
|
||||
export default withI18n()(InventoryGroupDetail);
|
||||
|
||||
@ -2,13 +2,12 @@ import React from 'react';
|
||||
import { GroupsAPI } from '@api';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
|
||||
import InventoryGroupDetail from './InventoryGroupDetail';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
const inventoryGroup = {
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
@ -27,6 +26,7 @@ const inventoryGroup = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('<InventoryGroupDetail />', () => {
|
||||
let wrapper;
|
||||
let history;
|
||||
@ -86,7 +86,7 @@ describe('<InventoryGroupDetail />', () => {
|
||||
'/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="Description"]').prop('value')).toBe(
|
||||
'Bar'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
@ -7,9 +7,10 @@ import styled from 'styled-components';
|
||||
|
||||
import AlertModal from '@components/AlertModal';
|
||||
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 { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
||||
import DeleteButton from '@components/DeleteButton';
|
||||
import ErrorDetail from '@components/ErrorDetail';
|
||||
import LaunchButton from '@components/LaunchButton';
|
||||
import { StatusIcon } from '@components/Sparkline';
|
||||
@ -24,16 +25,6 @@ import {
|
||||
InventoriesAPI,
|
||||
AdHocCommandsAPI,
|
||||
} 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)`
|
||||
.pf-c-form__label {
|
||||
@ -86,7 +77,7 @@ const getLaunchedByDetails = ({ summary_fields = {}, related = {} }) => {
|
||||
return { link, value };
|
||||
};
|
||||
|
||||
function JobDetail({ job, i18n, history }) {
|
||||
function JobDetail({ job, i18n }) {
|
||||
const {
|
||||
credentials,
|
||||
instance_group: instanceGroup,
|
||||
@ -95,8 +86,8 @@ function JobDetail({ job, i18n, history }) {
|
||||
labels,
|
||||
project,
|
||||
} = job.summary_fields;
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState();
|
||||
const history = useHistory();
|
||||
|
||||
const { value: launchedByValue, link: launchedByLink } =
|
||||
getLaunchedByDetails(job) || {};
|
||||
@ -125,7 +116,6 @@ function JobDetail({ job, i18n, history }) {
|
||||
history.push('/jobs');
|
||||
} catch (err) {
|
||||
setErrorMsg(err);
|
||||
setIsDeleteModalOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -262,7 +252,7 @@ function JobDetail({ job, i18n, history }) {
|
||||
label={i18n._(t`Artifacts`)}
|
||||
/>
|
||||
)}
|
||||
<ActionButtonWrapper>
|
||||
<CardActionsRow>
|
||||
{job.type !== 'system_job' &&
|
||||
job.summary_fields.user_capabilities.start && (
|
||||
<LaunchButton resource={job} aria-label={i18n._(t`Relaunch`)}>
|
||||
@ -274,45 +264,15 @@ function JobDetail({ job, i18n, history }) {
|
||||
</LaunchButton>
|
||||
)}
|
||||
{job.summary_fields.user_capabilities.delete && (
|
||||
<Button
|
||||
variant="danger"
|
||||
aria-label={i18n._(t`Delete`)}
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
<DeleteButton
|
||||
name={job.name}
|
||||
modalTitle={i18n._(t`Delete Job`)}
|
||||
onConfirm={deleteJob}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>
|
||||
</DeleteButton>
|
||||
)}
|
||||
</ActionButtonWrapper>
|
||||
{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>
|
||||
)}
|
||||
</CardActionsRow>
|
||||
{errorMsg && (
|
||||
<AlertModal
|
||||
isOpen={errorMsg}
|
||||
@ -330,4 +290,4 @@ JobDetail.propTypes = {
|
||||
job: Job.isRequired,
|
||||
};
|
||||
|
||||
export default withI18n()(withRouter(JobDetail));
|
||||
export default withI18n()(JobDetail);
|
||||
|
||||
@ -5,7 +5,7 @@ import { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import { OrganizationsAPI } from '@api';
|
||||
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 ContentError from '@components/ContentError';
|
||||
import ContentLoading from '@components/ContentLoading';
|
||||
@ -92,13 +92,13 @@ function OrganizationDetail({ i18n, organization }) {
|
||||
/>
|
||||
)}
|
||||
</DetailList>
|
||||
{summary_fields.user_capabilities.edit && (
|
||||
<div css="margin-top: 10px; text-align: right;">
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities.edit && (
|
||||
<Button component={Link} to={`/organizations/${id}/edit`}>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</CardActionsRow>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ class Project extends Component {
|
||||
{project && (
|
||||
<Route
|
||||
path="/projects/:id/details"
|
||||
render={() => <ProjectDetail match={match} project={project} />}
|
||||
render={() => <ProjectDetail project={project} />}
|
||||
/>
|
||||
)}
|
||||
{project && (
|
||||
|
||||
@ -1,25 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
import { Project } from '@types';
|
||||
import { Config } from '@contexts/Config';
|
||||
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 { CredentialChip } from '@components/Chip';
|
||||
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 }) {
|
||||
const {
|
||||
allow_override,
|
||||
@ -137,7 +127,7 @@ function ProjectDetail({ project, i18n }) {
|
||||
user={summary_fields.modified_by}
|
||||
/>
|
||||
</DetailList>
|
||||
<ActionButtonWrapper>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities &&
|
||||
summary_fields.user_capabilities.edit && (
|
||||
<Button
|
||||
@ -148,7 +138,7 @@ function ProjectDetail({ project, i18n }) {
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
)}
|
||||
</ActionButtonWrapper>
|
||||
</CardActionsRow>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
@ -157,4 +147,4 @@ ProjectDetail.propTypes = {
|
||||
project: Project.isRequired,
|
||||
};
|
||||
|
||||
export default withI18n()(withRouter(ProjectDetail));
|
||||
export default withI18n()(ProjectDetail);
|
||||
|
||||
@ -125,7 +125,7 @@ class Team extends Component {
|
||||
{team && (
|
||||
<Route
|
||||
path="/teams/:id/details"
|
||||
render={() => <TeamDetail match={match} team={team} />}
|
||||
render={() => <TeamDetail team={team} />}
|
||||
/>
|
||||
)}
|
||||
{team && (
|
||||
|
||||
@ -1,57 +1,49 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
|
||||
import { CardBody } from '@components/Card';
|
||||
import { CardBody, CardActionsRow } from '@components/Card';
|
||||
import { DetailList, Detail } from '@components/DetailList';
|
||||
import { formatDateString } from '@util/dates';
|
||||
|
||||
class TeamDetail extends Component {
|
||||
render() {
|
||||
const {
|
||||
team: { name, description, created, modified, summary_fields },
|
||||
match,
|
||||
i18n,
|
||||
} = this.props;
|
||||
function TeamDetail({ team, i18n }) {
|
||||
const { name, description, created, modified, summary_fields } = team;
|
||||
const { id } = useParams();
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList>
|
||||
<Detail
|
||||
label={i18n._(t`Name`)}
|
||||
value={name}
|
||||
dataCy="team-detail-name"
|
||||
/>
|
||||
<Detail label={i18n._(t`Description`)} value={description} />
|
||||
<Detail
|
||||
label={i18n._(t`Organization`)}
|
||||
value={
|
||||
<Link to={`/organizations/${summary_fields.organization.id}`}>
|
||||
{summary_fields.organization.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Created`)}
|
||||
value={formatDateString(created)}
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Last Modified`)}
|
||||
value={formatDateString(modified)}
|
||||
/>
|
||||
</DetailList>
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList>
|
||||
<Detail
|
||||
label={i18n._(t`Name`)}
|
||||
value={name}
|
||||
dataCy="team-detail-name"
|
||||
/>
|
||||
<Detail label={i18n._(t`Description`)} value={description} />
|
||||
<Detail
|
||||
label={i18n._(t`Organization`)}
|
||||
value={
|
||||
<Link to={`/organizations/${summary_fields.organization.id}`}>
|
||||
{summary_fields.organization.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
|
||||
<Detail
|
||||
label={i18n._(t`Last Modified`)}
|
||||
value={formatDateString(modified)}
|
||||
/>
|
||||
</DetailList>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities.edit && (
|
||||
<div css="margin-top: 10px; text-align: right;">
|
||||
<Button component={Link} to={`/teams/${match.params.id}/edit`}>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button component={Link} to={`/teams/${id}/edit`}>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
)}
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
</CardActionsRow>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n()(withRouter(TeamDetail));
|
||||
export default withI18n()(TeamDetail);
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
import styled from 'styled-components';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { CardBody } from '@components/Card';
|
||||
import { CardBody, CardActionsRow } from '@components/Card';
|
||||
import ContentError from '@components/ContentError';
|
||||
import LaunchButton from '@components/LaunchButton';
|
||||
import ContentLoading from '@components/ContentLoading';
|
||||
@ -19,20 +19,12 @@ import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||
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)`
|
||||
dd& {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
class JobTemplateDetail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -317,7 +309,7 @@ class JobTemplateDetail extends Component {
|
||||
/>
|
||||
)}
|
||||
</DetailList>
|
||||
<ButtonGroup>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities.edit && (
|
||||
<Button
|
||||
component={Link}
|
||||
@ -340,7 +332,7 @@ class JobTemplateDetail extends Component {
|
||||
)}
|
||||
</LaunchButton>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</CardActionsRow>
|
||||
</CardBody>
|
||||
)
|
||||
);
|
||||
|
||||
@ -19,8 +19,8 @@ describe('<JobTemplateDetail />', () => {
|
||||
verbosity: 1,
|
||||
summary_fields: {
|
||||
user_capabilities: { edit: true },
|
||||
created_by: { username: 'Joe' },
|
||||
modified_by: { username: 'Joe' },
|
||||
created_by: { id: 1, username: 'Joe' },
|
||||
modified_by: { id: 1, username: 'Joe' },
|
||||
credentials: [
|
||||
{ id: 1, kind: 'ssh', name: 'Credential 1' },
|
||||
{ id: 2, kind: 'awx', name: 'Credential 2' },
|
||||
@ -28,6 +28,8 @@ describe('<JobTemplateDetail />', () => {
|
||||
inventory: { name: 'Inventory' },
|
||||
project: { name: 'Project' },
|
||||
},
|
||||
created: '2020-04-25T01:23:45.678901Z',
|
||||
modified: '2020-04-25T01:23:45.678901Z',
|
||||
};
|
||||
|
||||
const mockInstanceGroups = {
|
||||
@ -100,12 +102,14 @@ describe('<JobTemplateDetail />', () => {
|
||||
skip_tags: 'coffe,tea',
|
||||
summary_fields: {
|
||||
user_capabilities: { edit: false },
|
||||
created_by: { username: 'Joe' },
|
||||
modified_by: { username: 'Joe' },
|
||||
created_by: { id: 1, username: 'Joe' },
|
||||
modified_by: { id: 1, username: 'Joe' },
|
||||
inventory: { name: 'Inventory' },
|
||||
project: { name: 'Project' },
|
||||
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(
|
||||
<JobTemplateDetail template={regularUser} />
|
||||
|
||||
@ -134,7 +134,7 @@ class User extends Component {
|
||||
{user && (
|
||||
<Route
|
||||
path="/users/:id/details"
|
||||
render={() => <UserDetail match={match} user={user} />}
|
||||
render={() => <UserDetail user={user} />}
|
||||
/>
|
||||
)}
|
||||
<Route
|
||||
|
||||
@ -1,77 +1,69 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
|
||||
import { CardBody } from '@components/Card';
|
||||
import { CardBody, CardActionsRow } from '@components/Card';
|
||||
import { DetailList, Detail } from '@components/DetailList';
|
||||
import { formatDateString } from '@util/dates';
|
||||
|
||||
class UserDetail extends Component {
|
||||
render() {
|
||||
const {
|
||||
user: {
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
first_name,
|
||||
last_name,
|
||||
last_login,
|
||||
created,
|
||||
is_superuser,
|
||||
is_system_auditor,
|
||||
summary_fields,
|
||||
},
|
||||
i18n,
|
||||
} = this.props;
|
||||
function UserDetail({ user, i18n }) {
|
||||
const {
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
first_name,
|
||||
last_name,
|
||||
last_login,
|
||||
created,
|
||||
is_superuser,
|
||||
is_system_auditor,
|
||||
summary_fields,
|
||||
} = user;
|
||||
|
||||
let user_type;
|
||||
if (is_superuser) {
|
||||
user_type = i18n._(t`System Administrator`);
|
||||
} else if (is_system_auditor) {
|
||||
user_type = i18n._(t`System Auditor`);
|
||||
} else {
|
||||
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>
|
||||
);
|
||||
let user_type;
|
||||
if (is_superuser) {
|
||||
user_type = i18n._(t`System Administrator`);
|
||||
} else if (is_system_auditor) {
|
||||
user_type = i18n._(t`System Auditor`);
|
||||
} else {
|
||||
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>
|
||||
<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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user