From e0d8d35090b528bc896e10ae64addca5ea85bfcf Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 24 Oct 2019 15:31:07 -0400 Subject: [PATCH 1/5] 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(); + }); }); From 9af3fa557bad32bcabddda60574ba0113b7662ce Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 28 Oct 2019 15:08:27 -0400 Subject: [PATCH 2/5] Fix merge conflict fallout. Remove stale edit click handler. --- .../src/screens/Project/ProjectList/ProjectListItem.jsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index d2927ba796..3df2871ac6 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -11,10 +11,6 @@ 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'; @@ -59,11 +55,6 @@ 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}`; From d8814b716212326b29bdc25e58b5cfe0406c85e5 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 28 Oct 2019 15:11:29 -0400 Subject: [PATCH 3/5] Add displayName so that ActionButtonCell can be referenced in tests --- awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx index cc94bc72f1..694c363bbe 100644 --- a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx +++ b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx @@ -1,6 +1,7 @@ import DataListCell from '@components/DataListCell'; import styled from 'styled-components'; +DataListCell.displayName = 'ActionButtonCell'; export default styled(DataListCell)` & > :not(:first-child) { margin-left: 20px; From 7b1158ee8e55e4deecd43da2462a0127122dd012 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 28 Oct 2019 15:31:03 -0400 Subject: [PATCH 4/5] Fix failing unit tests due to missing scm_revision key --- .../src/screens/Project/ProjectList/ProjectListItem.test.jsx | 2 ++ 1 file changed, 2 insertions(+) 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 287beefd12..b556e0957f 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx @@ -71,6 +71,7 @@ describe('', () => { url: '/api/v2/projects/1', type: 'project', scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', summary_fields: { last_job: { id: 9000, @@ -97,6 +98,7 @@ describe('', () => { url: '/api/v2/projects/1', type: 'project', scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', summary_fields: { last_job: { id: 9000, From 35c27c8b16268c2376c0e03267258c5d0798f050 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 29 Oct 2019 14:45:05 -0400 Subject: [PATCH 5/5] Tweak ActionButtonCell definition and export --- .../src/components/ActionButtonCell/ActionButtonCell.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx index 694c363bbe..ce8f58ef27 100644 --- a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx +++ b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx @@ -1,9 +1,10 @@ import DataListCell from '@components/DataListCell'; import styled from 'styled-components'; -DataListCell.displayName = 'ActionButtonCell'; -export default styled(DataListCell)` +const ActionButtonCell = styled(DataListCell)` & > :not(:first-child) { margin-left: 20px; } `; +ActionButtonCell.displayName = 'ActionButtonCell'; +export default ActionButtonCell;