webpack/components/extensions/HostDetails/Tabs/PackagesTab.js in katello-4.4.0.rc1 vs webpack/components/extensions/HostDetails/Tabs/PackagesTab.js in katello-4.4.0.rc2

- old
+ new

@@ -1,54 +1,72 @@ import React, { useCallback, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { - Button, - Hint, - HintBody, + ActionList, + ActionListItem, + Dropdown, DropdownItem, DropdownSeparator, - Dropdown, + DropdownToggle, + DropdownToggleAction, + KebabToggle, Skeleton, Split, SplitItem, - ActionList, - ActionListItem, - KebabToggle, } from '@patternfly/react-core'; import { TableVariant, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; import { translate as __ } from 'foremanReact/common/I18n'; import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors'; import { urlBuilder } from 'foremanReact/common/urlHelpers'; +import { useBulkSelect } from '../../../../components/Table/TableHooks'; import SelectableDropdown from '../../../SelectableDropdown'; import TableWrapper from '../../../../components/Table/TableWrapper'; -import { useUrlParams } from '../../../../components/Table/TableHooks'; import { PackagesStatus, PackagesLatestVersion } from '../../../../components/Packages'; -import { getInstalledPackagesWithLatest } from '../HostPackages/HostPackagesActions'; +import { + getInstalledPackagesWithLatest, + removePackageViaKatelloAgent, + upgradeAllViaKatelloAgent, + upgradePackageViaKatelloAgent, +} from '../HostPackages/HostPackagesActions'; import { selectHostPackagesStatus } from '../HostPackages/HostPackagesSelectors'; -import { HOST_PACKAGES_KEY, PACKAGES_VERSION_STATUSES, VERSION_STATUSES_TO_PARAM } from '../HostPackages/HostPackagesConstants'; +import { + HOST_PACKAGES_KEY, PACKAGES_VERSION_STATUSES, VERSION_STATUSES_TO_PARAM, +} from '../HostPackages/HostPackagesConstants'; +import { removePackage, updatePackage, removePackages, updatePackages } from './RemoteExecutionActions'; +import { katelloPackageUpdateUrl, packagesUpdateUrl } from './customizedRexUrlHelpers'; import './PackagesTab.scss'; import hostIdNotReady from '../HostDetailsActions'; import PackageInstallModal from './PackageInstallModal'; import defaultRemoteActionMethod, { KATELLO_AGENT } from '../hostDetailsHelpers'; export const PackagesTab = () => { const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS')); - const { id: hostId, name: hostName } = hostDetails; + const { + id: hostId, + name: hostname, + } = hostDetails; - const { searchParam } = useUrlParams(); - const [searchQuery, updateSearchQuery] = useState(searchParam || ''); + const dispatch = useDispatch(); const PACKAGE_STATUS = __('Status'); const [packageStatusSelected, setPackageStatusSelected] = useState(PACKAGE_STATUS); const activeFilters = [packageStatusSelected]; const defaultFilters = [PACKAGE_STATUS]; const [isBulkActionOpen, setIsBulkActionOpen] = useState(false); const toggleBulkAction = () => setIsBulkActionOpen(prev => !prev); const [isModalOpen, setIsModalOpen] = useState(false); const closeModal = () => setIsModalOpen(false); const showKatelloAgent = (defaultRemoteActionMethod({ hostDetails }) === KATELLO_AGENT); + const [isActionOpen, setIsActionOpen] = useState(false); + const onActionSelect = () => { + setIsActionOpen(false); + }; + const onActionToggle = () => { + setIsActionOpen(prev => !prev); + }; + const emptyContentTitle = __('This host does not have any packages.'); const emptyContentBody = __('Packages will appear here when available.'); const emptySearchTitle = __('No matching packages found'); const emptySearchBody = __('Try changing your search settings.'); const columnHeaders = [ @@ -71,33 +89,206 @@ ); const response = useSelector(state => selectAPIResponse(state, HOST_PACKAGES_KEY)); const { results, ...metadata } = response; const status = useSelector(state => selectHostPackagesStatus(state)); + const { + selectOne, + isSelected, + searchQuery, + selectedCount, + isSelectable, + selectedResults, + updateSearchQuery, + selectNone, + selectAllMode, + areAllRowsSelected, + fetchBulkParams, + ...selectAll + } = useBulkSelect({ + results, + metadata, + }); if (!hostId) return <Skeleton />; + const handleInstallPackagesClick = () => { setIsBulkActionOpen(false); setIsModalOpen(true); }; - const rowActions = [ - { - title: __('Upgrade via remote execution'), disabled: true, - }, - { - title: __('Upgrade via customized remote execution'), disabled: true, - }, + const removePackageViaRemoteExecution = packageName => dispatch(removePackage({ + hostname, + packageName, + })); + + const removeViaKatelloAgent = (packageName) => { + dispatch(removePackageViaKatelloAgent(hostId, { packages: [packageName] })); + selectNone(); + }; + + const removePackagesViaRemoteExecution = () => { + const selected = fetchBulkParams(); + setIsBulkActionOpen(false); + selectNone(); + dispatch(removePackages({ hostname, search: selected })); + }; + + const selectedPackageNames = () => selectedResults.map(({ name }) => name); + const selectedUpgradableVersions = () => selectedResults.map(({ upgradable_version: v }) => v); + + const removePackagesViaKatelloAgent = () => { + dispatch(removePackageViaKatelloAgent(hostId, { packages: selectedPackageNames() })); + selectNone(); + }; + + const defaultRemoteAction = defaultRemoteActionMethod({ hostDetails }); + + const removeBulk = () => { + if (defaultRemoteAction === KATELLO_AGENT) { + removePackagesViaKatelloAgent(); + } else { + removePackagesViaRemoteExecution(); + } + }; + + const handlePackageRemove = (packageName) => { + if (defaultRemoteAction === KATELLO_AGENT) { + removeViaKatelloAgent(packageName); + } else { + removePackageViaRemoteExecution(packageName); + } + }; + + const upgradeViaRemoteExecution = packageName => dispatch(updatePackage({ + hostname, + packageName, + })); + + const upgradeBulkViaRemoteExecution = () => { + const selected = fetchBulkParams(); + setIsBulkActionOpen(false); + selectNone(); + dispatch(updatePackages({ + hostname, + search: selected, + })); + }; + + const upgradeBulkViaKatelloAgent = () => { + if (areAllRowsSelected()) { + dispatch(upgradeAllViaKatelloAgent(hostId)); + } else { + dispatch(upgradePackageViaKatelloAgent(hostId, { packages: selectedUpgradableVersions() })); + } + selectNone(); + }; + + const upgradeBulk = () => { + if (defaultRemoteAction === KATELLO_AGENT) { + upgradeBulkViaKatelloAgent(); + } else { + upgradeBulkViaRemoteExecution(); + } + }; + + const upgradeViaCustomizedRemoteExecution = selectedCount ? + packagesUpdateUrl({ hostname, search: fetchBulkParams() }) : + '#'; + + const disableRemove = () => selectedCount === 0 || selectAllMode; + + const allUpgradable = () => selectedResults.length > 0 && + selectedResults.every(item => item.upgradable_version); + const disableUpgrade = () => selectedCount === 0 || + (selectAllMode && packageStatusSelected !== 'Upgradable') || + (defaultRemoteAction === KATELLO_AGENT && selectAllMode && !areAllRowsSelected()) || + (!selectAllMode && !allUpgradable()); + + const dropdownUpgradeItems = [ + <DropdownItem + aria-label="bulk_upgrade_rex" + key="bulk_upgrade_rex" + component="button" + onClick={upgradeBulkViaRemoteExecution} + > + {__('Upgrade via remote execution')} + </DropdownItem>, + <DropdownItem + aria-label="bulk_upgrade_customized_rex" + key="bulk_upgrade_customized_rex" + component="a" + href={upgradeViaCustomizedRemoteExecution} + > + {__('Upgrade via customized remote execution')} + </DropdownItem>, ]; + const dropdownRemoveItems = [ + <DropdownItem + aria-label="bulk_remove" + key="bulk_remove" + component="button" + onClick={removeBulk} + isDisabled={disableRemove()} + > + {__('Remove')} + </DropdownItem>, + <DropdownSeparator key="separator" />, + <DropdownItem + aria-label="install_pkg_on_host" + key="install_pkg_on_host" + component="button" + onClick={handleInstallPackagesClick} + > + {__('Install packages')} + </DropdownItem>, + ]; + const handlePackageStatusSelected = newStatus => setPackageStatusSelected((prevStatus) => { if (prevStatus === newStatus) { return PACKAGE_STATUS; } return newStatus; }); + const actionButtons = ( + <Split hasGutter> + <SplitItem> + <ActionList isIconList> + <ActionListItem> + <Dropdown + onSelect={onActionSelect} + toggle={ + <DropdownToggle + splitButtonItems={[ + <DropdownToggleAction key="action" aria-label="upgrade_actions" onClick={upgradeBulk}> + {__('Upgrade')} + </DropdownToggleAction>, + ]} + isDisabled={disableUpgrade()} + splitButtonVariant="action" + onToggle={onActionToggle} + /> + } + isOpen={isActionOpen} + dropdownItems={dropdownUpgradeItems} + /> + </ActionListItem> + <ActionListItem> + <Dropdown + toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />} + isOpen={isBulkActionOpen} + isPlain + dropdownItems={dropdownRemoveItems} + /> + </ActionListItem> + </ActionList> + </SplitItem> + </Split> + ); + const toggleGroup = ( <Split hasGutter> <SplitItem> <SelectableDropdown id="package-status-dropdown" @@ -109,65 +300,12 @@ /> </SplitItem> </Split> ); - const dropdownItems = [ - <DropdownItem - aria-label="remove_pkg_from_host" - key="remove_pkg_from_host" - component="button" - isDisabled - > - {__('Remove')} - </DropdownItem>, - <DropdownSeparator key="separator" />, - <DropdownItem - aria-label="install_pkg_on_host" - key="install_pkg_on_host" - component="button" - onClick={handleInstallPackagesClick} - > - {__('Install packages')} - </DropdownItem>, - ]; - - const actionButtons = ( - <> - <Split hasGutter> - <SplitItem> - <ActionList isIconList> - <ActionListItem> - <Button isDisabled> {__('Upgrade')} </Button> - </ActionListItem> - <ActionListItem> - <Dropdown - toggle={<KebabToggle aria-label="Packages bulk actions" onToggle={toggleBulkAction} />} - isOpen={isBulkActionOpen} - isPlain - dropdownItems={dropdownItems} - /> - </ActionListItem> - </ActionList> - </SplitItem> - </Split> - </> - ); - return ( <div> - <div id="packages-hint" className="margin-0-24 margin-top-16"> - <Hint> - <HintBody> - {__('Packages management functionality on this page is incomplete')}. - <br /> - <Button component="a" variant="link" isInline href={urlBuilder(`content_hosts/${hostId}/packages/installed`, '')}> - {__('Visit the previous Packages page')}. - </Button> - </HintBody> - </Hint> - </div> <div id="packages-tab"> <TableWrapper {...{ metadata, emptyContentTitle, @@ -179,44 +317,82 @@ defaultFilters, actionButtons, searchQuery, updateSearchQuery, toggleGroup, + selectedCount, + selectNone, + areAllRowsSelected, } } additionalListeners={[hostId, packageStatusSelected]} fetchItems={fetchItems} autocompleteEndpoint={`/hosts/${hostId}/packages/auto_complete_search`} foremanApiAutoComplete + rowsCount={results?.length} variant={TableVariant.compact} + {...selectAll} + displaySelectAllCheckbox > <Thead> <Tr> + <Th key="select-all" /> {columnHeaders.map(col => <Th key={col}>{col}</Th>)} <Th /> </Tr> </Thead> <Tbody> - {results?.map((packages) => { + {results?.map((pkg, rowIndex) => { const { id, name: packageName, nvra: installedVersion, rpm_id: rpmId, - } = packages; + upgradable_version: upgradableVersion, + } = pkg; + + const rowActions = [ + { + title: __('Remove'), + onClick: () => handlePackageRemove(packageName), + }, + ]; + + if (upgradableVersion) { + rowActions.unshift( + { + title: __('Upgrade via remote execution'), + onClick: () => upgradeViaRemoteExecution(upgradableVersion), + }, + { + title: __('Upgrade via customized remote execution'), + component: 'a', + href: katelloPackageUpdateUrl({ hostname, packageName: upgradableVersion }), + }, + ); + } + return ( <Tr key={`${id}`}> + <Td select={{ + disable: false, + isSelected: isSelected(id), + onSelect: (event, selected) => selectOne(selected, id, pkg), + rowIndex, + variant: 'checkbox', + }} + /> <Td> {rpmId ? <a href={urlBuilder(`packages/${rpmId}`, '')}>{packageName}</a> : packageName } </Td> - <Td><PackagesStatus {...packages} /></Td> + <Td><PackagesStatus {...pkg} /></Td> <Td>{installedVersion.replace(`${packageName}-`, '')}</Td> - <Td><PackagesLatestVersion {...packages} /></Td> + <Td><PackagesLatestVersion {...pkg} /></Td> <Td key={`rowActions-${id}`} actions={{ items: rowActions, }} @@ -231,10 +407,10 @@ {hostId && <PackageInstallModal isOpen={isModalOpen} closeModal={closeModal} hostId={hostId} - hostName={hostName} + hostName={hostname} showKatelloAgent={showKatelloAgent} /> } </div> );