From e7561234bcba77e5cf5a72d8ba82229eb305ce06 Mon Sep 17 00:00:00 2001 From: Amir Fefer Date: Tue, 8 Aug 2023 20:47:12 +0300 Subject: [PATCH] feat(HMS-2301): add query search for instance types select --- package-lock.json | 24 ++++++++++++ package.json | 1 + .../InstanceTypesSelect.test.js | 7 ++++ src/Components/InstanceTypesSelect/index.js | 38 +++++++++++++++---- .../steps/AccountCustomizations/aws.js | 14 +++++-- .../steps/AccountCustomizations/azure.js | 15 +++++++- .../steps/AccountCustomizations/gcp.js | 15 +++++++- src/Utils/querySearch.js | 25 ++++++++++++ src/mocks/fixtures/instanceTypes.fixtures.js | 8 ++-- 9 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 src/Utils/querySearch.js diff --git a/package-lock.json b/package-lock.json index 61f4b191..fed0462e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@unleash/proxy-client-react": "^3.5.2", "axios": "^0.27.2", "classnames": "^2.3.1", + "jsonata": "^2.0.3", "react": "17.0.2", "react-dom": "17.0.2", "react-query": "^3.39.2", @@ -3305,6 +3306,11 @@ "react-router-dom": "^5.0.0 || ^6.0.0" } }, + "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/@redhat-cloud-services/types": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", + "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" + }, "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -12340,6 +12346,14 @@ "node": ">=6" } }, + "node_modules/jsonata": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.0.3.tgz", + "integrity": "sha512-Up2H81MUtjqI/dWwWX7p4+bUMfMrQJVMN/jW6clFMTiYP528fBOBNtRu944QhKTs3+IsVWbgMeUTny5fw2VMUA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -21196,6 +21210,11 @@ "react-content-loader": "^6.2.0" }, "dependencies": { + "@redhat-cloud-services/types": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", + "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -28335,6 +28354,11 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonata": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.0.3.tgz", + "integrity": "sha512-Up2H81MUtjqI/dWwWX7p4+bUMfMrQJVMN/jW6clFMTiYP528fBOBNtRu944QhKTs3+IsVWbgMeUTny5fw2VMUA==" + }, "jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", diff --git a/package.json b/package.json index c780ab83..2e549e50 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@unleash/proxy-client-react": "^3.5.2", "axios": "^0.27.2", "classnames": "^2.3.1", + "jsonata": "^2.0.3", "react": "17.0.2", "react-dom": "17.0.2", "react-query": "^3.39.2", diff --git a/src/Components/InstanceTypesSelect/InstanceTypesSelect.test.js b/src/Components/InstanceTypesSelect/InstanceTypesSelect.test.js index 27e1ae01..10f9a351 100644 --- a/src/Components/InstanceTypesSelect/InstanceTypesSelect.test.js +++ b/src/Components/InstanceTypesSelect/InstanceTypesSelect.test.js @@ -49,6 +49,13 @@ describe('InstanceTypesSelect', () => { const items = await screen.findAllByLabelText(/^Instance Type/); expect(items).toHaveLength(1); }); + test('filter with a query', async () => { + const query = 'vcpus > 2 and cores > 2'; + const dropdown = await mountSelectAndClick(); + await userEvent.type(dropdown, query); + const items = await screen.findAllByLabelText(/^Instance Type/); + expect(items).toHaveLength(1); + }); }); }); diff --git a/src/Components/InstanceTypesSelect/index.js b/src/Components/InstanceTypesSelect/index.js index 2fd2ecb6..9ca23bd4 100644 --- a/src/Components/InstanceTypesSelect/index.js +++ b/src/Components/InstanceTypesSelect/index.js @@ -4,6 +4,8 @@ import { Spinner, Select, SelectOption, TextInput } from '@patternfly/react-core import { useQuery } from 'react-query'; import { fetchInstanceTypesList } from '../../API'; import { useWizardContext } from '../Common/WizardContext'; +import { evaluateQuery } from '../../Utils/querySearch'; +import _throttle from 'lodash/throttle'; const OPTIONS_PER_SCREEN = 3; const sanitizeSearchValue = (str) => str.replace(/\\+$/, ''); @@ -13,7 +15,6 @@ const InstanceTypesSelect = ({ setValidation, architecture }) => { const [isOpen, setIsOpen] = React.useState(false); const [numOptions, setNumOptions] = React.useState(OPTIONS_PER_SCREEN); const [filteredTypes, setFilteredTypes] = React.useState(null); - const [prevSearch, setPrevSearch] = React.useState(''); const [isTypeSupported, setTypeSupported] = React.useState(true); const { isLoading, @@ -49,6 +50,10 @@ const InstanceTypesSelect = ({ setValidation, architecture }) => { clearSelection(); } else { const chosenInstanceType = instanceTypes.find((instanceType) => selection === instanceType.name); + if (!chosenInstanceType) { + setIsOpen(false); + return; + } setTypeSupported(chosenInstanceType.supported); setWizardContext((prevState) => ({ ...prevState, @@ -69,15 +74,30 @@ const InstanceTypesSelect = ({ setValidation, architecture }) => { setIsOpen(false); }; - const onFilter = (_e, inputValue) => { - const search = sanitizeSearchValue(inputValue); - if (prevSearch !== search) { - setNumOptions(OPTIONS_PER_SCREEN); - setPrevSearch(search); - setFilteredTypes(instanceTypes.filter((i) => i.name.toLowerCase().includes(search.toLowerCase()))); + const queryFilter = async (search) => { + if (search.length > 0) { + const { error: queryError, result } = await evaluateQuery(search, instanceTypes); + if (queryError) { + setFilteredTypes([]); + } else if (Array.isArray(result)) { + setFilteredTypes(result); + } else if (result instanceof Object) { + setFilteredTypes([result]); + } } }; + const onTypeaheadInputChange = (inputValue) => { + if (inputValue === '') { + setFilteredTypes(null); + return; + } + const search = sanitizeSearchValue(inputValue); + setNumOptions(OPTIONS_PER_SCREEN); + const throttledFilter = _throttle(queryFilter, 200); + throttledFilter(search); + }; + const selectItemsMapper = (types, limit) => { if (limit < types?.length) types = types.slice(0, limit); return types?.map((instanceType, index) => ( @@ -132,7 +152,9 @@ const InstanceTypesSelect = ({ setValidation, architecture }) => { selections={chosenInstanceType} onToggle={onToggle} onSelect={onSelect} - onFilter={onFilter} + onFilter={() => {}} + isInputValuePersisted + onTypeaheadInputChanged={onTypeaheadInputChange} {...(numOptions < types?.length && { loadingVariant: { text: `View more (${types.length - numOptions})`, diff --git a/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js b/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js index a659067b..7678c8f5 100644 --- a/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js +++ b/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { Form, FormGroup, Popover, Title, Button } from '@patternfly/react-core'; +import { Form, FormGroup, Popover, Title, Button, Text } from '@patternfly/react-core'; import { HelpIcon } from '@patternfly/react-icons'; import { AWS_PROVIDER } from '../../../../constants'; @@ -86,8 +86,16 @@ const AccountCustomizationsAWS = ({ setStepValidated, image }) => { fieldId="aws-select-instance-types" labelIcon={ + Select AWS instance type based on your computing, memory, networking, or storage needs +
+
+ Tip: You can filter by a query search, i.e: +
+ {'vcpus = 2 and cores < 4 and memory < 4000'} + + } >