import React, { useState, useEffect, useCallback } from 'react'; import useDeepCompareEffect from 'use-deep-compare-effect'; import PropTypes from 'prop-types'; import { shallowEqual, useSelector, useDispatch } from 'react-redux'; import { capitalize, omit, isEqual } from 'lodash'; import { TableVariant } from '@patternfly/react-table'; import { Tabs, Tab, TabTitleText, Split, SplitItem, Select, SelectVariant, SelectOption, Button, Dropdown, DropdownItem, KebabToggle, Flex, FlexItem, Bullseye, DatePicker, ChipGroup, Chip, Text, } from '@patternfly/react-core'; import { STATUS } from 'foremanReact/constants'; import { translate as __ } from 'foremanReact/common/I18n'; import onSelect from '../../../../components/Table/helpers'; import TableWrapper from '../../../../components/Table/TableWrapper'; import { selectCVFilterErratumID, selectCVFilterErratumIDStatus, selectCVFilterErratumIDError, selectCVFilters, selectCVFilterDetails, selectCVFiltersStatus, } from '../ContentViewDetailSelectors'; import getContentViewDetails, { addCVFilterRule, removeCVFilterRule, getCVFilterErrata, deleteContentViewFilterRules, addContentViewFilterRules, } from '../ContentViewDetailActions'; import AddedStatusLabel from '../../../../components/AddedStatusLabel'; import ErratumTypeLabel from '../../../../components/ErratumTypeLabel'; import AffectedRepositoryTable from './AffectedRepositories/AffectedRepositoryTable'; import { ADDED, ALL_STATUSES, NOT_ADDED, ERRATA_TYPES } from '../../ContentViewsConstants'; import SelectableDropdown from '../../../../components/SelectableDropdown/SelectableDropdown'; import { dateFormat, dateParse } from './CVErrataDateFilterContent'; import { hasPermission } from '../../helpers'; const CVErrataIDFilterContent = ({ cvId, filterId, showAffectedRepos, setShowAffectedRepos, details, }) => { const dispatch = useDispatch(); const { results: filterResults } = useSelector(state => selectCVFilters(state, cvId), shallowEqual); const response = useSelector(state => selectCVFilterErratumID(state, cvId, filterId), shallowEqual); const status = useSelector(state => selectCVFilterErratumIDStatus(state, cvId, filterId), shallowEqual); const filterLoad = useSelector(state => selectCVFiltersStatus(state, cvId), shallowEqual); const error = useSelector(state => selectCVFilterErratumIDError(state, cvId, filterId), shallowEqual); const filterDetails = useSelector(state => selectCVFilterDetails(state, cvId, filterId), shallowEqual); const { repositories = [] } = filterDetails; const [rows, setRows] = useState([]); const [searchQuery, updateSearchQuery] = useState(''); const [activeTabKey, setActiveTabKey] = useState(0); const filterLoaded = filterLoad === 'RESOLVED'; const loading = status === STATUS.PENDING; const [bulkActionOpen, setBulkActionOpen] = useState(false); const toggleBulkAction = () => setBulkActionOpen(prevState => !prevState); const hasAddedSelected = rows.some(({ selected, added }) => selected && added); const hasNotAddedSelected = rows.some(({ selected, added }) => selected && !added); const [statusSelected, setStatusSelected] = useState(ALL_STATUSES); const [typeSelectOpen, setTypeSelectOpen] = useState(false); const [selectedTypes, setSelectedTypes] = useState(ERRATA_TYPES); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const activeFilters = [statusSelected, selectedTypes, startDate, endDate]; const defaultFilters = [ALL_STATUSES, ERRATA_TYPES, '', '']; const [apiStartDate, setApiStartDate] = useState(''); const [apiEndDate, setApiEndDate] = useState(''); const [dateType, setDateType] = useState('issued'); const [dateTypeSelectOpen, setDateTypeSelectOpen] = useState(false); const [startEntry, setStartEntry] = useState(false); const [endEntry, setEndEntry] = useState(false); const metadata = omit(response, ['results']); const { permissions } = details; const columnHeaders = [ __('Errata ID'), __('Type'), __('Issued'), __('Updated'), __('Severity'), __('Synopsis'), __('Status'), ]; const buildRows = useCallback((results) => { const newRows = []; const filterRules = filterResults.find(({ id }) => id === Number(filterId))?.rules || []; results.forEach((errata) => { const { id, errata_id: errataId, type, issued, updated, severity, title, filter_ids: filterIds, ...rest } = errata; const added = filterIds.includes(parseInt(filterId, 10)); const cells = [ { title: errataId }, { title: }, { title: issued }, { title: updated }, { title: severity || 'N/A' }, { title }, { title: }, ]; newRows.push({ cells, erratumId: errataId, erratumRuleId: filterRules?.find(({ errata_id: filterErrataId }) => filterErrataId === errataId)?.id, added, ...rest, errataId, }); }); return newRows.sort(({ added: addedA }, { added: addedB }) => { if (addedA === addedB) return 0; return addedA ? -1 : 1; }); }, [filterResults, filterId]); const bulkAdd = () => { setBulkActionOpen(false); const addData = rows.filter(({ selected, added }) => selected && !added).map(({ erratumId }) => ({ errata_ids: [erratumId] })); // eslint-disable-line max-len dispatch(addContentViewFilterRules(filterId, addData, () => dispatch(getContentViewDetails(cvId)))); }; const bulkRemove = () => { setBulkActionOpen(false); const erratumRuleIds = rows.filter(({ selected, added }) => selected && added).map(({ erratumRuleId }) => erratumRuleId); dispatch(deleteContentViewFilterRules(filterId, erratumRuleIds, () => dispatch(getContentViewDetails(cvId)))); }; useEffect(() => { if (!repositories.length && showAffectedRepos) { setActiveTabKey(1); } else { setActiveTabKey(0); } }, [showAffectedRepos, repositories.length]); useDeepCompareEffect(() => { const { results } = response; if (!loading && results && filterLoaded) { const newRows = buildRows(results); setRows(newRows); } }, [response, loading, filterLoaded, buildRows]); const actionResolver = ({ added }) => [ { title: __('Add'), isDisabled: added, onClick: (_event, _rowId, { erratumId }) => { dispatch(addCVFilterRule(filterId, { errata_ids: [erratumId] }, () => dispatch(getContentViewDetails(cvId)))); }, }, { title: __('Remove'), isDisabled: !added, onClick: (_event, _rowId, { erratumRuleId }) => { dispatch(removeCVFilterRule(filterId, erratumRuleId, () => dispatch(getContentViewDetails(cvId)))); }, }, ]; const validAPIDate = (date) => { if (!date || date === '') return true; const split = date.split('/'); if (split.length !== 3) { return false; } const [month, day, year] = split; return month && month.length === 2 && day && day.length === 2 && year && year.length === 4; }; const singleSelection = selection => (selectedTypes.length === 1 && selectedTypes.includes(selection)); const onTypeSelect = (selection) => { if (selectedTypes.includes(selection)) { if (selectedTypes.length === 1) return; setSelectedTypes(selectedTypes.filter(e => e !== selection)); } else setSelectedTypes([...selectedTypes, selection]); setTypeSelectOpen(false); }; const setValidStartDate = (e, value) => { setStartDate(value); if (validAPIDate(value)) setApiStartDate(value); }; const setValidEndDate = (e, value) => { setEndDate(value); if (validAPIDate(value)) setApiEndDate(value); }; const getCVFilterErrataWithOptions = useCallback((params = {}) => { let apiParams = { ...params, types: selectedTypes }; if (dateType) apiParams = { ...apiParams, date_type: dateType }; if (apiStartDate) apiParams = { ...apiParams, start_date: apiStartDate }; if (apiEndDate) apiParams = { ...apiParams, end_date: apiEndDate }; return getCVFilterErrata(cvId, filterId, apiParams, statusSelected); }, [cvId, filterId, statusSelected, selectedTypes, dateType, apiStartDate, apiEndDate]); const resetFilters = () => { setValidStartDate(''); setValidEndDate(''); setSelectedTypes(ERRATA_TYPES); setDateType('issued'); setStatusSelected(ALL_STATUSES); }; const resetFiltersDisabled = startDate === '' && endDate === '' && isEqual(selectedTypes, ERRATA_TYPES) && dateType === 'issued' && statusSelected === ALL_STATUSES; const emptyContentTitle = __('No errata filter rules yet'); const emptyContentBody = __('No errata to add yet'); const emptySearchTitle = __('No matching filter rules found.'); const emptySearchBody = __('Try changing your search settings.'); const invalidDateFormat = __('Enter a valid date: MM/DD/YYYY'); return ( setActiveTabKey(eventKey)} > {__('Errata')}} >
getCVFilterErrataWithOptions(params), [getCVFilterErrataWithOptions])} actionButtons={hasPermission(permissions, 'edit_content_views') && status === STATUS.RESOLVED && rows.length !== 0 && {hasPermission(permissions, 'edit_content_views') && } {hasPermission(permissions, 'edit_content_views') && } isOpen={bulkActionOpen} ouiaId="cv-errata-id-bulk-action-dropdown" isPlain dropdownItems={[ {__('Remove')} ] } /> } } nodesBelowSearch={status === STATUS.RESOLVED && rows.length !== 0 && <> setStartEntry(true)} onBlur={() => setStartEntry(false)} > {__('to')} setEndEntry(true)} onBlur={() => setEndEntry(false)} > setStatusSelected(ALL_STATUSES)} isReadOnly={statusSelected === ALL_STATUSES}> {statusSelected} setValidStartDate('')} isReadOnly={startDate === ''}> {startDate || __('ANY')} {__('to')} setValidEndDate('')} isReadOnly={endDate === ''}> {endDate || __('ANY')} {selectedTypes.map(type => ( onTypeSelect(type)} isReadOnly={singleSelection(type)} > {capitalize(type)} ))} } />
{(repositories.length || showAffectedRepos) && {__('Affected repositories')}} >
}
); }; CVErrataIDFilterContent.propTypes = { cvId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filterId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, showAffectedRepos: PropTypes.bool.isRequired, setShowAffectedRepos: PropTypes.func.isRequired, details: PropTypes.shape({ permissions: PropTypes.shape({}), }).isRequired, }; export default CVErrataIDFilterContent;