From e0d8d35090b528bc896e10ae64addca5ea85bfcf Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 24 Oct 2019 15:31:07 -0400 Subject: [PATCH] Adds edit buttons to Templates, Inventories, Organizations, and Projects list items when the user has edit capabilities. --- .../ActionButtonCell/ActionButtonCell.jsx | 8 +++ .../ActionButtonCell.test.jsx | 10 ++++ .../src/components/ActionButtonCell/index.js | 1 + .../InventoryList/InventoryListItem.jsx | 21 +++++++ .../InventoryList/InventoryListItem.test.jsx | 57 +++++++++++++++++++ .../OrganizationList/OrganizationListItem.jsx | 26 +++++++-- .../OrganizationListItem.test.jsx | 57 +++++++++++++++++++ .../Project/ProjectList/ProjectListItem.jsx | 42 +++++++++----- .../ProjectList/ProjectListItem.test.jsx | 52 +++++++++++++++++ .../TemplateList/TemplateListItem.jsx | 41 +++++++++---- .../TemplateList/TemplatesListItem.test.jsx | 38 +++++++++++++ 11 files changed, 324 insertions(+), 29 deletions(-) create mode 100644 awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx create mode 100644 awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx create mode 100644 awx/ui_next/src/components/ActionButtonCell/index.js diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx new file mode 100644 index 0000000000..cc94bc72f1 --- /dev/null +++ b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx @@ -0,0 +1,8 @@ +import DataListCell from '@components/DataListCell'; +import styled from 'styled-components'; + +export default styled(DataListCell)` + & > :not(:first-child) { + margin-left: 20px; + } +`; diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx new file mode 100644 index 0000000000..894e481a65 --- /dev/null +++ b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx @@ -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(); + expect(wrapper).toHaveLength(1); + }); +}); diff --git a/awx/ui_next/src/components/ActionButtonCell/index.js b/awx/ui_next/src/components/ActionButtonCell/index.js new file mode 100644 index 0000000000..6eff34a9c4 --- /dev/null +++ b/awx/ui_next/src/components/ActionButtonCell/index.js @@ -0,0 +1 @@ +export { default } from './ActionButtonCell'; diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.jsx b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.jsx index 4aba0a820e..eb2a021e89 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.jsx @@ -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`)} , + + {inventory.summary_fields.user_capabilities.edit && ( + + + + + + )} + , ]} /> diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx index bdccf68f6d..4485d9034d 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx @@ -18,6 +18,9 @@ describe('', () => { id: 1, name: 'Default', }, + user_capabilities: { + edit: true, + }, }, }} detailUrl="/inventories/inventory/1" @@ -28,4 +31,58 @@ describe('', () => { ); }); + test('edit button shown to users with edit capabilities', () => { + const wrapper = mountWithContexts( + + + {}} + /> + + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); + }); + test('edit button hidden from users without edit capabilities', () => { + const wrapper = mountWithContexts( + + + {}} + /> + + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); + }); }); diff --git a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.jsx b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.jsx index 0d5675091b..d5e8ed7cf7 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.jsx @@ -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 { , - + {i18n._(t`Members`)} @@ -84,6 +84,22 @@ class OrganizationListItem extends React.Component { , + + {organization.summary_fields.user_capabilities.edit && ( + + + + + + )} + , ]} /> diff --git a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.test.jsx index c67104f96e..410dac6cfb 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.test.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationListItem.test.jsx @@ -20,6 +20,9 @@ describe('', () => { users: 1, teams: 1, }, + user_capabilities: { + edit: true, + }, }, }} detailUrl="/organization/1" @@ -30,4 +33,58 @@ describe('', () => { ); }); + test('edit button shown to users with edit capabilities', () => { + const wrapper = mountWithContexts( + + + {}} + /> + + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); + }); + test('edit button hidden from users without edit capabilities', () => { + const wrapper = mountWithContexts( + + + {}} + /> + + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); + }); }); diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 03fd7df13f..d2927ba796 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -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 { )} - - - {project.name} - - + + {project.name} + , {project.scm_type.toUpperCase()} @@ -113,7 +118,7 @@ class ProjectListItem extends React.Component { /> ) : null} , - + {project.summary_fields.user_capabilities.start && ( @@ -125,7 +130,18 @@ class ProjectListItem extends React.Component { )} - , + {project.summary_fields.user_capabilities.edit && ( + + + + + + )} + , ]} /> diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx index bb5a7bcc4b..287beefd12 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx @@ -59,4 +59,56 @@ describe('', () => { ); expect(wrapper.find('ProjectSyncButton').exists()).toBeFalsy(); }); + test('edit button shown to users with edit capabilities', () => { + const wrapper = mountWithContexts( + {}} + 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( + {}} + 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(); + }); }); diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index 99f946032c..ac6e2807cd 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -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 { > , - )} - , + {template.summary_fields.user_capabilities.edit && ( + + + + + + )} + , ]} /> diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplatesListItem.test.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplatesListItem.test.jsx index e9376d84da..84091cfa3a 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplatesListItem.test.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplatesListItem.test.jsx @@ -43,4 +43,42 @@ describe('', () => { ); expect(wrapper.find('LaunchButton').exists()).toBeFalsy(); }); + test('edit button shown to users with edit capabilities', () => { + const wrapper = mountWithContexts( + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); + }); + test('edit button hidden from users without edit capabilities', () => { + const wrapper = mountWithContexts( + + ); + expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); + }); });