From 3e73a67fa9779537536225be62ba034813eb9017 Mon Sep 17 00:00:00 2001 From: Ondrej Ezr Date: Thu, 5 Oct 2023 20:06:29 +0200 Subject: [PATCH] feat(HMS-1772): Allow selecting custom resource group --- src/API/index.js | 7 ++ src/API/queryKeys.js | 1 + .../AzureResourceGroup.test.js | 39 ++++++++ src/Components/AzureResourceGroup/index.js | 98 +++++++++++++++++++ src/Components/LaunchDescriptionList/index.js | 20 +++- .../steps/AccountCustomizations/azure.js | 25 +++++ .../steps/ReservationProgress/index.js | 2 + src/mocks/fixtures/sources.fixtures.js | 18 ++++ src/mocks/handlers.js | 4 +- src/mocks/utils.js | 7 +- 10 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 src/Components/AzureResourceGroup/AzureResourceGroup.test.js create mode 100644 src/Components/AzureResourceGroup/index.js diff --git a/src/API/index.js b/src/API/index.js index f8f018d8..accf6830 100644 --- a/src/API/index.js +++ b/src/API/index.js @@ -72,3 +72,10 @@ export const fetchLaunchTemplates = async (sourceID, region) => { } = await axios.get(provisioningUrl(`sources/${sourceID}/launch_templates?region=${region}`)); return data; }; + +export const fetchResourceGroups = async (sourceID) => { + const { + data: { azure }, + } = await axios.get(provisioningUrl(`sources/${sourceID}/upload_info`)); + return azure?.resource_groups; +}; diff --git a/src/API/queryKeys.js b/src/API/queryKeys.js index 806c0efc..1605eb9e 100644 --- a/src/API/queryKeys.js +++ b/src/API/queryKeys.js @@ -4,3 +4,4 @@ export const PUBKEYS_QUERY_KEY = ['pubkeys']; export const instanceTypesQueryKeys = (region) => ['instanceTypes', region]; export const IMAGE_REGIONS_KEY = 'image_region'; export const TEMPLATES_KEY = 'templates'; +export const AZURE_RG_KEY = 'azure_resource_group'; diff --git a/src/Components/AzureResourceGroup/AzureResourceGroup.test.js b/src/Components/AzureResourceGroup/AzureResourceGroup.test.js new file mode 100644 index 00000000..beeb84a7 --- /dev/null +++ b/src/Components/AzureResourceGroup/AzureResourceGroup.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '../../mocks/utils'; +import { provisioningUrl } from '../../API/helpers'; +import { azureSourceUploadInfo } from '../../mocks/fixtures/sources.fixtures'; +import AzureResourceGroup from '.'; + +describe('AzureResourceGroup', () => { + test('populate resource group select', async () => { + await mountSelectAndOpen(); + const items = await screen.findAllByLabelText(/^Resource group/); + expect(items).toHaveLength(azureSourceUploadInfo.azure.resource_groups.length); + }); + + test('handles error', async () => { + const { server, rest } = window.msw; + + server.use( + rest.get(provisioningUrl('sources/:sourceID/upload_info'), (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ msg: 'AWS API error: unable to get AWS upload info' })); + }) + ); + + render(, { + contextValues: { chosenSource: '66' }, + }); + + expect(await screen.findByText('Can not fetch resource groups')).toBeInTheDocument(); + }); +}); + +const mountSelectAndOpen = async () => { + render(, { + contextValues: { chosenSource: '66' }, + }); + const selectDropdown = await screen.findByLabelText('Select resource group'); + await userEvent.click(selectDropdown); + return selectDropdown; +}; diff --git a/src/Components/AzureResourceGroup/index.js b/src/Components/AzureResourceGroup/index.js new file mode 100644 index 00000000..a6d458a2 --- /dev/null +++ b/src/Components/AzureResourceGroup/index.js @@ -0,0 +1,98 @@ +import React from 'react'; + +import { Spinner, Select, SelectOption, TextInput } from '@patternfly/react-core'; +import { useQuery } from '@tanstack/react-query'; +import { AZURE_RG_KEY } from '../../API/queryKeys'; +import { fetchResourceGroups } from '../../API'; +import { useWizardContext } from '../Common/WizardContext'; + +const AzureResourceGroup = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [{ chosenSource, azureResourceGroup }, setWizardContext] = useWizardContext(); + const [selection, setSelection] = React.useState(azureResourceGroup); + + const { + isInitialLoading: isLoading, + error, + data: resourceGroups, + } = useQuery([AZURE_RG_KEY, chosenSource], () => fetchResourceGroups(chosenSource), { + staleTime: 30 * 1000, // data is considered fresh for 30 seconds + enabled: !!chosenSource, + }); + + if (!chosenSource || chosenSource === '') { + return ( + <> + + + ); + } + + if (isLoading) { + return ; + } + + if (error) { + return ( + <> + setIsOpen(isExpanded)} + onSelect={onSelect} + isOpen={isOpen} + onClear={clearSelection} + selections={selection} + placeholderText="redhat-deployed (default)" + typeAheadAriaLabel="Select resource group" + maxHeight="220px" + > + {resourceGroups.map((name, idx) => ( + + ))} + + ); +}; + +export default AzureResourceGroup; diff --git a/src/Components/LaunchDescriptionList/index.js b/src/Components/LaunchDescriptionList/index.js index 3a2a04b0..c623896b 100644 --- a/src/Components/LaunchDescriptionList/index.js +++ b/src/Components/LaunchDescriptionList/index.js @@ -9,10 +9,22 @@ import { useQuery } from '@tanstack/react-query'; import { fetchLaunchTemplates } from '../../API'; import { humanizeProvider } from '../Common/helpers'; import { TEMPLATES_KEY } from '../../API/queryKeys'; +import { AZURE_PROVIDER } from '../../constants'; const LaunchDescriptionList = ({ imageName }) => { const [ - { chosenRegion, chosenSshKeyName, uploadedKey, chosenInstanceType, chosenNumOfInstances, chosenSource, sshPublicName, provider, chosenTemplate }, + { + chosenRegion, + chosenSshKeyName, + uploadedKey, + chosenInstanceType, + chosenNumOfInstances, + chosenSource, + sshPublicName, + provider, + chosenTemplate, + azureResourceGroup, + }, ] = useWizardContext(); const { sources } = useSourcesData(provider); const { data: templates } = useQuery([TEMPLATES_KEY, `${chosenRegion}-${chosenSource}`], () => fetchLaunchTemplates(chosenSource, chosenRegion), { @@ -41,6 +53,12 @@ const LaunchDescriptionList = ({ imageName }) => { Account {getChosenSourceName()} + {provider === AZURE_PROVIDER && azureResourceGroup && ( + + Resource group + {azureResourceGroup} + + )} {regionLabel} {chosenRegion} diff --git a/src/Components/ProvisioningWizard/steps/AccountCustomizations/azure.js b/src/Components/ProvisioningWizard/steps/AccountCustomizations/azure.js index d1e252ba..e4ff59ca 100644 --- a/src/Components/ProvisioningWizard/steps/AccountCustomizations/azure.js +++ b/src/Components/ProvisioningWizard/steps/AccountCustomizations/azure.js @@ -9,6 +9,7 @@ import SourcesSelect from '../../../SourcesSelect'; import InstanceCounter from '../../../InstanceCounter'; import InstanceTypesSelect from '../../../InstanceTypesSelect'; import RegionsSelect from '../../../RegionsSelect'; +import AzureResourceGroup from '../../../AzureResourceGroup'; import { useWizardContext } from '../../../Common/WizardContext'; const AccountCustomizationsAzure = ({ setStepValidated, image }) => { @@ -77,6 +78,30 @@ const AccountCustomizationsAzure = ({ setStepValidated, image }) => { > + Azure resource group} + bodyContent={
Azure resource group to deploy the VM resources into. If left blank, defaults to ‘redhat-deployed’.
} + > + + + } + > + +
{ chosenImageID, provider, chosenTemplate, + azureResourceGroup, }, ] = useWizardContext(); const { nextInterval, currentInterval } = useInterval(POLLING_BACKOFF_INTERVAL); @@ -65,6 +66,7 @@ const ReservationProgress = ({ setLaunchSuccess }) => { [region(provider)]: chosenRegion, pubkey_id: chosenSshKeyId, ...(chosenTemplate && { launch_template_id: chosenTemplate }), + ...(azureResourceGroup && { resource_group: azureResourceGroup }), }); React.useEffect(() => { diff --git a/src/mocks/fixtures/sources.fixtures.js b/src/mocks/fixtures/sources.fixtures.js index dc47a05a..ebb8c9c8 100644 --- a/src/mocks/fixtures/sources.fixtures.js +++ b/src/mocks/fixtures/sources.fixtures.js @@ -17,10 +17,28 @@ export const gcpSourcesList = { ], }; +export const azureSourcesList = { + data: [ + { + name: 'Azure Source 1', + id: '66', + }, + ], +}; + export const gcpSourceUploadInfo = () => ({ gcp: {} }); export const awsSourceUploadInfo = (account_id = '123456789') => ({ aws: { account_id } }); +export const azureSourceUploadInfo = { + azure: { + resource_groups: ['testGroup', 'Cool Group'], + subscription_id: '617807e1-e4e0-4855-983c-1e3ce1e49674', + tenant_id: '617807e1-e4e0-481c-983c-be3ce1e49253', + }, + provider: 'azure', +}; + export const awsSourceFailedUploadInfo = () => ({ msg: 'AWS API error: unable to get AWS upload info', trace_id: 'trcid', diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index 27e2f5b1..b4b10319 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -1,7 +1,7 @@ import { rest } from 'msw'; import { imageBuilderURL, provisioningUrl } from '../API/helpers'; import { awsInstanceTypeList, azureInstanceTypeList } from './fixtures/instanceTypes.fixtures'; -import { sourcesList, gcpSourcesList, awsSourceUploadInfo, gcpSourceUploadInfo } from './fixtures/sources.fixtures'; +import { sourcesList, gcpSourcesList, awsSourceUploadInfo, azureSourceUploadInfo, gcpSourceUploadInfo } from './fixtures/sources.fixtures'; import { pubkeysList } from './fixtures/pubkeys.fixtures'; import { clonedImages, parentImage, successfulCloneStatus } from './fixtures/image.fixtures'; import { @@ -30,6 +30,8 @@ export const handlers = [ return res(ctx.status(200), ctx.json(awsSourceUploadInfo())); } else if (sourceID === '10') { return res(ctx.status(200), ctx.json(gcpSourceUploadInfo())); + } else if (sourceID === '66') { + return res(ctx.status(200), ctx.json(azureSourceUploadInfo)); } }), rest.get(provisioningUrl('pubkeys'), (req, res, ctx) => { diff --git a/src/mocks/utils.js b/src/mocks/utils.js index 96449ce9..6da7e665 100644 --- a/src/mocks/utils.js +++ b/src/mocks/utils.js @@ -5,7 +5,7 @@ import { render } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WizardProvider } from '../Components/Common/WizardContext'; -const AllProviders = ({ children, provider, ...contextValues }) => { +const AllProviders = ({ children, provider, queryRetries = false, ...contextValues }) => { const queryClient = new QueryClient({ logger: { log: console.log, @@ -13,6 +13,11 @@ const AllProviders = ({ children, provider, ...contextValues }) => { // ✅ no more errors on the console error: () => {}, }, + defaultOptions: { + queries: { + retry: queryRetries, + }, + }, }); return (