From 7cf30db56bb0b0b637a0e8c8a5a511cda25721e5 Mon Sep 17 00:00:00 2001 From: Alejandro Visiedo Date: Sun, 1 Oct 2023 17:10:50 +0200 Subject: [PATCH] HMS-2593 feat: add VerifyRegistry component This change add an initial component to the wizard, which check verify when the registration happened. This change add new callbacks to the application context to make available the information into the whole application. Signed-off-by: Alejandro Visiedo --- src/AppContext.tsx | 12 + src/AppEntry.tsx | 10 + .../VerifyRegistry/VerifyRegistry.scss | 1 + .../VerifyRegistry/VerifyRegistry.test.tsx | 10 + .../VerifyRegistry/VerifyRegistry.tsx | 263 ++++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss create mode 100644 src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx create mode 100644 src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx diff --git a/src/AppContext.tsx b/src/AppContext.tsx index 0d0a559..7e9291d 100644 --- a/src/AppContext.tsx +++ b/src/AppContext.tsx @@ -1,5 +1,6 @@ import { createContext } from 'react'; import { Domain } from './Api'; +import { VerifyState } from './Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry'; /** * It represents the application context so common events and properties @@ -17,6 +18,11 @@ export interface IAppContext { getToken: () => string; /** Set the value of the token. */ setToken: (value: string) => void; + /** Retrieve the value of the registered status which is updated once + * the user has registered the domain by using ipa-hcc tool. */ + getRegisteredStatus: () => VerifyState; + /** Setter for the registered status. */ + setRegisteredStatus: (value: VerifyState) => void; /** Get the ephemeral domain state that manage the wizard. */ getDomain: () => Domain; /** Set the ephemeral domain information. */ @@ -40,6 +46,12 @@ export const AppContext = createContext({ setToken: (value: string) => { throw new Error('Function "setToken" not implemented: value=' + value); }, + getRegisteredStatus: (): VerifyState => { + return 'initial'; + }, + setRegisteredStatus: (value: VerifyState) => { + throw new Error('Function "setRegisteredStatus" not implemented: value=' + value); + }, getDomain: (): Domain => { return {} as Domain; }, diff --git a/src/AppEntry.tsx b/src/AppEntry.tsx index 4fe8379..557347b 100644 --- a/src/AppEntry.tsx +++ b/src/AppEntry.tsx @@ -7,12 +7,14 @@ import { getBaseName } from '@redhat-cloud-services/frontend-components-utilitie import logger from 'redux-logger'; import { AppContext } from './AppContext'; import { Domain } from './Api'; +import { VerifyState } from './Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry'; const AppEntry = () => { const appContext = useContext(AppContext); const [domains, setDomains] = useState([]); const [wizardToken, setWizardToken] = useState(''); const [wizardDomain, setWizardDomain] = useState({} as Domain); + const [wizardRegisterStatus, setWizardRegisterStatus] = useState('initial'); const cbSetDomains = (domains: Domain[]) => { appContext.domains = domains; setDomains(domains); @@ -29,6 +31,12 @@ const AppEntry = () => { const cbSetWizardDomain = (value: Domain) => { setWizardDomain(value); }; + const cbGetRegisterStatus = (): VerifyState => { + return wizardRegisterStatus; + }; + const cbSetRegisterStatus = (value: VerifyState) => { + setWizardRegisterStatus(value); + }; return ( @@ -39,6 +47,8 @@ const AppEntry = () => { wizard: { getToken: cbGetWizardToken, setToken: cbSetWizardToken, + getRegisteredStatus: cbGetRegisterStatus, + setRegisteredStatus: cbSetRegisterStatus, getDomain: cbGetWizardDomain, setDomain: cbSetWizardDomain, }, diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss new file mode 100644 index 0000000..acd9576 --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.scss @@ -0,0 +1 @@ +@import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx new file mode 100644 index 0000000..ad7d07b --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.test.tsx @@ -0,0 +1,10 @@ +// import React from 'react'; +// import { render } from '@testing-library/react'; +// import Page1 from './Page1'; +import '@testing-library/jest-dom'; + +test('expect VerifyRegistry to render children', () => { + // render(); + // expect(screen.getByRole('heading')).toHaveTextContent('Hello'); + return; +}); diff --git a/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx new file mode 100644 index 0000000..90305a1 --- /dev/null +++ b/src/Routes/WizardPage/Components/VerifyRegistry/VerifyRegistry.tsx @@ -0,0 +1,263 @@ +import React, { useEffect, useState } from 'react'; + +import { AxiosError } from 'axios'; +import { Button, Icon, Stack, StackItem, TextContent } from '@patternfly/react-core'; +import { CheckCircleIcon } from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { PendingIcon } from '@patternfly/react-icons/dist/esm/icons/pending-icon'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; + +import './VerifyRegistry.scss'; +import { Domain, ResourcesApiFactory } from '../../../../Api'; + +/* Common definitions */ + +export type VerifyState = 'initial' | 'waiting' | 'timed-out' | 'not-found' | 'completed'; + +/* VerifyRegistryIcon component */ + +interface VerifyRegistryIconProps { + state: VerifyState; +} + +const VerifyRegistryIcon = (props: VerifyRegistryIconProps) => { + return ( + <> + {props.state == 'initial' && ( + + + + )} + {props.state == 'waiting' && ( + + + + )} + {props.state == 'timed-out' && ( + + + + )} + {props.state == 'not-found' && ( + + + + )} + {props.state == 'completed' && ( + + + + )} + + ); +}; + +/* VerifyRegistryLabel component */ + +interface VerifyRegistryLabelProps { + state: VerifyState; +} + +const VerifyRegistryLabel = (props: VerifyRegistryLabelProps) => { + return ( + <> + {props.state == 'initial' && Verify registration} + {props.state == 'waiting' && Verify registration} + {props.state == 'timed-out' && Verify registration} + {props.state == 'not-found' && Verify registration} + {props.state == 'completed' && Verify registration} + + ); +}; + +/* VerifyRegistryLabel component */ + +interface VerifyRegistryDescriptionProps { + state: VerifyState; +} + +const VerifyRegistryDescription = (props: VerifyRegistryDescriptionProps) => { + return ( + <> + {props.state == 'initial' && Running verification test} + {props.state == 'waiting' && Waiting for registration data} + {props.state == 'timed-out' && Test timed out} + {props.state == 'not-found' && Registration data not found} + {props.state == 'completed' && Test completed} + + ); +}; + +/* VerifyRegistryyFooter */ + +interface VerifyRegistryFooterProps { + state: VerifyState; + onTest?: () => void; +} + +const VerifyRegistryFooter = (props: VerifyRegistryFooterProps) => { + const linkTroubleshootRegistration = 'https://www.google.com/search?q=freeipa+troubleshooting'; + return ( + <> + {props.state == 'initial' && ( + <> + + + )} + {props.state == 'waiting' && <>} + {props.state == 'timed-out' && ( + <> + + + )} + {props.state == 'not-found' && ( + <> + + + )} + {props.state == 'completed' && ( + <> + + + )} + + ); +}; + +/* VerifyRegistry component */ + +interface VerifyRegistryProps { + state: VerifyState; + uuid: string; + onChange: (newState: VerifyState, domain?: Domain) => void; +} + +const VerifyRegistry = (props: VerifyRegistryProps) => { + const [isPolling, setIsPolling] = useState(true); + + const base_url = '/api/idmsvc/v1'; + const resources_api = ResourcesApiFactory(undefined, base_url, undefined); + const timeout = 3 * 1000; // Seconds + + /** TODO Extract this effect in a hook to simplify this code */ + useEffect(() => { + let intervalId: NodeJS.Timeout | null = null; + let elapsedTime = 0; + let newState: VerifyState = props.state; + let domain: Domain | undefined = undefined; + const stopPolling = (state: VerifyState, domain?: Domain) => { + if (intervalId) { + clearInterval(intervalId); + } + intervalId = null; + setIsPolling(false); + if (state === 'completed') { + props.onChange(state, domain); + } else { + props.onChange(state); + } + }; + if (!isPolling) { + return; + } + const fetchData = async () => { + try { + const response = await resources_api.readDomain(props.uuid, undefined, undefined); + newState = 'completed'; + domain = response.data; + newState = 'completed'; + } catch (error) { + const axiosError = error as AxiosError; + switch (axiosError.code) { + case AxiosError.ECONNABORTED: + case AxiosError.ERR_BAD_OPTION: + case AxiosError.ERR_BAD_OPTION_VALUE: + case AxiosError.ERR_CANCELED: + newState = 'waiting'; + break; + case AxiosError.ERR_DEPRECATED: + case AxiosError.ERR_FR_TOO_MANY_REDIRECTS: + case AxiosError.ERR_NETWORK: + case AxiosError.ETIMEDOUT: + newState = 'timed-out'; + break; + case AxiosError.ERR_BAD_REQUEST: + case AxiosError.ERR_BAD_RESPONSE: + default: + newState = 'waiting'; + break; + } + } + + if (newState !== undefined && newState !== props.state) { + switch (newState) { + case 'timed-out': + case 'waiting': + props.onChange(newState); + // setState(newState); + break; + default: + if (elapsedTime >= timeout) { + newState = 'timed-out'; + stopPolling(newState); + } + break; + case 'completed': + stopPolling(newState, domain); + break; + } + } + elapsedTime += 1000; // Increase elapsed time by 1 second + if (elapsedTime > timeout) { + newState = 'timed-out'; + stopPolling(newState); + } + }; + + fetchData(); + intervalId = setInterval(fetchData, 1000); + + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + }, [isPolling]); + + const onRetry = () => { + props.onChange('initial'); + // setState('initial'); + setIsPolling(true); + }; + + return ( + + + + + + + + + + + + + + + + + ); +}; + +// VerifyRegistry.defaultProps = defaultVerifyRegistryProps; + +export default VerifyRegistry;