diff --git a/src/Components/SmartComponents/SystemsPage/SystemTableToolbar.test.js b/src/Components/SmartComponents/SystemsPage/SystemTableToolbar.test.js deleted file mode 100644 index 277160006..000000000 --- a/src/Components/SmartComponents/SystemsPage/SystemTableToolbar.test.js +++ /dev/null @@ -1,182 +0,0 @@ -import SystemsTableToolbar from './SystemsTableToolbar'; -import toJson from 'enzyme-to-json'; -import { mountWithIntl } from '../../../Helpers/MiscHelper'; -import { BrowserRouter as Router } from 'react-router-dom'; -import configureStore from 'redux-mock-store'; -import { Provider } from 'react-redux'; -import { intl } from '../../../Utilities/IntlProvider'; -import useOsVersionFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/OsVersionFilter'; -import { systems } from '../../../Helpers/fixtures'; - -const mockStore = configureStore([store => next => action => { }]); -let store = mockStore({}); - -const applyMock = jest.fn(); -const handleSelectMock = jest.fn(); -const doOptOutMock = jest.fn(); - -jest.mock('../../PresentationalComponents/Filters/PrimaryToolbarFilters/OsVersionFilter') - -let wrapper; - -beforeEach(() => { - wrapper = mountWithIntl( - - - - - - ); -}); - -describe('SystemsTableToolbar component', () => { - it("Renders toolbar", () => { - expect( - wrapper.find({ - ouiaId: "PrimaryToolbar", - }) - ).toHaveLength(1); - }); - - it('Should match snapshot', () => { - expect(toJson(wrapper.find('Toolbar'))).toMatchSnapshot(); - }); - - it('Select all systems', () => { - const selectAllToggle = wrapper.find('DropdownToggle button').first(); - selectAllToggle.simulate('click'); - - wrapper.find('Dropdown').first().find("button").at(2).simulate('click'); - const bulkSelectedSystems = systems.data.map((system) => { - return { - display_name: system.display_name, - id: system.id, - opt_out: system.attributes.opt_out, - culled_timestamp: system.culled_timestamp, - cve_count: system.cve_count, - insights_id: system.insights_id, - last_evaluation: system.last_evaluation, - last_upload: system.last_upload, - os: system.os, - rules_evaluation: system.rules_evaluation, - stale_timestamp: system.stale_timestamp, - stale_warning_timestamp: system.stale_warning_timestamp, - tags: system.tags, - type: system.type, - updated: system.updated, - remediation: system.remediation, - selected: true - }; - }); - - expect(handleSelectMock).toHaveBeenCalledWith(bulkSelectedSystems, true); - - }); - - it('Should exclude the selected system', () => { - wrapper = mountWithIntl( - - - - - - ); - - const actionsToggle = wrapper.find('KebabToggle button'); - actionsToggle.simulate('click'); - - const exclude = wrapper.find('Dropdown').at(3).find("DropdownItem button").at(0) - const include = wrapper.find('Dropdown').at(3).find("DropdownItem button").at(1); - - exclude.simulate('click'); - - expect(include.props('disabled')).toBeTruthy(); - expect(exclude.text()).toBe('Disable Vulnerability analysis on system'); - expect(doOptOutMock).toHaveBeenCalled(); - }) - - it('Should include the excluded selected system', () => { - wrapper = mountWithIntl( - - - - - - ); - - const actionsToggle = wrapper.find('KebabToggle button'); - actionsToggle.simulate('click'); - - const exclude = wrapper.find('Dropdown').at(3).find("DropdownItem button").at(0) - const include = wrapper.find('Dropdown').at(3).find("DropdownItem button").at(1); - - include.simulate('click'); - - expect(exclude.props('disabled')).toBeTruthy(); - expect(include.text()).toBe('Enable Vulnerability analysis on system'); - expect(doOptOutMock).toHaveBeenCalled(); - }) - - it("The default filter is set by name", () => { - const container = wrapper.find('button[data-ouia-component-id="ConditionalFilter"]'); - expect(container).toHaveLength(1); - expect(container.text()).toBe('Name'); - }) -}); diff --git a/src/Components/SmartComponents/SystemsPage/SystemsPage.js b/src/Components/SmartComponents/SystemsPage/SystemsPage.js index 928de4abb..8903f6047 100644 --- a/src/Components/SmartComponents/SystemsPage/SystemsPage.js +++ b/src/Components/SmartComponents/SystemsPage/SystemsPage.js @@ -1,9 +1,8 @@ -import React, { useEffect, useState, Fragment } from 'react'; +import React, { useEffect, useState, Fragment, useMemo } from 'react'; import { useIntl } from 'react-intl'; import messages from '../../../Messages'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; -import SystemsTableToolbar from './SystemsTableToolbar'; -import { PERMISSIONS, SYSTEMS_ALLOWED_PARAMS } from '../../../Helpers/constants'; +import { PERMISSIONS, SYSTEMS_ALLOWED_PARAMS, SYSTEMS_DEFAULT_FILTERS, SYSTEMS_FILTER_PARAMS } from '../../../Helpers/constants'; import ReducerRegistry from '../../../Utilities/ReducerRegistry'; import { Main } from '@redhat-cloud-services/frontend-components/Main'; import { systemTableRowActions } from '../../../Helpers/CVEHelper'; @@ -14,36 +13,45 @@ import { clearSystemStore, clearInventoryStore, selectRows, - changeColumnsSystemList + changeColumnsSystemList, + fetchSystems, + fetchSystemsIds } from '../../../Store/Actions/Actions'; import { useUrlParams } from '../../../Helpers/MiscHelper'; import { InventoryTable } from '@redhat-cloud-services/frontend-components/Inventory'; import ErrorHandler from '../../PresentationalComponents/ErrorHandler/ErrorHandler'; import { TableVariant } from '@patternfly/react-table'; -import { useColumnManagement, useGetEntities, useOptOutSystems, useRbac } from '../../../Helpers/Hooks'; +import { useBulkSelect, useColumnManagement, useGetEntities, useOptOutSystems, useRbac } from '../../../Helpers/Hooks'; import * as APIHelper from '../../../Helpers/APIHelper'; import { EmptyStateNoSystems } from '../../PresentationalComponents/EmptyStates/EmptyStates'; -import { clearNotifications } from '@redhat-cloud-services/frontend-components-notifications/redux'; +import { addNotification, clearNotifications } from '@redhat-cloud-services/frontend-components-notifications/redux'; import NoAccessPage from '../../PresentationalComponents/StaticPages/NoAccessPage'; import Spinner from '@redhat-cloud-services/frontend-components/Spinner'; +import DownloadSystemsReport from '../Reports/DownloadSystemsReport'; +import { kebabItemDownloadPDF } from '../../PresentationalComponents/Kebab/KebabItems'; +import { buildActiveFilters, exportConfig, isFilterInDefaultState, removeFilters } from '../../../Helpers/TableToolbarHelper'; +import DownloadReport from '../../../Helpers/DownloadReport'; +import useOsVersionFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/OsVersionFilter'; +import useSearchFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/SearchFilter'; +import excludedFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/ExcludedFilter'; const SystemsPage = () => { - const [[canReadVulnerabilityResults, + const [[ + canReadVulnerabilityResults, canSetExcludedIncluded, canExport, - canReadExcluded], - isLoading] = useRbac([ + canReadExcluded + ], isLoading + ] = useRbac([ PERMISSIONS.readVulnerabilityResults, PERMISSIONS.setExcludedIncluded, PERMISSIONS.basicReporting, PERMISSIONS.readExcluded ]); - const [[ - canReadHostsInventory], - isLoadingInventory] = useRbac([ - PERMISSIONS.readHosts - ], 'inventory'); + const [[canReadHostsInventory], isLoadingInventory] = useRbac([PERMISSIONS.readHosts], 'inventory'); + + const [exportPDF, setExportPDF] = useState(false); const inventoryRef = React.createRef(); const dispatch = useDispatch(); @@ -90,12 +98,10 @@ const SystemsPage = () => { const handleSelect = (payload, selecting) => dispatch(selectRows(payload, selecting)); - const onRefreshInventory = () => { - dispatch(clearInventoryStore()); - // timestamp is used to force inventory to refresh - // if it wasn't there inventory might ignore request to refresh because parameters are the same - inventoryRef.current.onRefreshData(({ timestamp: Date.now(), page: 1 })); - }; + const onRefreshInventory = () => ( + dispatch(clearInventoryStore()), + inventoryRef.current.onRefreshData(({ page: 1 })) + ); const doOptOut = useOptOutSystems(onRefreshInventory); const getEntities = useGetEntities(APIHelper.getSystems, {}); @@ -103,17 +109,82 @@ const SystemsPage = () => { const [columnCounter, setColumnCount] = useState(0); useEffect(() => setColumnCount(columnCounter + 1), [columns]); - // TODO: let InventoryTable render its own toolbar instead of using custom one + const downloadReport = format => { + let params = { ...parameters }; + DownloadReport.exec( + fetchSystems, + params, + format, + 'system-list', + notification => dispatch( + addNotification(notification) + ), + () => dispatch(clearNotifications()) + ); + }; + + const kebabProps = useMemo(() => { + return { + selectedExcluded: selectedRows.some(({ opt_out: optOut }) => optOut === true), + selectedIncluded: selectedRows.some(({ opt_out: optOut }) => optOut === false) + }; + }, [selectedRows]); + + const kebabOptions = [ + '', + ...canSetExcludedIncluded ? [{ + label: intl.formatMessage(messages.systemKebabDisableAnalysis, { count: selectedRowsCount }), + onClick: () => doOptOut(selectedRows, selectedRows?.[0].display_name, true), + props: { isDisabled: !selectedRowsCount || !kebabProps.selectedIncluded } + }, + { + label: intl.formatMessage(messages.systemKebabEnableAnalysis, { count: selectedRowsCount }), + onClick: () => doOptOut(selectedRows, selectedRows?.[0].display_name, false, selectedRows), + props: { isDisabled: !selectedRowsCount || !kebabProps.selectedExcluded } + }] : [], + { + label: intl.formatMessage(messages.columnManagementModalTitle), + onClick: () => setColumnManagementModalOpen(true) + } + ]; + + const rawData = { data: items, meta: { totalItems }, isLoaded }; + + const bulkSelectProps = useBulkSelect({ + rawData, + selectedRows, + selectedRowsCount, + handleSelect, + fetchResource: ops => fetchSystemsIds({ ...parameters, ...ops }) + }); + + const osVersionFilter = useOsVersionFilter( + parameters.rhel_version, + apply + ); + + const filterConfigItems = [ + useSearchFilter( + 'filter', + messages.systemsSearchName, + messages.searchFilterByName, + parameters.filter, + apply + ), + ...(canReadExcluded ? [excludedFilter(apply, parameters)] : []), + ...osVersionFilter + ]; + return ( - isLoading ? : + isLoading ? : canReadVulnerabilityResults ? - { ColumnManagementModal } + {ColumnManagementModal} -
+
- {isLoadingInventory ? : + {isLoadingInventory ? : hasError && !canReadHostsInventory - ? + ? : ( { columns={defaultColumns => mergeColumns(defaultColumns)} getEntities={getEntities} hideFilters={{ all: true }} - noSystemsTable={} + showTags + noSystemsTable={} + exportConfig={canExport ? { + isDisabled: items?.meta?.totalItems === 0, + extraItems: [kebabItemDownloadPDF(exportPDF, setExportPDF)], + ouiaId: 'export', + ...exportConfig({ downloadReport }) + } : null} + bulkSelect={bulkSelectProps} + actionsConfig={{ + actions: kebabOptions, + dropdownProps: { ouiaId: 'toolbar-actions' } + }} + filterConfig={{ + items: filterConfigItems + }} + activeFiltersConfig={{ + filters: buildActiveFilters(parameters), + onDelete: (_, chips, reset) => + removeFilters(chips, apply, reset, SYSTEMS_DEFAULT_FILTERS), + deleteTitle: intl.formatMessage(messages.resetFilters), + showDeleteButton: !isFilterInDefaultState( + parameters, + canReadExcluded ? SYSTEMS_DEFAULT_FILTERS : {}, + SYSTEMS_FILTER_PARAMS) + }} > - + {exportPDF && + setExportPDF(false)} + /> + } )}
- : + : ); }; diff --git a/src/Components/SmartComponents/SystemsPage/SystemsTableToolbar.js b/src/Components/SmartComponents/SystemsPage/SystemsTableToolbar.js deleted file mode 100644 index 025d3f064..000000000 --- a/src/Components/SmartComponents/SystemsPage/SystemsTableToolbar.js +++ /dev/null @@ -1,160 +0,0 @@ -import React, { Fragment, useState, useMemo } from 'react'; -import propTypes from 'prop-types'; -import { injectIntl } from 'react-intl'; -import messages from '../../../Messages'; -import { useDispatch } from 'react-redux'; -import { dataShape } from '../../../Helpers/MiscHelper'; -import { fetchSystems, fetchSystemsIds } from '../../../Store/Actions/Actions'; -import { PrimaryToolbar } from '@redhat-cloud-services/frontend-components/PrimaryToolbar'; -import useSearchFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/SearchFilter'; -import { exportConfig, buildActiveFilters, removeFilters, isFilterInDefaultState } from '../../../Helpers/TableToolbarHelper'; -import DownloadReport from '../../../Helpers/DownloadReport'; -import DownloadSystemsReport from '../Reports/DownloadSystemsReport'; -import { kebabItemDownloadPDF } from '../../PresentationalComponents/Kebab/KebabItems'; -import excludedFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/ExcludedFilter'; -import { SYSTEMS_DEFAULT_FILTERS, SYSTEMS_FILTER_PARAMS } from '../../../Helpers/constants'; -import { useBulkSelect } from '../../../Helpers/Hooks'; -import { - addNotification, - clearNotifications -} from '@redhat-cloud-services/frontend-components-notifications/redux'; -import useOsVersionFilter from '../../PresentationalComponents/Filters/PrimaryToolbarFilters/OsVersionFilter'; - -const SystemsTableToolbar = ({ - selectedRows, - selectedRowsCount, - intl, - canExport, - canSetExcludedIncluded, - canReadExcluded, - parameters, - rawData, - methods -}) => { - const [exportPDF, setExportPDF] = useState(false); - const { apply, handleSelect, doOptOut, setColumnManagementModalOpen } = methods; - const dispatch = useDispatch(); - - const downloadReport = format => { - let params = { ...parameters }; - DownloadReport.exec( - fetchSystems, - params, - format, - 'system-list', - notification => dispatch( - addNotification(notification) - ), - () => dispatch(clearNotifications()) - ); - }; - - const kebabProps = useMemo(() => { - return { - selectedExcluded: selectedRows.some(({ opt_out: optOut }) => optOut === true), - selectedIncluded: selectedRows.some(({ opt_out: optOut }) => optOut === false) - }; - }, [selectedRows]); - - const kebabOptions = [ - '', - ...canSetExcludedIncluded ? [{ - label: intl.formatMessage(messages.systemKebabDisableAnalysis, { count: selectedRowsCount }), - onClick: () => doOptOut(selectedRows, selectedRows?.[0].display_name, true), - props: { isDisabled: !selectedRowsCount || !kebabProps.selectedIncluded } - }, - { - label: intl.formatMessage(messages.systemKebabEnableAnalysis, { count: selectedRowsCount }), - onClick: () => doOptOut(selectedRows, selectedRows?.[0].display_name, false, selectedRows), - props: { isDisabled: !selectedRowsCount || !kebabProps.selectedExcluded } - }] : [], - { - label: intl.formatMessage(messages.columnManagementModalTitle), - onClick: () => setColumnManagementModalOpen(true) - } - ]; - - const bulkSelectProps = useBulkSelect({ - rawData, - selectedRows, - selectedRowsCount, - handleSelect, - fetchResource: ops => fetchSystemsIds({ ...parameters, ...ops }) - }); - - const osVersionFilter = useOsVersionFilter( - parameters.rhel_version, - apply - ); - - const filterConfigItems = [ - useSearchFilter( - 'filter', - messages.systemsSearchName, - messages.searchFilterByName, - parameters.filter, - apply - ), - ...(canReadExcluded ? [excludedFilter(apply, parameters)] : []), - ...osVersionFilter - ]; - - return - removeFilters(chips, apply, reset, SYSTEMS_DEFAULT_FILTERS), - deleteTitle: intl.formatMessage(messages.resetFilters), - showDeleteButton: !isFilterInDefaultState( - parameters, - canReadExcluded ? SYSTEMS_DEFAULT_FILTERS : {}, - SYSTEMS_FILTER_PARAMS) - }} - exportConfig={canExport ? { - isDisabled: rawData.meta.totalItems === 0, - extraItems: [kebabItemDownloadPDF(exportPDF, setExportPDF)], - ouiaId: 'export', - ...exportConfig({ downloadReport }) - } : null} - /> - - {exportPDF && - setExportPDF(false)} - /> - } - - ; - -}; - -SystemsTableToolbar.propTypes = { - rawData: dataShape, - canExport: propTypes.bool, - canSetExcludedIncluded: propTypes.bool, - canReadExcluded: propTypes.bool, - parameters: propTypes.object, - selectedRows: propTypes.array, - selectedRowsCount: propTypes.number, - methods: propTypes.shape({ - doOptOut: propTypes.func, - apply: propTypes.func, - handleSelect: propTypes.func, - setColumnManagementModalOpen: propTypes.func - }), - intl: propTypes.any -}; - -export default injectIntl(SystemsTableToolbar);