Skip to content

Commit

Permalink
Fixes #33668 - Add permissions checks to CV UI (#9724)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjha4 committed Oct 26, 2021
1 parent e7f754b commit c296322
Show file tree
Hide file tree
Showing 52 changed files with 714 additions and 413 deletions.
18 changes: 10 additions & 8 deletions app/views/katello/api/v2/content_views/base.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,16 @@ child :versions => :versions do
attributes :environment_ids
end

node :permissions do |cv|
{
:view_content_views => cv.readable?,
:edit_content_views => cv.editable?,
:destroy_content_views => cv.deletable?,
:publish_content_views => cv.publishable?,
:promote_or_remove_content_views => cv.promotable_or_removable?
}
if params.key?(:include_permissions)
node :permissions do |cv|
{
:view_content_views => cv.readable?,
:edit_content_views => cv.editable?,
:destroy_content_views => cv.deletable?,
:publish_content_views => cv.publishable?,
:promote_or_remove_content_views => cv.promotable_or_removable?
}
end
end

child :components => :components do
Expand Down
1 change: 1 addition & 0 deletions app/views/katello/api/v2/content_views/index.json.rabl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
object false

extends "katello/api/v2/common/metadata"
extends 'katello/api/v2/content_views/permissions'

child @collection[:results] => :results do
extends "katello/api/v2/content_views/base"
Expand Down
8 changes: 8 additions & 0 deletions app/views/katello/api/v2/content_views/permissions.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
collection @content_views

if params.key?(:include_permissions)
user = User.current # current_user is not available here
node do
node(:can_create) { user.can?("create_content_views") }
end
end
7 changes: 6 additions & 1 deletion webpack/components/ActionableDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ const ActionableDetail = ({
onEdit,
currentAttribute,
setCurrentAttribute,
disabled,
}) => {
const displayProps = { attribute, value, onEdit };
const displayProps = {
attribute, value, onEdit, disabled,
};

return (
<React.Fragment key={label}>
Expand Down Expand Up @@ -65,6 +68,7 @@ ActionableDetail.propTypes = {
tooltip: PropTypes.string,
currentAttribute: PropTypes.string,
setCurrentAttribute: PropTypes.func,
disabled: PropTypes.bool,
};

ActionableDetail.defaultProps = {
Expand All @@ -74,6 +78,7 @@ ActionableDetail.defaultProps = {
value: null,
currentAttribute: undefined,
setCurrentAttribute: undefined,
disabled: false,
};

export default ActionableDetail;
7 changes: 6 additions & 1 deletion webpack/components/EditableSwitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Switch } from '@patternfly/react-core';
import { noop } from 'foremanReact/common/helpers';
import PropTypes from 'prop-types';

const EditableSwitch = ({ value, attribute, onEdit }) => {
const EditableSwitch = ({
value, attribute, onEdit, disabled,
}) => {
const identifier = `${attribute} switch`;

return (
Expand All @@ -12,6 +14,7 @@ const EditableSwitch = ({ value, attribute, onEdit }) => {
aria-label={identifier}
isChecked={value}
onChange={v => onEdit(v, attribute)}
disabled={disabled}
/>
);
};
Expand All @@ -20,11 +23,13 @@ EditableSwitch.propTypes = {
value: PropTypes.bool.isRequired,
attribute: PropTypes.string,
onEdit: PropTypes.func,
disabled: PropTypes.bool,
};

EditableSwitch.defaultProps = {
attribute: '',
onEdit: noop,
disabled: false,
};

export default EditableSwitch;
27 changes: 16 additions & 11 deletions webpack/components/EditableTextInput/EditableTextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Loading from '../Loading';
import './editableTextInput.scss';

const EditableTextInput = ({
onEdit, value, textArea, attribute, placeholder, component, currentAttribute, setCurrentAttribute,
onEdit, value, textArea, attribute, placeholder,
component, currentAttribute, setCurrentAttribute, disabled,
}) => {
// Tracks input box state
const [inputValue, setInputValue] = useState(value);
Expand Down Expand Up @@ -106,16 +107,18 @@ const EditableTextInput = ({
{value || (<i>{placeholder}</i>)}
</Text>
</SplitItem>
<SplitItem>
<Button
className="foreman-edit-icon"
aria-label={`edit ${attribute}`}
variant="plain"
onClick={onEditClick}
>
<PencilAltIcon />
</Button>
</SplitItem>
{!disabled &&
<SplitItem>
<Button
className="foreman-edit-icon"
aria-label={`edit ${attribute}`}
variant="plain"
onClick={onEditClick}
>
<PencilAltIcon />
</Button>
</SplitItem>
}
</Split>
);
};
Expand All @@ -129,6 +132,7 @@ EditableTextInput.propTypes = {
component: PropTypes.string,
currentAttribute: PropTypes.string,
setCurrentAttribute: PropTypes.func,
disabled: PropTypes.bool,
};

EditableTextInput.defaultProps = {
Expand All @@ -138,6 +142,7 @@ EditableTextInput.defaultProps = {
component: undefined,
currentAttribute: undefined,
setCurrentAttribute: undefined,
disabled: false,
};

export default EditableTextInput;
46 changes: 31 additions & 15 deletions webpack/components/Table/EmptyStateMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EmptyState,
Bullseye,
Title } from '@patternfly/react-core';
import PropTypes from 'prop-types';
import { translate as __ } from 'foremanReact/common/I18n';
import { CubeIcon, ExclamationCircleIcon, SearchIcon } from '@patternfly/react-icons';
import { global_danger_color_200 as dangerColor } from '@patternfly/react-tokens';

Expand All @@ -18,19 +19,34 @@ const KatelloEmptyStateIcon = ({ error, search, customIcon }) => {

const EmptyStateMessage = ({
title, body, error, search, customIcon,
}) => (
<Bullseye>
<EmptyState variant={EmptyStateVariant.small}>
<KatelloEmptyStateIcon error={!!error} search={search} customIcon={customIcon} />
<Title headingLevel="h2" size="lg">
{title}
</Title>
<EmptyStateBody>
{body}
</EmptyStateBody>
</EmptyState>
</Bullseye>
);
}) => {
let emptyStateTitle = title;
let emptyStateBody = body;
if (error) {
if (error?.response?.data?.error) {
const { response: { data: { error: { message, details } } } } = error;
emptyStateTitle = message;
emptyStateBody = details;
} else if (error?.response?.status) {
const { response: { status } } = error;
emptyStateTitle = status;
emptyStateBody = error?.response?.data?.displayMessage || __('Something went wrong! Please check server logs!');
}
}
return (
<Bullseye>
<EmptyState variant={EmptyStateVariant.small}>
<KatelloEmptyStateIcon error={!!error} search={search} customIcon={customIcon} />
<Title headingLevel="h2" size="lg">
{emptyStateTitle}
</Title>
<EmptyStateBody>
{emptyStateBody}
</EmptyStateBody>
</EmptyState>
</Bullseye>
);
};

KatelloEmptyStateIcon.propTypes = {
error: PropTypes.bool,
Expand All @@ -56,8 +72,8 @@ EmptyStateMessage.propTypes = {
};

EmptyStateMessage.defaultProps = {
title: 'Unable to connect',
body: 'There was an error retrieving data from the server. Check your connection and try again.',
title: __('Unable to connect'),
body: __('There was an error retrieving data from the server. Check your connection and try again.'),
error: undefined,
search: false,
customIcon: undefined,
Expand Down
1 change: 1 addition & 0 deletions webpack/scenes/ContentViews/ContentViewsActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const createContentViewsParams = (extraParams) => {
const getParams = {
organization_id: orgId(),
nondefault: true,
include_permissions: true,
...extraParams,
};
if (extraParams?.include_default) delete getParams.nondefault;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ContentViewsPage from '../../ContentViewsPage.js';

const cvIndexData = require('./CvData.fixtures');

const cvIndexPath = api.getApiUrl('/content_views?organization_id=1&nondefault=true&per_page=20&page=1');
const cvIndexPath = api.getApiUrl('/content_views?organization_id=1&nondefault=true&include_permissions=true&per_page=20&page=1');
const autocompleteUrl = '/content_views/auto_complete_search';
const renderOptions = { apiNamespace: CONTENT_VIEWS_KEY };
const environmentPathsPath = api.getApiUrl('/organizations/1/environments/paths');
Expand All @@ -28,7 +28,7 @@ const affectedActivationKeysData = require('../../Details/Versions/Delete/__test
const hostURL = foremanApi.getApiUrl('/hosts');
const affectedHostData = require('./affectedHosts.fixtures.json');

const cVDropDownOptionsPath = api.getApiUrl('/content_views?organization_id=1&environment_id=9&include_default=true&full_result=true');
const cVDropDownOptionsPath = api.getApiUrl('/content_views?organization_id=1&environment_id=9&include_default=true&include_permissions=true&full_result=true');
const cVDropDownOptionsData = require('../../Details/Versions/Delete/__tests__/cvDropDownOptionsResponse.fixture.json');

const cvDeleteUrl = api.getApiUrl('/content_views/20/remove');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import SelectableDropdown from '../../../../components/SelectableDropdown/Select
import '../../../../components/EditableTextInput/editableTextInput.scss';
import ComponentContentViewAddModal from './ComponentContentViewAddModal';
import ComponentContentViewBulkAddModal from './ComponentContentViewBulkAddModal';
import { hasPermission } from '../../helpers';

const ContentViewComponents = ({ cvId, details }) => {
const response = useSelector(state => selectCVComponents(state, cvId));
Expand Down Expand Up @@ -67,7 +68,7 @@ const ContentViewComponents = ({ cvId, details }) => {
const addComponentsResolved = componentAddedStatus === STATUS.RESOLVED;
const removeComponentsResolved = componentRemovedStatus === STATUS.RESOLVED;

const { label } = details || {};
const { label, permissions } = details || {};

const bulkRemoveEnabled = () => rows.some(row => row.selected && row.added);
const bulkAddEnabled = () => rows.some(row => row.selected && !row.added);
Expand Down Expand Up @@ -140,7 +141,7 @@ const ContentViewComponents = ({ cvId, details }) => {
<SplitItem>
<ComponentVersion {...{ componentCV }} />
</SplitItem>
{componentCvId && cvVersion &&
{hasPermission(permissions, 'edit_content_views') && componentCvId && cvVersion &&
<SplitItem>
<Button
className="foreman-edit-icon"
Expand Down Expand Up @@ -175,7 +176,7 @@ const ContentViewComponents = ({ cvId, details }) => {
});
});
return newRows;
}, [onAdd, results]);
}, [onAdd, results, permissions]);

const actionResolver = (rowData, { _rowIndex }) => [
{
Expand Down Expand Up @@ -237,8 +238,8 @@ const ContentViewComponents = ({ cvId, details }) => {
status,
activeFilters,
defaultFilters,
actionResolver,
}}
actionResolver={hasPermission(permissions, 'edit_content_views') ? actionResolver : null}
onSelect={onSelect(rows, setRows)}
cells={columnHeaders}
variant={TableVariant.compact}
Expand All @@ -258,23 +259,25 @@ const ContentViewComponents = ({ cvId, details }) => {
placeholderText={__('Status')}
/>
</SplitItem>
<SplitItem>
<ActionList>
<ActionListItem>
<Button onClick={addBulk} isDisabled={!(bulkAddEnabled())} variant="secondary" aria-label="bulk_add_components">
{__('Add content views')}
</Button>
</ActionListItem>
<ActionListItem>
<Dropdown
toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />}
isOpen={bulkActionOpen}
isPlain
dropdownItems={dropdownItems}
/>
</ActionListItem>
</ActionList>
</SplitItem>
{hasPermission(permissions, 'edit_content_views') &&
<SplitItem>
<ActionList>
<ActionListItem>
<Button onClick={addBulk} isDisabled={!(bulkAddEnabled())} variant="secondary" aria-label="bulk_add_components">
{__('Add content views')}
</Button>
</ActionListItem>
<ActionListItem>
<Dropdown
toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />}
isOpen={bulkActionOpen}
isPlain
dropdownItems={dropdownItems}
/>
</ActionListItem>
</ActionList>
</SplitItem>
}
</Split>
{versionEditing &&
<ComponentContentViewAddModal
Expand Down Expand Up @@ -303,12 +306,14 @@ ContentViewComponents.propTypes = {
cvId: PropTypes.number.isRequired,
details: PropTypes.shape({
label: PropTypes.string,
permissions: PropTypes.shape({}),
}),
};

ContentViewComponents.defaultProps = {
details: {
label: '',
permissions: {},
},
};

Expand Down

0 comments on commit c296322

Please sign in to comment.