import React, { useState, useEffect, useCallback } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { lowerCase, upperFirst } from 'lodash';
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import {
Bullseye,
Split,
SplitItem,
Button,
ActionList,
ActionListItem,
Dropdown,
DropdownItem,
KebabToggle,
} from '@patternfly/react-core';
import { TableVariant, fitContent } from '@patternfly/react-table';
import { STATUS } from 'foremanReact/constants';
import { translate as __ } from 'foremanReact/common/I18n';
import { urlBuilder } from 'foremanReact/common/urlHelpers';
import PropTypes from 'prop-types';
import TableWrapper from '../../../../components/Table/TableWrapper';
import onSelect from '../../../../components/Table/helpers';
import { getContentViewRepositories, getRepositoryTypes, updateContentView } from '../ContentViewDetailActions';
import {
selectCVRepos,
selectCVReposStatus,
selectCVReposError,
selectRepoTypes,
selectRepoTypesStatus,
} 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 { 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, 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 { permissions } = details;
const [rows, setRows] = 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 hasAddedSelected = rows.some(({ selected, added }) => selected && added);
const hasNotAddedSelected = rows.some(({ selected, added }) => selected && !added);
const columnHeaders = [
{ title: __('Type'), transforms: [fitContent] },
__('Name'),
__('Product'),
__('Sync state'),
__('Content'),
{ title: __('Status') },
];
const loading = status === STATUS.PENDING;
const buildRows = useCallback(() => {
const newRows = [];
results.forEach((repo) => {
const {
id,
content_type: contentType,
name,
added_to_content_view: addedToCV,
product: { id: productId, name: productName },
content_counts: counts,
last_sync_words: lastSyncWords,
last_sync: lastSync,
} = repo;
const cells = [
{ title: },
{ title: {name} },
productName,
{ title: },
{ title: },
{
title: ,
},
];
newRows.push({
repoId: id,
cells,
added: addedToCV || statusSelected === ADDED,
});
});
return newRows;
}, [statusSelected, results]);
useDeepCompareEffect(() => {
if (!loading && results) {
const newRows = buildRows(results);
setRows(newRows);
}
}, [response, loading, buildRows, results]);
useEffect(() => {
dispatch(getRepositoryTypes());
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Get repo type filter selections dynamically from the API
useDeepCompareEffect(() => {
if (repoTypesStatus === STATUS.RESOLVED && repoTypesResponse) {
const allRepoTypes = {};
allRepoTypes[allRepositories] = 'all';
repoTypesResponse.forEach((type) => {
const { name } = type;
const typeFullName = name in repoTypeNames ?
repoTypeNames[name] : upperFirst(lowerCase(name));
allRepoTypes[`${typeFullName} Repositories`] = name;
});
setRepoTypes(allRepoTypes);
}
}, [repoTypesResponse, repoTypesStatus]);
const toggleBulkAction = () => {
setBulkActionOpen(!bulkActionOpen);
};
const onAdd = (repos) => {
const { repository_ids: repositoryIds = [] } = details;
dispatch(updateContentView(
cvId,
{ repository_ids: repositoryIds.concat(repos) },
() =>
dispatch(getContentViewRepositories(
cvId,
typeSelected !== 'All repositories' ? {
content_type: repoTypes[typeSelected],
} : {},
statusSelected,
)),
));
};
const onRemove = (repos) => {
const reposToDelete = [].concat(repos);
const { repository_ids: repositoryIds = [] } = details;
const deletedRepos = repositoryIds.filter(x => !reposToDelete.includes(x));
dispatch(updateContentView(
cvId, { repository_ids: deletedRepos },
() =>
dispatch(getContentViewRepositories(
cvId,
typeSelected !== 'All repositories' ? {
content_type: repoTypes[typeSelected],
} : {},
statusSelected,
)),
));
};
const addBulk = () => {
setBulkActionOpen(false);
const reposToAdd = rows.filter(({ selected, added }) =>
selected && !added).map(({ repoId }) => repoId);
deselectAll();
onAdd(reposToAdd);
};
const removeBulk = () => {
setBulkActionOpen(false);
const reposToDelete = rows.filter(({ selected, added }) =>
selected && added).map(({ repoId }) => repoId);
deselectAll();
onRemove(reposToDelete);
};
const actionResolver = ({
parent,
compoundParent,
noactions,
added,
}) => {
if (parent || compoundParent || noactions) return null;
return [
{
title: 'Add',
isDisabled: added,
onClick: (_event, _rowId, rowInfo) => {
onAdd(rowInfo.repoId);
},
},
{
title: 'Remove',
isDisabled: !added,
onClick: (_event, _rowId, rowInfo) => {
onRemove(rowInfo.repoId);
},
},
];
};
const getCVReposWithOptions = useCallback((params = {}) => {
const allParams = { ...params };
if (typeSelected !== 'All repositories') allParams.content_type = repoTypes[typeSelected];
return getContentViewRepositories(cvId, allParams, statusSelected);
}, [cvId, repoTypes, statusSelected, typeSelected]);
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, statusSelected];
const defaultFilters = [allRepositories, ALL_STATUSES];
const dropdownItems = [
{__('Remove')}
,
];
return (
getCVReposWithOptions(params), [getCVReposWithOptions])}
additionalListeners={[typeSelected, statusSelected]}
actionButtons={
{hasPermission(permissions, 'edit_content_views') &&
}
isOpen={bulkActionOpen}
isPlain
dropdownItems={dropdownItems}
/>
}
}
/>
);
};
ContentViewRepositories.propTypes = {
cvId: PropTypes.number.isRequired,
details: PropTypes.shape({
repository_ids: PropTypes.arrayOf(PropTypes.number),
permissions: PropTypes.shape({}),
}).isRequired,
};
export default ContentViewRepositories;