From 729bef8642346bbb3e2749761f5949d182537351 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Fri, 20 Dec 2024 16:08:43 -0500 Subject: [PATCH] fix(RHINENG-13682): Update eligible filter to single select This PR updates the system eligibility filter in the select systems modal for a task from radio buttons to a single select dropdown. The functionality should remain the same. Go to the Available tab on the Tasks app and click the Select systems button on a task to load the system modal and select the eligibility filter. --- .../SystemTable/SelectCustomFilter.js | 74 +++++++++++++++++++ .../SystemTable/SystemTable.js | 39 ++++++---- .../__tests__/SelectCustomFilter.tests.js | 65 ++++++++++++++++ src/SmartComponents/SystemTable/constants.js | 10 ++- src/SmartComponents/SystemTable/helpers.js | 10 ++- 5 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 src/SmartComponents/SystemTable/SelectCustomFilter.js create mode 100644 src/SmartComponents/SystemTable/__tests__/SelectCustomFilter.tests.js diff --git a/src/SmartComponents/SystemTable/SelectCustomFilter.js b/src/SmartComponents/SystemTable/SelectCustomFilter.js new file mode 100644 index 00000000..65f90c92 --- /dev/null +++ b/src/SmartComponents/SystemTable/SelectCustomFilter.js @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import propTypes from 'prop-types'; +import { + MenuToggle, + Select, + SelectList, + SelectOption, +} from '@patternfly/react-core'; +import { findFilterData } from './helpers'; + +const SelectCustomFilter = ({ + filterId, + options, + selectedValue, + setFilterData, +}) => { + const [isOpen, setOpen] = useState(false); + + const handleSelectChange = (value) => { + setFilterData(findFilterData(value, options)); + }; + + const toggle = (toggleRef) => ( + setOpen(!isOpen)} + isExpanded={isOpen} + style={{ + width: 'auto', + }} + > + {options.find((item) => item.value === selectedValue.value)?.label} + + ); + + return ( +
+ +
+ ); +}; + +SelectCustomFilter.propTypes = { + filterId: propTypes.string, + options: propTypes.array, + setFilterData: propTypes.func, + selectedValue: propTypes.object, +}; + +export default SelectCustomFilter; diff --git a/src/SmartComponents/SystemTable/SystemTable.js b/src/SmartComponents/SystemTable/SystemTable.js index 68f6430b..89e87a61 100644 --- a/src/SmartComponents/SystemTable/SystemTable.js +++ b/src/SmartComponents/SystemTable/SystemTable.js @@ -2,8 +2,8 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import propTypes from 'prop-types'; import { - ELIGIBLE_SYSTEMS, - ALL_SYSTEMS, + ELIGIBLE_SYSTEMS_VALUE, + ALL_SYSTEMS_VALUE, API_MAX_SYSTEMS, eligibilityFilterItems, defaultOnLoad, @@ -21,6 +21,7 @@ import { conditionalFilterType } from '@redhat-cloud-services/frontend-component import usePromiseQueue from '../../Utilities/hooks/usePromiseQueue'; import { NoCentOsEmptyState } from './NoCentOsEmptyState'; import { CENTOS_CONVERSION_SLUGS } from '../AvailableTasks/QuickstartButton'; +import SelectCustomFilter from './SelectCustomFilter'; const SystemTable = ({ bulkSelectIds, @@ -32,7 +33,7 @@ const SystemTable = ({ }) => { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); - const [eligibility, setEligibility] = useState(ELIGIBLE_SYSTEMS); + const [eligibility, setEligibility] = useState(eligibilityFilterItems[0]); const inventory = useRef(null); const { getRegistry } = useContext(RegistryContext); const dispatch = useDispatch(); @@ -95,17 +96,23 @@ const SystemTable = ({ }; }); + const setEligibilityData = (value) => { + setEligibility(value); + setShowEligibilityAlert(value === ELIGIBLE_SYSTEMS_VALUE); + }; + const eligibilityFilter = { label: 'Task Eligibility', - type: conditionalFilterType.radio, + type: conditionalFilterType.custom, filterValues: { - onChange: (event, value) => { - setEligibility(value); - setShowEligibilityAlert(value === ELIGIBLE_SYSTEMS); - }, - items: eligibilityFilterItems, - value: eligibility, - placeholder: 'Filter Eligible Systems', + children: ( + + ), }, }; @@ -114,21 +121,21 @@ const SystemTable = ({ { id: 'Task eligibility', category: 'Task eligibility', - chips: [{ name: eligibility, value: eligibility }], + chips: [{ name: eligibility.label, value: eligibility.value }], }, ], onDelete: (event, itemsToRemove, isAll) => { if (isAll) { - setEligibility(ELIGIBLE_SYSTEMS); + setEligibility(eligibilityFilterItems[0]); setShowEligibilityAlert(true); } else { itemsToRemove.map((item) => { if (item.category === 'Task eligibility') { - if (eligibility === ALL_SYSTEMS) { - setEligibility(ELIGIBLE_SYSTEMS); + if (eligibility.value === ALL_SYSTEMS_VALUE) { + setEligibility(eligibilityFilterItems[0]); setShowEligibilityAlert(true); } else { - setEligibility(ALL_SYSTEMS); + setEligibility(eligibilityFilterItems[1]); setShowEligibilityAlert(false); } } diff --git a/src/SmartComponents/SystemTable/__tests__/SelectCustomFilter.tests.js b/src/SmartComponents/SystemTable/__tests__/SelectCustomFilter.tests.js new file mode 100644 index 00000000..d37847c3 --- /dev/null +++ b/src/SmartComponents/SystemTable/__tests__/SelectCustomFilter.tests.js @@ -0,0 +1,65 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import SelectCustomFilter from '../SelectCustomFilter'; +import { render, screen, waitFor, within } from '@testing-library/react'; + +const options = [ + { value: 'op1', label: 'option 1' }, + { value: 'op2', label: 'option 2' }, + { value: 'op3', label: 'option 3' }, +]; +const filterId = 'my-filter'; +const setFilterData = jest.fn(); + +describe('SelectCustomFilter component', () => { + it('Should handle select values', async () => { + const selectedValue = { value: 'op1', label: 'option 1' }; + + render( + + ); + + await waitFor(() => + userEvent.click( + screen.getByRole('button', { + name: /option 1/i, + }) + ) + ); + + const option1 = screen.getByRole('option', { + name: /option 1/i, + }); + const option2 = screen.getByRole('option', { + name: /option 2/i, + }); + const option3 = screen.getByRole('option', { + name: /option 3/i, + }); + + expect( + within(option1).getByRole('img', { + hidden: true, + }) + ).toBeTruthy(); + expect( + within(option2).queryByRole('img', { + hidden: true, + }) + ).toBeFalsy(); + expect( + within(option3).queryByRole('img', { + hidden: true, + }) + ).toBeFalsy(); + + await waitFor(() => userEvent.click(option2)); + + expect(setFilterData).toHaveBeenCalledWith(options[1]); + }); +}); diff --git a/src/SmartComponents/SystemTable/constants.js b/src/SmartComponents/SystemTable/constants.js index e8973515..d5cec166 100644 --- a/src/SmartComponents/SystemTable/constants.js +++ b/src/SmartComponents/SystemTable/constants.js @@ -56,11 +56,13 @@ export const defaultOnLoad = (columns, getRegistry) => { }); }; -export const ELIGIBLE_SYSTEMS = 'Eligible Systems'; -export const ALL_SYSTEMS = 'All Systems'; +const ELIGIBLE_SYSTEMS = 'Eligible Systems'; +export const ELIGIBLE_SYSTEMS_VALUE = 'eligible-systems'; +const ALL_SYSTEMS = 'All Systems'; +export const ALL_SYSTEMS_VALUE = 'all-systems'; export const eligibilityFilterItems = [ - { label: ELIGIBLE_SYSTEMS, value: ELIGIBLE_SYSTEMS }, - { label: ALL_SYSTEMS, value: ALL_SYSTEMS }, + { label: ELIGIBLE_SYSTEMS, value: ELIGIBLE_SYSTEMS_VALUE }, + { label: ALL_SYSTEMS, value: ALL_SYSTEMS_VALUE }, ]; // Max systems we can ask for from the API, otherwise the connected status diff --git a/src/SmartComponents/SystemTable/helpers.js b/src/SmartComponents/SystemTable/helpers.js index 89e211e6..a129d456 100644 --- a/src/SmartComponents/SystemTable/helpers.js +++ b/src/SmartComponents/SystemTable/helpers.js @@ -1,4 +1,4 @@ -import { ALL_SYSTEMS } from './constants'; +import { ALL_SYSTEMS_VALUE } from './constants'; const buildSortString = (orderBy, orderDirection) => { let sortString = orderBy ? '&sort=' : ''; @@ -87,7 +87,9 @@ const buildWorkloadFiltersString = (filters) => { }; const buildEligibilityFilterString = ({ filters }) => { - return filters[0]?.chips[0]?.value === ALL_SYSTEMS ? '&all_systems=true' : ''; + return filters[0]?.chips[0]?.value === ALL_SYSTEMS_VALUE + ? '&all_systems=true' + : ''; }; // TODO this should be based on a URLSearchParams object and use its toString() function @@ -120,3 +122,7 @@ export const findCheckedValue = (total, selected) => { return false; } }; + +export const findFilterData = (optionName, options) => { + return options.find((item) => item.label === optionName); +};