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;