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, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons';
import ActionButtonCell from '@components/ActionButtonCell';
import DataListCell from '@components/DataListCell'; import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck'; import DataListCheck from '@components/DataListCheck';
import ListActionButton from '@components/ListActionButton';
import VerticalSeparator from '@components/VerticalSeparator'; import VerticalSeparator from '@components/VerticalSeparator';
import { Inventory } from '@types'; import { Inventory } from '@types';
@@ -47,6 +51,23 @@ class InventoryListItem extends React.Component {
? i18n._(t`Smart Inventory`) ? i18n._(t`Smart Inventory`)
: i18n._(t`Inventory`)} : i18n._(t`Inventory`)}
</DataListCell>, </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> </DataListItemRow>

View File

@@ -18,6 +18,9 @@ describe('<InventoryListItem />', () => {
id: 1, id: 1,
name: 'Default', name: 'Default',
}, },
user_capabilities: {
edit: true,
},
}, },
}} }}
detailUrl="/inventories/inventory/1" detailUrl="/inventories/inventory/1"
@@ -28,4 +31,58 @@ describe('<InventoryListItem />', () => {
</I18nProvider> </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, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { PencilAltIcon } from '@patternfly/react-icons';
import ActionButtonCell from '@components/ActionButtonCell';
import DataListCell from '@components/DataListCell'; import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck'; import DataListCheck from '@components/DataListCheck';
import ListActionButton from '@components/ListActionButton';
import VerticalSeparator from '@components/VerticalSeparator'; import VerticalSeparator from '@components/VerticalSeparator';
import { Organization } from '@types'; import { Organization } from '@types';
@@ -66,11 +70,7 @@ class OrganizationListItem extends React.Component {
</Link> </Link>
</span> </span>
</DataListCell>, </DataListCell>,
<DataListCell <DataListCell key="related-field-counts">
key="related-field-counts"
righthalf="true"
width={2}
>
<ListGroup> <ListGroup>
{i18n._(t`Members`)} {i18n._(t`Members`)}
<Badge isRead> <Badge isRead>
@@ -84,6 +84,22 @@ class OrganizationListItem extends React.Component {
</Badge> </Badge>
</ListGroup> </ListGroup>
</DataListCell>, </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> </DataListItemRow>

View File

@@ -20,6 +20,9 @@ describe('<OrganizationListItem />', () => {
users: 1, users: 1,
teams: 1, teams: 1,
}, },
user_capabilities: {
edit: true,
},
}, },
}} }}
detailUrl="/organization/1" detailUrl="/organization/1"
@@ -30,4 +33,58 @@ describe('<OrganizationListItem />', () => {
</I18nProvider> </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, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { t } from '@lingui/macro'; 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 { Link as _Link } from 'react-router-dom';
import { SyncIcon } from '@patternfly/react-icons'; import { SyncIcon } from '@patternfly/react-icons';
import styled from 'styled-components'; import styled from 'styled-components';
import ActionButtonCell from '@components/ActionButtonCell';
import ClipboardCopyButton from '@components/ClipboardCopyButton'; import ClipboardCopyButton from '@components/ClipboardCopyButton';
import DataListCell from '@components/DataListCell'; import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck'; import DataListCheck from '@components/DataListCheck';
@@ -21,12 +25,6 @@ import { StatusIcon } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator'; import VerticalSeparator from '@components/VerticalSeparator';
import { Project } from '@types'; 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 { class ProjectListItem extends React.Component {
static propTypes = { static propTypes = {
project: Project.isRequired, 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() { render() {
const { project, isSelected, onSelect, detailUrl, i18n } = this.props; const { project, isSelected, onSelect, detailUrl, i18n } = this.props;
const labelId = `check-action-${project.id}`; const labelId = `check-action-${project.id}`;
@@ -94,11 +97,13 @@ class ProjectListItem extends React.Component {
</Link> </Link>
</Tooltip> </Tooltip>
)} )}
<span id={labelId}> <Link
<Link to={`${detailUrl}`}> id={labelId}
<b>{project.name}</b> to={`${detailUrl}`}
</Link> style={{ marginLeft: '10px' }}
</span> >
<b>{project.name}</b>
</Link>
</DataListCell>, </DataListCell>,
<DataListCell key="type"> <DataListCell key="type">
{project.scm_type.toUpperCase()} {project.scm_type.toUpperCase()}
@@ -113,7 +118,7 @@ class ProjectListItem extends React.Component {
/> />
) : null} ) : null}
</DataListCell>, </DataListCell>,
<DataListCell lastcolumn="true" key="action"> <ActionButtonCell lastcolumn="true" key="action">
{project.summary_fields.user_capabilities.start && ( {project.summary_fields.user_capabilities.start && (
<Tooltip content={i18n._(t`Sync Project`)} position="top"> <Tooltip content={i18n._(t`Sync Project`)} position="top">
<ProjectSyncButton projectId={project.id}> <ProjectSyncButton projectId={project.id}>
@@ -125,7 +130,18 @@ class ProjectListItem extends React.Component {
</ProjectSyncButton> </ProjectSyncButton>
</Tooltip> </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> </DataListItemRow>

View File

@@ -59,4 +59,56 @@ describe('<ProjectsListItem />', () => {
); );
expect(wrapper.find('ProjectSyncButton').exists()).toBeFalsy(); 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'; } from '@patternfly/react-core';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; 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 DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck'; import DataListCheck from '@components/DataListCheck';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
@@ -20,6 +21,16 @@ import { toTitleCase } from '@util/strings';
import styled from 'styled-components'; 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)` const DataListItemCells = styled(PFDataListItemCells)`
display: flex; display: flex;
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
@@ -37,13 +48,10 @@ const LeftDataListCell = styled(DataListCell)`
} }
`; `;
const RightDataListCell = styled(DataListCell)` const RightDataListCell = styled(DataListCell)`
@media screen and (max-width: 768px) { ${rightStyle}
&& { `;
padding-top: 0px; const RightActionButtonCell = styled(ActionButtonCell)`
flex: 0 0 33%; ${rightStyle}
padding-right: 20px;
}
}
`; `;
class TemplateListItem extends Component { class TemplateListItem extends Component {
@@ -87,8 +95,8 @@ class TemplateListItem extends Component {
> >
<Sparkline jobs={template.summary_fields.recent_jobs} /> <Sparkline jobs={template.summary_fields.recent_jobs} />
</RightDataListCell>, </RightDataListCell>,
<RightDataListCell <RightActionButtonCell
css="max-width: 40px;" css="max-width: 80px;"
righthalf="true" righthalf="true"
lastcolumn="true" lastcolumn="true"
key="launch" key="launch"
@@ -107,7 +115,18 @@ class TemplateListItem extends Component {
</LaunchButton> </LaunchButton>
</Tooltip> </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> </DataListItemRow>

View File

@@ -43,4 +43,42 @@ describe('<TemplatesListItem />', () => {
); );
expect(wrapper.find('LaunchButton').exists()).toBeFalsy(); 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();
});
}); });