From 5f99bc79cf6bf7c99bf5b91dd2c3333410406ab5 Mon Sep 17 00:00:00 2001 From: regexowl Date: Thu, 30 Mar 2023 09:23:20 +0200 Subject: [PATCH] Wizard: Migrate activation keys calls to RTK Query This migrates calls to RHSM endpoints `activation_keys` and `/activation_keys/{name}` to RTK Query. Tests were also updated to use Mock Service Worker instead of previous Jest mock function. --- .../ActivationKeyInformation.js | 224 ++++++++++-------- .../formComponents/ActivationKeys.js | 97 +++++--- .../formComponents/ReviewStepTextLists.js | 172 ++++++++------ src/api.js | 16 +- src/store/apiSlice.js | 14 +- .../CreateImageWizard.azure.beta.test.js | 11 - .../CreateImageWizard.beta.test.js | 62 ----- .../CreateImageWizard.test.js | 62 ----- src/test/mocks/handlers.js | 76 +++++- 9 files changed, 374 insertions(+), 360 deletions(-) diff --git a/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.js b/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.js index b0b8718c..3dbce2e8 100644 --- a/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.js +++ b/src/Components/CreateImageWizard/formComponents/ActivationKeyInformation.js @@ -1,7 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { useFormApi } from '@data-driven-forms/react-form-renderer'; +import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context'; import { + Alert, + Spinner, Text, TextContent, TextList, @@ -21,119 +24,152 @@ import { Tr, } from '@patternfly/react-table'; -import api from '../../../api'; +import { useGetActivationKeyInformationQuery } from '../../../store/apiSlice'; const ActivationKeyInformation = () => { const { getState } = useFormApi(); + const { currentStep } = useContext(WizardContext); const activationKey = getState()?.values?.['subscription-activation-key']; - const [role, setRole] = useState(undefined); - const [serviceLevel, setServiceLevel] = useState(undefined); - const [usage, setUsage] = useState(undefined); - const [additionalRepositories, setRepositories] = useState(undefined); - useEffect(() => { - const fetchKeyInformation = async () => { - const data = await api.getActivationKey(activationKey); - setRole(data?.role); - setServiceLevel(data?.serviceLevel); - setUsage(data?.usage); - setRepositories(data?.additionalRepositories); - }; - fetchKeyInformation(); - }, []); + const { + data: activationKeyInfo, + isFetching: isFetchingActivationKeyInfo, + isSuccess: isSuccessActivationKeyInfo, + isError: isErrorActivationKeyInfo, + } = useGetActivationKeyInformationQuery(activationKey, { + skip: !activationKey, + }); return ( <> - - - Name: - - {activationKey} - - Role: - - {role || 'Not defined'} - - SLA: - - {serviceLevel || 'Not defined'} - - - Usage: - - - {usage || 'Not defined'} - - - Additional repositories: - - - The core repositories for your operating system version are - always enabled and do not need to be explicitly added to the - activation key. - - - } - > - - - - - {additionalRepositories?.length > 0 ? ( + {isFetchingActivationKeyInfo && } + {isSuccessActivationKeyInfo && ( + + + + Name: + + + {activationKey} + + + Role: + + + {activationKeyInfo.body?.role || 'Not defined'} + + + SLA: + + + {activationKeyInfo.body?.serviceLevel || 'Not defined'} + + + Usage: + + + {activationKeyInfo.body?.usage || 'Not defined'} + + + Additional repositories: - - Additional repositories + + The core repositories for your operating system version + are always enabled and do not need to be explicitly added + to the activation key. - - - - Name - - - - {additionalRepositories?.map((repo, index) => ( - - {repo.repositoryLabel} - - ))} - - } > - ) : ( - 'None' - )} - - - + + + {activationKeyInfo.body?.additionalRepositories?.length > 0 ? ( + + + Additional repositories + + + + + Name + + + + {activationKeyInfo.body?.additionalRepositories?.map( + (repo, index) => ( + + {repo.repositoryLabel} + + ) + )} + + + + } + > + + + ) : ( + 'None' + )} + + + + )} + {isErrorActivationKeyInfo && ( + + + + Name: + + + {activationKey} + + + + )} + {currentStep.name === 'registration' && ( + <> +
+ + Information about the activation key cannot be loaded. Please check + the key was not removed and try again later. + + + )} ); }; diff --git a/src/Components/CreateImageWizard/formComponents/ActivationKeys.js b/src/Components/CreateImageWizard/formComponents/ActivationKeys.js index 893d3e09..4a211793 100644 --- a/src/Components/CreateImageWizard/formComponents/ActivationKeys.js +++ b/src/Components/CreateImageWizard/formComponents/ActivationKeys.js @@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react'; import useFieldApi from '@data-driven-forms/react-form-renderer/use-field-api'; import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; import { + Alert, FormGroup, Select, SelectOption, @@ -11,26 +12,25 @@ import { } from '@patternfly/react-core'; import PropTypes from 'prop-types'; -import api from '../../../api'; +import { useGetActivationKeysQuery } from '../../../store/apiSlice'; const ActivationKeys = ({ label, isRequired, ...props }) => { const { change, getState } = useFormApi(); const { input } = useFieldApi(props); - const [activationKeys, setActivationKeys] = useState([]); const [isOpen, setIsOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); const [activationKeySelected, selectActivationKey] = useState( getState()?.values?.['subscription-activation-key'] ); - useEffect(() => { - setIsLoading(true); - const data = api.getActivationKeys(); - data.then((keys) => { - setActivationKeys(keys); - setIsLoading(false); - }); + const { + data: activationKeys, + isFetching: isFetchingActivationKeys, + isSuccess: isSuccessActivationKeys, + isError: isErrorActivationKeys, + refetch, + } = useGetActivationKeysQuery(); + useEffect(() => { if (insights.chrome.isProd()) { change('subscription-server-url', 'subscription.rhsm.redhat.com'); change('subscription-base-url', 'https://cdn.redhat.com/'); @@ -51,36 +51,57 @@ const ActivationKeys = ({ label, isRequired, ...props }) => { change(input.name, undefined); }; + const handleToggle = () => { + if (!isOpen) { + refetch(); + } + setIsOpen(!isOpen); + }; + return ( - - - + + + {isErrorActivationKeys && ( + + Activation keys cannot be reached, try again later. + + )} + ); }; diff --git a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js b/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js index b6db5bb7..81707cce 100644 --- a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js +++ b/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useFormApi } from '@data-driven-forms/react-form-renderer'; import { + Alert, Button, Popover, Spinner, @@ -27,6 +28,7 @@ import { useGetAWSSourcesQuery, useGetAzureSourcesQuery, } from '../../../store/apiSlice'; +import { useGetActivationKeyInformationQuery } from '../../../store/apiSlice'; import { googleAccType } from '../steps/googleCloud'; const ExpirationWarning = () => { @@ -429,85 +431,103 @@ export const RegisterLaterList = () => { export const RegisterNowList = () => { const { getState } = useFormApi(); + const activationKey = getState()?.values?.['subscription-activation-key']; + const { isError } = useGetActivationKeyInformationQuery(activationKey, { + skip: !activationKey, + }); return ( - - - - Registration type - - - - {getState()?.values?.['register-system']?.startsWith( - 'register-now' - ) && ( - - Register with Red Hat Subscription Manager (RHSM) -
-
- )} - {(getState()?.values?.['register-system'] === - 'register-now-insights' || - getState()?.values?.['register-system'] === - 'register-now-rhc') && ( - - Connect to Red Hat Insights -
-
- )} - {getState()?.values?.['register-system'] === 'register-now-rhc' && ( - - Use remote host configuration (RHC) utility -
-
- )} -
-
- - Activation key - - - Activation keys enable you to register a system with - appropriate subscriptions, system purpose, and repositories - attached. -
-
- If using an activation key with command line registration, you - must provide your organization's ID. Your - organization's ID is{' '} - {getState()?.values?.['subscription-organization-id'] !== - undefined ? ( - getState()?.values?.['subscription-organization-id'] - ) : ( - - )} -
-
- } + <> + + + - - - - - - - -
-
+ + + + + + + +
+ + {isError && ( + + Information about the activation key cannot be loaded. Please check + the key was not removed and try again later. + + )} + ); }; diff --git a/src/api.js b/src/api.js index 59060c06..f5915232 100644 --- a/src/api.js +++ b/src/api.js @@ -1,6 +1,6 @@ import axios from 'axios'; -import { CONTENT_SOURCES, IMAGE_BUILDER_API, RHSM_API } from './constants'; +import { CONTENT_SOURCES, IMAGE_BUILDER_API } from './constants'; const postHeaders = { headers: { 'Content-Type': 'application/json' } }; @@ -77,18 +77,6 @@ async function getVersion() { return request.data; } -async function getActivationKeys() { - const path = '/activation_keys'; - const request = await axios.get(RHSM_API.concat(path)); - return request.data.body; -} - -async function getActivationKey(name) { - const path = `/activation_keys/${name}`; - const request = await axios.get(RHSM_API.concat(path)); - return request.data.body; -} - // get clones of a compose async function getClones(id, limit, offset) { const params = new URLSearchParams({ @@ -127,6 +115,4 @@ export default { getPackagesContentSources, getRepositories, getVersion, - getActivationKeys, - getActivationKey, }; diff --git a/src/store/apiSlice.js b/src/store/apiSlice.js index dbf6620e..53c61238 100644 --- a/src/store/apiSlice.js +++ b/src/store/apiSlice.js @@ -1,6 +1,10 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import { IMAGE_BUILDER_API, PROVISIONING_SOURCES_ENDPOINT } from '../constants'; +import { + IMAGE_BUILDER_API, + PROVISIONING_SOURCES_ENDPOINT, + RHSM_API, +} from '../constants'; export const apiSlice = createApi({ reducerPath: 'api', @@ -53,6 +57,12 @@ export const apiSlice = createApi({ query: (distribution) => `${IMAGE_BUILDER_API}/architectures/${distribution}`, }), + getActivationKeys: builder.query({ + query: () => `${RHSM_API}/activation_keys`, + }), + getActivationKeyInformation: builder.query({ + query: (activationKey) => `${RHSM_API}/activation_keys/${activationKey}`, + }), }), }); @@ -61,5 +71,7 @@ export const { useGetArchitecturesByDistributionQuery, useGetAzureSourcesQuery, useGetAzureSourceDetailQuery, + useGetActivationKeysQuery, + useGetActivationKeyInformationQuery, usePrefetch, } = apiSlice; diff --git a/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js b/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js index 5a2eba3a..c1e9f086 100644 --- a/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js +++ b/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js @@ -32,17 +32,6 @@ describe('Step Upload to Azure', () => { // scrollTo is not defined in jsdom window.HTMLElement.prototype.scrollTo = function () {}; - // mock the activation key api call - const mockActivationKeys = [{ name: 'name0' }, { name: 'name1' }]; - jest - .spyOn(api, 'getActivationKeys') - .mockImplementation(() => Promise.resolve(mockActivationKeys)); - - const mockActivationKey = { body: [{ name: 'name0' }, { name: 'name1' }] }; - jest.spyOn(api, 'getActivationKey').mockImplementation((name) => { - return Promise.resolve(mockActivationKey[name]); - }); - jest .spyOn(api, 'getRepositories') .mockImplementation(() => Promise.resolve(mockRepositoryResults)); diff --git a/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js b/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js index 96bc93dc..9084be61 100644 --- a/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js +++ b/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js @@ -143,17 +143,6 @@ beforeAll(() => { // scrollTo is not defined in jsdom window.HTMLElement.prototype.scrollTo = function () {}; - // mock the activation key api call - const mockActivationKeys = [{ name: 'name0' }, { name: 'name1' }]; - jest - .spyOn(api, 'getActivationKeys') - .mockImplementation(() => Promise.resolve(mockActivationKeys)); - - const mockActivationKey = { body: [{ name: 'name0' }, { name: 'name1' }] }; - jest.spyOn(api, 'getActivationKey').mockImplementation((name) => { - return Promise.resolve(mockActivationKey[name]); - }); - global.insights = { chrome: { auth: { @@ -804,57 +793,6 @@ describe('Click through all steps', () => { screen.getByRole('button', { name: /Next/ }).click(); // registration - const mockActivationKeys = [ - { id: '0', name: 'name0' }, - { id: 1, name: 'name1' }, - ]; - jest - .spyOn(api, 'getActivationKeys') - .mockImplementation(() => Promise.resolve(mockActivationKeys)); - const mockActivationKey = { - name0: { - additionalRepositories: [ - { - repositoryLabel: 'repository0', - }, - { - repositoryLabel: 'repository1', - }, - { - repositoryLabel: 'repository2', - }, - ], - id: '0', - name: 'name0', - releaseVersion: '', - role: '', - serviceLevel: 'Self-Support', - usage: 'Production', - }, - name1: { - additionalRepositories: [ - { - repositoryLabel: 'repository3', - }, - { - repositoryLabel: 'repository4', - }, - { - repositoryLabel: 'repository5', - }, - ], - id: '1', - name: 'name1', - releaseVersion: '', - role: '', - serviceLevel: 'Premium', - usage: 'Production', - }, - }; - jest.spyOn(api, 'getActivationKey').mockImplementation((name) => { - return Promise.resolve(mockActivationKey[name]); - }); - const activationKeyDropdown = await screen.findByRole('textbox', { name: 'Select activation key', }); diff --git a/src/test/Components/CreateImageWizard/CreateImageWizard.test.js b/src/test/Components/CreateImageWizard/CreateImageWizard.test.js index dfca3ac7..e69120c2 100644 --- a/src/test/Components/CreateImageWizard/CreateImageWizard.test.js +++ b/src/test/Components/CreateImageWizard/CreateImageWizard.test.js @@ -110,17 +110,6 @@ beforeAll(() => { // scrollTo is not defined in jsdom window.HTMLElement.prototype.scrollTo = function () {}; - // mock the activation key api call - const mockActivationKeys = [{ name: 'name0' }, { name: 'name1' }]; - jest - .spyOn(api, 'getActivationKeys') - .mockImplementation(() => Promise.resolve(mockActivationKeys)); - - const mockActivationKey = { body: [{ name: 'name0' }, { name: 'name1' }] }; - jest.spyOn(api, 'getActivationKey').mockImplementation((name) => { - return Promise.resolve(mockActivationKey[name]); - }); - global.insights = { chrome: { auth: { @@ -1380,57 +1369,6 @@ describe('Click through all steps', () => { screen.getByRole('button', { name: /Next/ }).click(); // registration - const mockActivationKeys = [ - { id: '0', name: 'name0' }, - { id: 1, name: 'name1' }, - ]; - jest - .spyOn(api, 'getActivationKeys') - .mockImplementation(() => Promise.resolve(mockActivationKeys)); - const mockActivationKey = { - name0: { - additionalRepositories: [ - { - repositoryLabel: 'repository0', - }, - { - repositoryLabel: 'repository1', - }, - { - repositoryLabel: 'repository2', - }, - ], - id: '0', - name: 'name0', - releaseVersion: '', - role: '', - serviceLevel: 'Self-Support', - usage: 'Production', - }, - name1: { - additionalRepositories: [ - { - repositoryLabel: 'repository3', - }, - { - repositoryLabel: 'repository4', - }, - { - repositoryLabel: 'repository5', - }, - ], - id: '1', - name: 'name1', - releaseVersion: '', - role: '', - serviceLevel: 'Premium', - usage: 'Production', - }, - }; - jest.spyOn(api, 'getActivationKey').mockImplementation((name) => { - return Promise.resolve(mockActivationKey[name]); - }); - const registrationRadio = screen.getByTestId('registration-radio-now'); await user.click(registrationRadio); diff --git a/src/test/mocks/handlers.js b/src/test/mocks/handlers.js index 4d99a33b..477938e1 100644 --- a/src/test/mocks/handlers.js +++ b/src/test/mocks/handlers.js @@ -1,6 +1,6 @@ import { rest } from 'msw'; -import { PROVISIONING_SOURCES_ENDPOINT } from '../../constants'; +import { PROVISIONING_SOURCES_ENDPOINT, RHSM_API } from '../../constants'; const baseURL = 'http://localhost'; @@ -245,4 +245,78 @@ export const handlers = [ } } ), + rest.get(baseURL.concat(`${RHSM_API}/activation_keys`), (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + body: [ + { + id: 0, + name: 'name0', + }, + { + id: 1, + name: 'name1', + }, + ], + }) + ); + }), + rest.get( + baseURL.concat(`${RHSM_API}/activation_keys/:key`), + (req, res, ctx) => { + const { key } = req.params; + if (key === 'name0') { + return res( + ctx.status(200), + ctx.json({ + body: { + additionalRepositories: [ + { + repositoryLabel: 'repository0', + }, + { + repositoryLabel: 'repository1', + }, + { + repositoryLabel: 'repository2', + }, + ], + id: '0', + name: 'name0', + releaseVersion: '', + role: '', + serviceLevel: 'Self-Support', + usage: 'Production', + }, + }) + ); + } else if (key === 'name1') { + return res( + ctx.status(200), + ctx.json({ + body: { + additionalRepositories: [ + { + repositoryLabel: 'repository3', + }, + { + repositoryLabel: 'repository4', + }, + { + repositoryLabel: 'repository5', + }, + ], + id: '1', + name: 'name1', + releaseVersion: '', + role: '', + serviceLevel: 'Premium', + usage: 'Production', + }, + }) + ); + } + } + ), ];