webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js in katello-4.2.2 vs webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js in katello-4.3.0.rc1

- old
+ new

@@ -25,47 +25,49 @@ selectCVRepos, selectCVReposStatus, selectCVReposError, selectRepoTypes, selectRepoTypesStatus, - selectCVDetails, } from '../ContentViewDetailSelectors'; import { ADDED, NOT_ADDED, ALL_STATUSES } from '../../ContentViewsConstants'; import ContentCounts from './ContentCounts'; import LastSync from './LastSync'; import RepoIcon from './RepoIcon'; import AddedStatusLabel from '../../../../components/AddedStatusLabel'; import SelectableDropdown from '../../../../components/SelectableDropdown'; import { capitalize } from '../../../../utils/helpers'; +import { hasPermission } from '../../helpers'; const allRepositories = 'All repositories'; // Add any exceptions to the display names here // [API_value]: displayed_value const repoTypeNames = { docker: 'Container', ostree: 'OSTree', }; -const ContentViewRepositories = ({ cvId }) => { +const ContentViewRepositories = ({ cvId, details }) => { const dispatch = useDispatch(); const response = useSelector(state => selectCVRepos(state, cvId), shallowEqual); + const { results, ...metadata } = response; const status = useSelector(state => selectCVReposStatus(state, cvId), shallowEqual); const error = useSelector(state => selectCVReposError(state, cvId), shallowEqual); const repoTypesResponse = useSelector(state => selectRepoTypes(state), shallowEqual); const repoTypesStatus = useSelector(state => selectRepoTypesStatus(state), shallowEqual); - const details = useSelector(state => selectCVDetails(state, cvId), shallowEqual); + const { permissions } = details; const [rows, setRows] = useState([]); - const [metadata, setMetadata] = useState({}); + const deselectAll = () => setRows(rows.map(row => ({ ...row, selected: false }))); const [searchQuery, updateSearchQuery] = useState(''); const [typeSelected, setTypeSelected] = useState(allRepositories); const [statusSelected, setStatusSelected] = useState(ALL_STATUSES); // repoTypes object format: [displayed_value]: API_value const [repoTypes, setRepoTypes] = useState({}); const [bulkActionOpen, setBulkActionOpen] = useState(false); - const [bulkActionEnabled, setBulkActionEnabled] = useState(false); + const hasAddedSelected = rows.some(({ selected, added }) => selected && added); + const hasNotAddedSelected = rows.some(({ selected, added }) => selected && !added); const columnHeaders = [ { title: __('Type'), transforms: [fitContent] }, __('Name'), __('Product'), @@ -73,11 +75,11 @@ __('Content'), { title: __('Status') }, ]; const loading = status === STATUS.PENDING; - const buildRows = useCallback((results) => { + const buildRows = useCallback(() => { const newRows = []; results.forEach((repo) => { const { id, content_type: contentType, @@ -97,33 +99,30 @@ { title: <ContentCounts {...{ counts, productId }} repoId={id} /> }, { title: <AddedStatusLabel added={addedToCV || statusSelected === ADDED} />, }, ]; - newRows.push({ repoId: id, cells }); + newRows.push({ + repoId: id, + cells, + added: addedToCV || statusSelected === ADDED, + }); }); return newRows; - }, [statusSelected]); + }, [statusSelected, results]); useDeepCompareEffect(() => { - const { results, ...meta } = response; - setMetadata(meta); - if (!loading && results) { const newRows = buildRows(results); setRows(newRows); } - }, [response, loading, buildRows]); + }, [response, loading, buildRows, results]); useEffect(() => { dispatch(getRepositoryTypes()); }, []); // eslint-disable-line react-hooks/exhaustive-deps - useDeepCompareEffect(() => { - const rowsAreSelected = rows.some(row => row.selected); - setBulkActionEnabled(rowsAreSelected); - }, [rows]); // Get repo type filter selections dynamically from the API useDeepCompareEffect(() => { if (repoTypesStatus === STATUS.RESOLVED && repoTypesResponse) { const allRepoTypes = {}; @@ -154,37 +153,43 @@ dispatch(updateContentView(cvId, { repository_ids: deletedRepos })); }; const addBulk = () => { setBulkActionOpen(false); - const reposToAdd = []; - rows.forEach(row => row.selected && reposToAdd.push(row.repoId)); + const reposToAdd = rows.filter(({ selected, added }) => + selected && !added).map(({ repoId }) => repoId); + deselectAll(); onAdd(reposToAdd); }; const removeBulk = () => { setBulkActionOpen(false); - const reposToDelete = []; - rows.forEach(row => row.selected && reposToDelete.push(row.repoId)); + const reposToDelete = rows.filter(({ selected, added }) => + selected && added).map(({ repoId }) => repoId); + deselectAll(); onRemove(reposToDelete); }; - const actionResolver = (rowData, { _rowIndex }) => { - if (rowData.parent || rowData.compoundParent || rowData.noactions) return null; - const { repository_ids: repositoryIds } = details; + const actionResolver = ({ + parent, + compoundParent, + noactions, + added, + }) => { + if (parent || compoundParent || noactions) return null; return [ { title: 'Add', - isDisabled: repositoryIds && repositoryIds.includes(rowData.repoId), - onClick: (_event, rowId, rowInfo) => { + isDisabled: added, + onClick: (_event, _rowId, rowInfo) => { onAdd(rowInfo.repoId); }, }, { title: 'Remove', - isDisabled: repositoryIds && !repositoryIds.includes(rowData.repoId), - onClick: (_event, rowId, rowInfo) => { + isDisabled: !added, + onClick: (_event, _rowId, rowInfo) => { onRemove(rowInfo.repoId); }, }, ]; }; @@ -198,17 +203,18 @@ const emptyContentTitle = __("You currently don't have any repositories to add to this content view."); const emptyContentBody = __('Please add some repositories.'); // needs link const emptySearchTitle = __('No matching repositories found'); const emptySearchBody = __('Try changing your search settings.'); - const activeFilters = (typeSelected && typeSelected !== allRepositories) || - (statusSelected && statusSelected !== ALL_STATUSES); + const activeFilters = [typeSelected, statusSelected]; + const defaultFilters = [allRepositories, ALL_STATUSES]; + const dropdownItems = [ - <DropdownItem aria-label="bulk_add" key="bulk_add" isDisabled={!bulkActionEnabled} component="button" onClick={addBulk}> + <DropdownItem aria-label="bulk_add" key="bulk_add" isDisabled={!hasNotAddedSelected} component="button" onClick={addBulk}> {__('Add')} </DropdownItem>, - <DropdownItem aria-label="bulk_remove" key="bulk_remove" isDisabled={!bulkActionEnabled} component="button" onClick={removeBulk}> + <DropdownItem aria-label="bulk_remove" key="bulk_remove" isDisabled={!hasAddedSelected} component="button" onClick={removeBulk}> {__('Remove')} </DropdownItem>, ]; return ( @@ -220,65 +226,73 @@ emptyContentBody, emptySearchTitle, emptySearchBody, searchQuery, updateSearchQuery, - actionResolver, error, status, activeFilters, + defaultFilters, }} - onSelect={onSelect(rows, setRows)} + actionResolver={hasPermission(permissions, 'edit_content_views') ? actionResolver : null} + onSelect={hasPermission(permissions, 'edit_content_views') ? onSelect(rows, setRows) : null} cells={columnHeaders} variant={TableVariant.compact} autocompleteEndpoint="/repositories/auto_complete_search" fetchItems={useCallback(params => getCVReposWithOptions(params), [getCVReposWithOptions])} additionalListeners={[typeSelected, statusSelected]} - > - <Split hasGutter> - <SplitItem> - <SelectableDropdown - items={Object.keys(repoTypes)} - title="Type" - selected={typeSelected} - setSelected={setTypeSelected} - placeholderText="Type" - loading={repoTypesStatus === STATUS.PENDING} - error={repoTypesStatus === STATUS.ERROR} - /> - </SplitItem> - <SplitItem> - <SelectableDropdown - items={[ADDED, NOT_ADDED, ALL_STATUSES]} - title="Status" - selected={statusSelected} - setSelected={setStatusSelected} - placeholderText="Status" - /> - </SplitItem> - <SplitItem> - <ActionList> - <ActionListItem> - <Button onClick={addBulk} isDisabled={!bulkActionEnabled} variant="secondary" aria-label="add_repositories"> - Add repositories - </Button> - </ActionListItem> - <ActionListItem> - <Dropdown - toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />} - isOpen={bulkActionOpen} - isPlain - dropdownItems={dropdownItems} - /> - </ActionListItem> - </ActionList> - </SplitItem> - </Split> - </TableWrapper> + actionButtons={ + <Split hasGutter> + <SplitItem> + <SelectableDropdown + items={Object.keys(repoTypes)} + title={__('Type')} + selected={typeSelected} + setSelected={setTypeSelected} + placeholderText={__('Type')} + loading={repoTypesStatus === STATUS.PENDING} + error={repoTypesStatus === STATUS.ERROR} + /> + </SplitItem> + <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={!hasNotAddedSelected} variant="secondary" aria-label="add_repositories"> + {__('Add repositories')} + </Button> + </ActionListItem> + <ActionListItem> + <Dropdown + toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />} + isOpen={bulkActionOpen} + isPlain + dropdownItems={dropdownItems} + /> + </ActionListItem> + </ActionList> + </SplitItem> + } + </Split> + } + /> ); }; ContentViewRepositories.propTypes = { cvId: PropTypes.number.isRequired, + details: PropTypes.shape({ + repository_ids: PropTypes.arrayOf(PropTypes.number), + permissions: PropTypes.shape({}), + }).isRequired, }; export default ContentViewRepositories;