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;