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();
+ });
});