webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js in katello-4.2.2 vs webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js in katello-4.3.0.rc1
- old
+ new
@@ -1,9 +1,10 @@
import React, { useState, useCallback } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { useDispatch, useSelector } from 'react-redux';
-import { Bullseye, Split, SplitItem, Button } from '@patternfly/react-core';
+import { Bullseye, Split, SplitItem, Button, ActionList,
+ ActionListItem, Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core';
import { Link } from 'react-router-dom';
import { TableVariant, fitContent, TableText } from '@patternfly/react-table';
import { PencilAltIcon } from '@patternfly/react-icons';
import { STATUS } from 'foremanReact/constants';
import { translate as __ } from 'foremanReact/common/I18n';
@@ -29,26 +30,31 @@
import ContentViewIcon from '../../components/ContentViewIcon';
import { ADDED, ALL_STATUSES, NOT_ADDED } from '../../ContentViewsConstants';
import SelectableDropdown from '../../../../components/SelectableDropdown/SelectableDropdown';
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));
const status = useSelector(state => selectCVComponentsStatus(state, cvId));
const error = useSelector(state => selectCVComponentsError(state, cvId));
+ const { results, ...metadata } = response;
const componentAddedStatus = useSelector(state => selectCVComponentAddStatus(state, cvId));
const componentRemovedStatus = useSelector(state => selectCVComponentRemoveStatus(state, cvId));
const [rows, setRows] = useState([]);
- const [metadata, setMetadata] = useState({});
const [searchQuery, updateSearchQuery] = useState('');
const [statusSelected, setStatusSelected] = useState(ALL_STATUSES);
const [versionEditing, setVersionEditing] = useState(false);
const [compositeCvEditing, setCompositeCvEditing] = useState(null);
const [componentCvEditing, setComponentCvEditing] = useState(null);
const [componentLatest, setComponentLatest] = useState(false);
const [componentId, setComponentId] = useState(null);
+ const [selectedComponentsToAdd, setSelectedComponentsToAdd] = useState(null);
+ const [bulkAdding, setBulkAdding] = useState(false);
+ const [bulkActionOpen, setBulkActionOpen] = useState(false);
const dispatch = useDispatch();
const columnHeaders = [
{ title: __('Type'), transforms: [fitContent] },
{ title: __('Name') },
@@ -60,25 +66,26 @@
];
const loading = status === STATUS.PENDING;
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);
const onAdd = useCallback(({
componentCvId, published, added, latest,
}) => {
- if (published) {
- dispatch(getContentViewDetails(componentCvId));
+ if (published) { // If 1 or more versions present, open a modal to let user select version
+ dispatch(getContentViewDetails(componentCvId, 'bulk_add'));
setVersionEditing(true);
setCompositeCvEditing(cvId);
setComponentCvEditing(componentCvId);
setComponentLatest(latest);
setComponentId(added);
- } else {
+ } else { // if no versions are present, default to always latest and add cv without modal
dispatch(addComponent({
compositeContentViewId: cvId,
components: [{ latest: true, content_view_id: componentCvId }],
}));
}
@@ -91,69 +98,87 @@
compositeContentViewId: cvId,
component_ids: componentIds,
}));
};
+ const addBulk = () => {
+ const rowsToAdd = rows.filter(row => row.selected && !row.added);
+ setSelectedComponentsToAdd(rowsToAdd);
+ setCompositeCvEditing(cvId);
+ setBulkAdding(true);
+ };
+
const onRemove = (componentIdToRemove) => {
dispatch(removeComponent({
compositeContentViewId: cvId,
component_ids: [componentIdToRemove],
}));
};
- const buildRows = useCallback((results) => {
+ const toggleBulkAction = () => {
+ setBulkActionOpen(!bulkActionOpen);
+ };
+
+ const buildRows = useCallback(() => {
const newRows = [];
results.forEach((componentCV) => {
const {
- id: componentCvId, content_view: cv, content_view_version: cvVersion, latest,
+ id: componentCvId, content_view: cv, content_view_version: cvVersion,
+ latest, component_content_view_versions: componentCvVersions,
} = componentCV;
const { environments, repositories } = cvVersion || {};
const {
id,
name,
description,
} = cv;
const cells = [
{ title: <Bullseye><ContentViewIcon composite={false} /></Bullseye> },
- { title: <Link to={urlBuilder('labs/content_views', '', id)}>{name}</Link> },
+ { title: <a href={urlBuilder('content_views', '') + id}>{name}</a> },
{
title:
<Split>
<SplitItem>
<ComponentVersion {...{ componentCV }} />
</SplitItem>
- {componentCvId && cvVersion &&
+ {hasPermission(permissions, 'edit_content_views') && componentCvId && cvVersion &&
<SplitItem>
<Button
className="foreman-edit-icon"
aria-label="edit_version"
variant="plain"
onClick={() => {
- onAdd({
- componentCvId: id, published: cvVersion, added: componentCvId, latest,
- });
- }}
+ onAdd({
+ componentCvId: id, published: cvVersion, added: componentCvId, latest,
+ });
+ }}
>
<PencilAltIcon />
</Button>
</SplitItem>}
</Split>,
},
{ title: environments ? <ComponentEnvironments {...{ environments }} /> : __('Not yet published') },
- { title: <Link to={urlBuilder(`labs/content_views/${id}#repositories`, '')}>{ repositories ? repositories.length : 0 }</Link> },
+ { title: <Link to={urlBuilder(`content_views/${id}#repositories`, '')}>{repositories ? repositories.length : 0}</Link> },
{
title: <AddedStatusLabel added={!!componentCvId} />,
},
{ title: <TableText wrapModifier="truncate">{description || __('No description')}</TableText> },
];
newRows.push({
- componentCvId: id, added: componentCvId, published: cvVersion, latest, cells,
+ componentCvId: id,
+ componentCvName: name,
+ added: componentCvId,
+ componentCvVersions,
+ published: cvVersion,
+ latest,
+ cells,
});
});
return newRows;
- }, [onAdd]);
+ }, [onAdd, results, permissions]);
const actionResolver = (rowData, { _rowIndex }) => [
{
title: __('Add'),
isDisabled: rowData.added,
@@ -173,25 +198,32 @@
onRemove(rowInfo.added);
},
},
];
+ const dropdownItems = [
+ <DropdownItem aria-label="bulk_add" key="bulk_add" isDisabled={!(bulkAddEnabled())} component="button" onClick={addBulk}>
+ {__('Add')}
+ </DropdownItem>,
+ <DropdownItem aria-label="bulk_remove" key="bulk_remove" isDisabled={!(bulkRemoveEnabled())} component="button" onClick={removeBulk}>
+ {__('Remove')}
+ </DropdownItem>,
+ ];
+
const emptyContentTitle = __(`No content views belong to ${label}`);
const emptyContentBody = __('Please add some content views.');
const emptySearchTitle = __('No matching content views found');
const emptySearchBody = __('Try changing your search settings.');
- const activeFilters = statusSelected && statusSelected !== ALL_STATUSES;
+ const activeFilters = [statusSelected];
+ const defaultFilters = [ALL_STATUSES];
useDeepCompareEffect(() => {
- const { results, ...meta } = response;
- setMetadata(meta);
-
if (!loading && results) {
const newRows = buildRows(results);
setRows(newRows);
}
- }, [response, buildRows, loading]);
+ }, [results, response, buildRows, loading]);
return (
<TableWrapper
{...{
rows,
@@ -203,60 +235,86 @@
searchQuery,
updateSearchQuery,
error,
status,
activeFilters,
- actionResolver,
+ defaultFilters,
}}
+ actionResolver={hasPermission(permissions, 'edit_content_views') ? actionResolver : null}
onSelect={onSelect(rows, setRows)}
cells={columnHeaders}
variant={TableVariant.compact}
autocompleteEndpoint="/content_views/auto_complete_search"
fetchItems={useCallback(params =>
getContentViewComponents(cvId, params, statusSelected), [cvId, statusSelected])}
additionalListeners={[statusSelected, addComponentsResolved, removeComponentsResolved]}
- >
- <Split hasGutter>
- <SplitItem>
- <SelectableDropdown
- items={[ADDED, NOT_ADDED, ALL_STATUSES]}
- title={__('Status')}
- selected={statusSelected}
- setSelected={setStatusSelected}
- placeholderText={__('Status')}
- />
- </SplitItem>
- <SplitItem>
- <Button onClick={removeBulk} isDisabled={!(bulkRemoveEnabled())} variant="secondary" aria-label="remove_components">
- {__('Remove content views')}
- </Button>
- </SplitItem>
- </Split>
- {versionEditing &&
- <ComponentContentViewAddModal
- cvId={compositeCvEditing}
- componentCvId={componentCvEditing}
- componentId={componentId}
- latest={componentLatest}
- show={versionEditing}
- setIsOpen={setVersionEditing}
- aria-label="copy_content_view_modal"
- />
+ actionButtons={
+ <>
+ <Split hasGutter>
+ <SplitItem>
+ <SelectableDropdown
+ items={[ADDED, NOT_ADDED, ALL_STATUSES]}
+ title={__('Status')}
+ selected={statusSelected}
+ setSelected={setStatusSelected}
+ placeholderText={__('Status')}
+ />
+ </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
+ cvId={compositeCvEditing}
+ componentCvId={componentCvEditing}
+ componentId={componentId}
+ latest={componentLatest}
+ show={versionEditing}
+ setIsOpen={setVersionEditing}
+ aria-label="edit_component_modal"
+ />}
+ {bulkAdding &&
+ <ComponentContentViewBulkAddModal
+ cvId={compositeCvEditing}
+ rowsToAdd={selectedComponentsToAdd}
+ onClose={() => setBulkAdding(false)}
+ aria-label="bulk_add_components_modal"
+ />}
+ </>
}
- </TableWrapper>
+ />
);
};
ContentViewComponents.propTypes = {
cvId: PropTypes.number.isRequired,
details: PropTypes.shape({
label: PropTypes.string,
+ permissions: PropTypes.shape({}),
}),
};
ContentViewComponents.defaultProps = {
details: {
label: '',
+ permissions: {},
},
};
export default ContentViewComponents;