import React, { useCallback, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Button, Split, SplitItem, ActionList, ActionListItem, Dropdown, DropdownItem, KebabToggle, Skeleton, Tooltip, ToggleGroup, ToggleGroupItem, } from '@patternfly/react-core'; import { TimesIcon, CheckIcon } from '@patternfly/react-icons'; import { TableVariant, TableText, Thead, Tbody, Tr, Th, Td, ExpandableRowContent, } from '@patternfly/react-table'; import { translate as __ } from 'foremanReact/common/I18n'; import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors'; import IsoDate from 'foremanReact/components/common/dates/IsoDate'; import { urlBuilder } from 'foremanReact/common/urlHelpers'; import { propsToCamelCase } from 'foremanReact/common/helpers'; import SelectableDropdown from '../../../../SelectableDropdown'; import { useSet, useBulkSelect, useUrlParams } from '../../../../../components/Table/TableHooks'; import TableWrapper from '../../../../../components/Table/TableWrapper'; import { ErrataType, ErrataSeverity } from '../../../../../components/Errata'; import { getInstallableErrata, regenerateApplicability, applyViaKatelloAgent } from './HostErrataActions'; import ErratumExpansionDetail from './ErratumExpansionDetail'; import ErratumExpansionContents from './ErratumExpansionContents'; import { selectHostErrataStatus } from './HostErrataSelectors'; import { HOST_ERRATA_KEY, ERRATA_TYPES, ERRATA_SEVERITIES, TYPES_TO_PARAM, SEVERITIES_TO_PARAM, PARAM_TO_FRIENDLY_NAME } from './HostErrataConstants'; import { installErrata } from '../RemoteExecutionActions'; import { errataInstallUrl } from '../customizedRexUrlHelpers'; import './ErrataTab.scss'; import hostIdNotReady from '../../HostDetailsActions'; import { defaultRemoteActionMethod, KATELLO_AGENT } from '../../hostDetailsHelpers'; export const ErrataTab = () => { const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS')); const { id: hostId, name: hostname, content_facet_attributes: contentFacetAttributes, } = hostDetails; const contentFacet = propsToCamelCase(contentFacetAttributes ?? {}); const dispatch = useDispatch(); const toggleGroupStates = ['all', 'installable']; const [ALL, INSTALLABLE] = toggleGroupStates; const ERRATA_TYPE = __('Type'); const ERRATA_SEVERITY = __('Severity'); const [isBulkActionOpen, setIsBulkActionOpen] = useState(false); const toggleBulkAction = () => setIsBulkActionOpen(prev => !prev); const expandedErrata = useSet([]); const erratumIsExpanded = id => expandedErrata.has(id); const { type: initialType, severity: initialSeverity, show, searchParam, } = useUrlParams(); const [toggleGroupState, setToggleGroupState] = useState(show ?? INSTALLABLE); const [errataTypeSelected, setErrataTypeSelected] = useState(PARAM_TO_FRIENDLY_NAME[initialType] ?? ERRATA_TYPE); const [errataSeveritySelected, setErrataSeveritySelected] = useState(PARAM_TO_FRIENDLY_NAME[initialSeverity] ?? ERRATA_SEVERITY); const activeFilters = [errataTypeSelected, errataSeveritySelected]; const defaultFilters = [ERRATA_TYPE, ERRATA_SEVERITY]; const emptyContentTitle = __('This host does not have any installable errata.'); const emptyContentBody = __('Installable errata will appear here when available.'); const emptySearchTitle = __('No matching errata found'); const emptySearchBody = __('Try changing your search settings.'); const columnHeaders = [ __('Errata'), __('Type'), __('Severity'), __('Installable'), __('Synopsis'), __('Published date'), ]; const fetchItems = useCallback( (params) => { if (!hostId) return hostIdNotReady; const modifiedParams = { ...params }; if (errataTypeSelected !== ERRATA_TYPE) { modifiedParams.type = TYPES_TO_PARAM[errataTypeSelected]; } if (errataSeveritySelected !== ERRATA_SEVERITY) { modifiedParams.severity = SEVERITIES_TO_PARAM[errataSeveritySelected]; } return getInstallableErrata( hostId, { include_applicable: toggleGroupState === ALL, ...modifiedParams, }, ); }, [hostId, toggleGroupState, ALL, ERRATA_SEVERITY, ERRATA_TYPE, errataTypeSelected, errataSeveritySelected], ); const response = useSelector(state => selectAPIResponse(state, HOST_ERRATA_KEY)); const { results, ...metadata } = response; const status = useSelector(state => selectHostErrataStatus(state)); const errataSearchQuery = id => `errata_id = ${id}`; const { selectOne, isSelected, searchQuery, selectedCount, isSelectable, updateSearchQuery, selectNone, fetchBulkParams, ...selectAll } = useBulkSelect({ results, metadata, idColumn: 'errata_id', isSelectable: result => result.installable, initialSearchQuery: searchParam || '', }); if (!hostId) return ; const applyErratumViaRemoteExecution = id => dispatch(installErrata({ hostname, search: errataSearchQuery(id), })); const applyViaRemoteExecution = () => { dispatch(installErrata({ hostname, search: fetchBulkParams(), })); const params = { page: metadata.page, per_page: metadata.per_page, search: metadata.search }; dispatch(getInstallableErrata( hostId, { ...params, include_applicable: toggleGroupState === ALL }, )); }; const bulkCustomizedRexUrl = () => errataInstallUrl({ hostname, search: (selectedCount > 0) ? fetchBulkParams() : '', }); const recalculateErrata = () => { setIsBulkActionOpen(false); dispatch(regenerateApplicability(hostId)); }; const applyByKatelloAgent = () => { const selected = fetchBulkParams(); setIsBulkActionOpen(false); selectNone(); dispatch(applyViaKatelloAgent(hostId, { search: selected })); }; const applyErratumViaKatelloAgent = id => dispatch(applyViaKatelloAgent( hostId, { errata_ids: [id] }, )); const defaultRemoteAction = defaultRemoteActionMethod({ hostDetails }); const apply = () => { if (defaultRemoteAction === KATELLO_AGENT) { applyByKatelloAgent(); } else { applyViaRemoteExecution(); } }; const dropdownItems = [ {__('Recalculate')} , ]; if (defaultRemoteAction === KATELLO_AGENT) { dropdownItems.push(( {__('Apply via Katello agent')} )); } dropdownItems.push(( {__('Apply via remote execution')} )); dropdownItems.push(( {__('Apply via customized remote execution')} )); const handleErrataTypeSelected = newType => setErrataTypeSelected((prevType) => { if (prevType === newType) { return ERRATA_TYPE; } return newType; }); const handleErrataSeveritySelected = newSeverity => setErrataSeveritySelected((prevSeverity) => { if (prevSeverity === newSeverity) { return ERRATA_SEVERITY; } return newSeverity; }); const actionButtons = ( <> } isOpen={isBulkActionOpen} isPlain dropdownItems={dropdownItems} /> ); const hostIsNonLibrary = ( contentFacet?.contentViewDefault === false && contentFacet.lifecycleEnvironmentLibrary === false ); const toggleGroup = ( {hostIsNonLibrary && setToggleGroupState(ALL)} /> setToggleGroupState(INSTALLABLE)} /> } ); return (
{columnHeaders.map(col => {col})} <> {results?.map((erratum, rowIndex) => { const { id, errata_id: errataId, created_at: createdAt, updated: publishedAt, title, installable: isInstallable, } = erratum; const isExpanded = erratumIsExpanded(id); let rowActions; if (isInstallable) { rowActions = [ { title: __('Apply via remote execution'), onClick: () => applyErratumViaRemoteExecution(errataId), }, { title: __('Apply via customized remote execution'), component: 'a', href: errataInstallUrl({ hostname, search: errataSearchQuery(errataId) }), }, ]; if (contentFacet.katelloAgentInstalled && contentFacet.katelloAgentEnabled) { rowActions.unshift({ title: __('Apply via Katello agent'), onClick: () => applyErratumViaKatelloAgent(errataId), }); } } else { rowActions = [ { title: __('Apply Erratum'), component: 'a', href: urlBuilder(`errata/${id}/content-hosts`, ''), }, ]; } return ( expandedErrata.onToggle(isOpen, id), }} /> selectOne(selected, errataId), rowIndex, variant: 'checkbox', }} /> {errataId} {isInstallable ? {__('Yes')} : {__('No')} } {title} ); }) }
); }; export default ErrataTab;