Adds edit buttons to Templates, Inventories, Organizations, and Projects list items when the user has edit capabilities.

This commit is contained in:
mabashian 2019-10-24 15:31:07 -04:00
parent bbbacd62ae
commit e0d8d35090
11 changed files with 324 additions and 29 deletions

View File

@ -0,0 +1,8 @@
import DataListCell from '@components/DataListCell';
import styled from 'styled-components';
export default styled(DataListCell)`
& > :not(:first-child) {
margin-left: 20px;
}
`;

View File

@ -0,0 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import ActionButtonCell from './ActionButtonCell';
describe('ActionButtonCell', () => {
test('renders the expected content', () => {
const wrapper = mount(<ActionButtonCell />);
expect(wrapper).toHaveLength(1);
});
});

View File

@ -0,0 +1 @@
export { default } from './ActionButtonCell';

View File

@ -5,12 +5,16 @@ import {
DataListItem,
DataListItemRow,
DataListItemCells,
Tooltip,
} from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons';
import ActionButtonCell from '@components/ActionButtonCell';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import ListActionButton from '@components/ListActionButton';
import VerticalSeparator from '@components/VerticalSeparator';
import { Inventory } from '@types';
@ -47,6 +51,23 @@ class InventoryListItem extends React.Component {
? i18n._(t`Smart Inventory`)
: i18n._(t`Inventory`)}
</DataListCell>,
<ActionButtonCell lastcolumn="true" key="action">
{inventory.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Inventory`)} position="top">
<ListActionButton
variant="plain"
component={Link}
to={`/inventories/${
inventory.kind === 'smart'
? 'smart_inventory'
: 'inventory'
}/${inventory.id}/edit`}
>
<PencilAltIcon />
</ListActionButton>
</Tooltip>
)}
</ActionButtonCell>,
]}
/>
</DataListItemRow>

View File

@ -18,6 +18,9 @@ describe('<InventoryListItem />', () => {
id: 1,
name: 'Default',
},
user_capabilities: {
edit: true,
},
},
}}
detailUrl="/inventories/inventory/1"
@ -28,4 +31,58 @@ describe('<InventoryListItem />', () => {
</I18nProvider>
);
});
test('edit button shown to users with edit capabilities', () => {
const wrapper = mountWithContexts(
<I18nProvider>
<MemoryRouter initialEntries={['/inventories']} initialIndex={0}>
<InventoryListItem
inventory={{
id: 1,
name: 'Inventory',
summary_fields: {
organization: {
id: 1,
name: 'Default',
},
user_capabilities: {
edit: true,
},
},
}}
detailUrl="/inventories/inventory/1"
isSelected
onSelect={() => {}}
/>
</MemoryRouter>
</I18nProvider>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('edit button hidden from users without edit capabilities', () => {
const wrapper = mountWithContexts(
<I18nProvider>
<MemoryRouter initialEntries={['/inventories']} initialIndex={0}>
<InventoryListItem
inventory={{
id: 1,
name: 'Inventory',
summary_fields: {
organization: {
id: 1,
name: 'Default',
},
user_capabilities: {
edit: false,
},
},
}}
detailUrl="/inventories/inventory/1"
isSelected
onSelect={() => {}}
/>
</MemoryRouter>
</I18nProvider>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
});
});

View File

@ -7,12 +7,16 @@ import {
DataListItem,
DataListItemRow,
DataListItemCells,
Tooltip,
} from '@patternfly/react-core';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { PencilAltIcon } from '@patternfly/react-icons';
import ActionButtonCell from '@components/ActionButtonCell';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import ListActionButton from '@components/ListActionButton';
import VerticalSeparator from '@components/VerticalSeparator';
import { Organization } from '@types';
@ -66,11 +70,7 @@ class OrganizationListItem extends React.Component {
</Link>
</span>
</DataListCell>,
<DataListCell
key="related-field-counts"
righthalf="true"
width={2}
>
<DataListCell key="related-field-counts">
<ListGroup>
{i18n._(t`Members`)}
<Badge isRead>
@ -84,6 +84,22 @@ class OrganizationListItem extends React.Component {
</Badge>
</ListGroup>
</DataListCell>,
<ActionButtonCell lastcolumn="true" key="action">
{organization.summary_fields.user_capabilities.edit && (
<Tooltip
content={i18n._(t`Edit Organization`)}
position="top"
>
<ListActionButton
variant="plain"
component={Link}
to={`/organizations/${organization.id}/edit`}
>
<PencilAltIcon />
</ListActionButton>
</Tooltip>
)}
</ActionButtonCell>,
]}
/>
</DataListItemRow>

View File

@ -20,6 +20,9 @@ describe('<OrganizationListItem />', () => {
users: 1,
teams: 1,
},
user_capabilities: {
edit: true,
},
},
}}
detailUrl="/organization/1"
@ -30,4 +33,58 @@ describe('<OrganizationListItem />', () => {
</I18nProvider>
);
});
test('edit button shown to users with edit capabilities', () => {
const wrapper = mountWithContexts(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<OrganizationListItem
organization={{
id: 1,
name: 'Org',
summary_fields: {
related_field_counts: {
users: 1,
teams: 1,
},
user_capabilities: {
edit: true,
},
},
}}
detailUrl="/organization/1"
isSelected
onSelect={() => {}}
/>
</MemoryRouter>
</I18nProvider>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('edit button hidden from users without edit capabilities', () => {
const wrapper = mountWithContexts(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<OrganizationListItem
organization={{
id: 1,
name: 'Org',
summary_fields: {
related_field_counts: {
users: 1,
teams: 1,
},
user_capabilities: {
edit: false,
},
},
}}
detailUrl="/organization/1"
isSelected
onSelect={() => {}}
/>
</MemoryRouter>
</I18nProvider>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
});
});

View File

@ -8,10 +8,14 @@ import {
Tooltip,
} from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import { PencilAltIcon, SyncIcon } from '@patternfly/react-icons';
import { Link as _Link } from 'react-router-dom';
import { SyncIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import ActionButtonCell from '@components/ActionButtonCell';
import ClipboardCopyButton from '@components/ClipboardCopyButton';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
@ -21,12 +25,6 @@ import { StatusIcon } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator';
import { Project } from '@types';
/* eslint-disable react/jsx-pascal-case */
const Link = styled(props => <_Link {...props} />)`
margin-right: 10px;
`;
/* eslint-enable react/jsx-pascal-case */
class ProjectListItem extends React.Component {
static propTypes = {
project: Project.isRequired,
@ -61,6 +59,11 @@ class ProjectListItem extends React.Component {
);
};
handleEditClick = project => {
const { history } = this.props;
history.push(`/projects/${project.id}/edit`);
};
render() {
const { project, isSelected, onSelect, detailUrl, i18n } = this.props;
const labelId = `check-action-${project.id}`;
@ -94,11 +97,13 @@ class ProjectListItem extends React.Component {
</Link>
</Tooltip>
)}
<span id={labelId}>
<Link to={`${detailUrl}`}>
<b>{project.name}</b>
</Link>
</span>
<Link
id={labelId}
to={`${detailUrl}`}
style={{ marginLeft: '10px' }}
>
<b>{project.name}</b>
</Link>
</DataListCell>,
<DataListCell key="type">
{project.scm_type.toUpperCase()}
@ -113,7 +118,7 @@ class ProjectListItem extends React.Component {
/>
) : null}
</DataListCell>,
<DataListCell lastcolumn="true" key="action">
<ActionButtonCell lastcolumn="true" key="action">
{project.summary_fields.user_capabilities.start && (
<Tooltip content={i18n._(t`Sync Project`)} position="top">
<ProjectSyncButton projectId={project.id}>
@ -125,7 +130,18 @@ class ProjectListItem extends React.Component {
</ProjectSyncButton>
</Tooltip>
)}
</DataListCell>,
{project.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Project`)} position="top">
<ListActionButton
variant="plain"
component={Link}
to={`/projects/${project.id}/edit`}
>
<PencilAltIcon />
</ListActionButton>
</Tooltip>
)}
</ActionButtonCell>,
]}
/>
</DataListItemRow>

View File

@ -59,4 +59,56 @@ describe('<ProjectsListItem />', () => {
);
expect(wrapper.find('ProjectSyncButton').exists()).toBeFalsy();
});
test('edit button shown to users with edit capabilities', () => {
const wrapper = mountWithContexts(
<ProjectsListItem
isSelected={false}
detailUrl="/project/1"
onSelect={() => {}}
project={{
id: 1,
name: 'Project 1',
url: '/api/v2/projects/1',
type: 'project',
scm_type: 'git',
summary_fields: {
last_job: {
id: 9000,
status: 'successful',
},
user_capabilities: {
edit: true,
},
},
}}
/>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('edit button hidden from users without edit capabilities', () => {
const wrapper = mountWithContexts(
<ProjectsListItem
isSelected={false}
detailUrl="/project/1"
onSelect={() => {}}
project={{
id: 1,
name: 'Project 1',
url: '/api/v2/projects/1',
type: 'project',
scm_type: 'git',
summary_fields: {
last_job: {
id: 9000,
status: 'successful',
},
user_capabilities: {
edit: false,
},
},
}}
/>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
});
});

View File

@ -8,8 +8,9 @@ import {
} from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import { RocketIcon } from '@patternfly/react-icons';
import { PencilAltIcon, RocketIcon } from '@patternfly/react-icons';
import ActionButtonCell from '@components/ActionButtonCell';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import LaunchButton from '@components/LaunchButton';
@ -20,6 +21,16 @@ import { toTitleCase } from '@util/strings';
import styled from 'styled-components';
const rightStyle = `
@media screen and (max-width: 768px) {
&& {
padding-top: 0px;
flex: 0 0 33%;
padding-right: 20px;
}
}
`;
const DataListItemCells = styled(PFDataListItemCells)`
display: flex;
@media screen and (max-width: 768px) {
@ -37,13 +48,10 @@ const LeftDataListCell = styled(DataListCell)`
}
`;
const RightDataListCell = styled(DataListCell)`
@media screen and (max-width: 768px) {
&& {
padding-top: 0px;
flex: 0 0 33%;
padding-right: 20px;
}
}
${rightStyle}
`;
const RightActionButtonCell = styled(ActionButtonCell)`
${rightStyle}
`;
class TemplateListItem extends Component {
@ -87,8 +95,8 @@ class TemplateListItem extends Component {
>
<Sparkline jobs={template.summary_fields.recent_jobs} />
</RightDataListCell>,
<RightDataListCell
css="max-width: 40px;"
<RightActionButtonCell
css="max-width: 80px;"
righthalf="true"
lastcolumn="true"
key="launch"
@ -107,7 +115,18 @@ class TemplateListItem extends Component {
</LaunchButton>
</Tooltip>
)}
</RightDataListCell>,
{template.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Template`)} position="top">
<ListActionButton
variant="plain"
component={Link}
to={`/templates/${template.type}/${template.id}/edit`}
>
<PencilAltIcon />
</ListActionButton>
</Tooltip>
)}
</RightActionButtonCell>,
]}
/>
</DataListItemRow>

View File

@ -43,4 +43,42 @@ describe('<TemplatesListItem />', () => {
);
expect(wrapper.find('LaunchButton').exists()).toBeFalsy();
});
test('edit button shown to users with edit capabilities', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
isSelected={false}
template={{
id: 1,
name: 'Template 1',
url: '/templates/job_template/1',
type: 'job_template',
summary_fields: {
user_capabilities: {
edit: true,
},
},
}}
/>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('edit button hidden from users without edit capabilities', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
isSelected={false}
template={{
id: 1,
name: 'Template 1',
url: '/templates/job_template/1',
type: 'job_template',
summary_fields: {
user_capabilities: {
edit: false,
},
},
}}
/>
);
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
});
});