diff --git a/src/components/content/deployedServices/myServices/MyServices.tsx b/src/components/content/deployedServices/myServices/MyServices.tsx index 1b12675f..8f6310a2 100644 --- a/src/components/content/deployedServices/myServices/MyServices.tsx +++ b/src/components/content/deployedServices/myServices/MyServices.tsx @@ -45,10 +45,12 @@ import { useDestroyRequestSubmitQuery } from '../../order/destroy/useDestroyRequ import { Locks } from '../../order/locks/Locks'; import { Migrate } from '../../order/migrate/Migrate'; import { Modify } from '../../order/modify/Modify'; +import OrderSubmitStatusAlert from '../../order/orderStatus/OrderSubmitStatusAlert'; import { useServiceDetailsPollingQuery } from '../../order/orderStatus/useServiceDetailsPollingQuery'; import { PurgeServiceStatusAlert } from '../../order/purge/PurgeServiceStatusAlert'; import { usePurgeRequestStatusQuery } from '../../order/purge/usePurgeRequestStatusQuery'; import { usePurgeRequestSubmitQuery } from '../../order/purge/usePurgeRequestSubmitQuery'; +import useRedeployFailedDeploymentQuery from '../../order/retryDeployment/useRedeployFailedDeploymentQuery'; import { Scale } from '../../order/scale/Scale'; import RestartServiceStatusAlert from '../../order/serviceState/restart/RestartServiceStatusAlert'; import { useServiceStateRestartQuery } from '../../order/serviceState/restart/useServiceStateRestartQuery'; @@ -90,6 +92,7 @@ function MyServices(): React.JSX.Element { const [isStopRequestSubmitted, setIsStopRequestSubmitted] = useState(false); const [isRestartRequestSubmitted, setIsRestartRequestSubmitted] = useState(false); const [isDestroyRequestSubmitted, setIsDestroyRequestSubmitted] = useState(false); + const [isRetryDeployRequestSubmitted, setIsRetryDeployRequestSubmitted] = useState(false); const [isPurgeRequestSubmitted, setIsPurgeRequestSubmitted] = useState(false); const [isMyServiceDetailsModalOpen, setIsMyServiceDetailsModalOpen] = useState(false); const [isMyServiceHistoryModalOpen, setIsMyServiceHistoryModalOpen] = useState(false); @@ -99,6 +102,7 @@ function MyServices(): React.JSX.Element { const [isLocksModalOpen, setIsLocksModalOpen] = useState(false); const serviceDestroyQuery = useDestroyRequestSubmitQuery(); const servicePurgeQuery = usePurgeRequestSubmitQuery(); + const redeployFailedDeploymentQuery = useRedeployFailedDeploymentQuery(); const serviceStateStartQuery = useServiceStateStartQuery(refreshData); const serviceStateStopQuery = useServiceStateStopQuery(refreshData); const serviceStateRestartQuery = useServiceStateRestartQuery(refreshData); @@ -116,6 +120,16 @@ function MyServices(): React.JSX.Element { ] ); + const getRetryDeployServiceDetailsByIdQuery = useServiceDetailsPollingQuery( + activeRecord?.serviceId, + redeployFailedDeploymentQuery.isSuccess, + activeRecord?.serviceHostingType ?? DeployedService.serviceHostingType.SELF, + [ + DeployedServiceDetails.serviceDeploymentState.DEPLOYMENT_SUCCESSFUL, + DeployedServiceDetails.serviceDeploymentState.DEPLOYMENT_FAILED, + ] + ); + const getStartServiceDetailsQuery = useServiceDetailsByServiceStatePollingQuery( activeRecord?.serviceId, serviceStateStartQuery.isSuccess, @@ -144,6 +158,9 @@ function MyServices(): React.JSX.Element { getStartServiceDetailsQuery.data?.serviceState, getStopServiceDetailsQuery.data?.serviceState, getRestartServiceDetailsQuery.data?.serviceState, + getRetryDeployServiceDetailsByIdQuery.isError, + redeployFailedDeploymentQuery.error, + getRetryDeployServiceDetailsByIdQuery.data?.serviceDeploymentState, ]); const getPurgeServiceDetailsQuery = usePurgeRequestStatusQuery( @@ -518,6 +535,33 @@ function MyServices(): React.JSX.Element { ), }, + { + key: 'retryDeployment', + label: + record.serviceDeploymentState.toString() === + DeployedService.serviceDeploymentState.DEPLOYMENT_FAILED.toString() ? ( + { + retryDeployment(record); + }} + > + + + ) : ( + <> + ), + }, { key: 'start', label: ( @@ -636,6 +680,13 @@ function MyServices(): React.JSX.Element { return true; }; + const isDisableRetryDeploymentBtn = (record: DeployedService) => { + if (record.serviceDeploymentState === DeployedService.serviceDeploymentState.DEPLOYING) { + return true; + } + return false; + }; + const isDisableStartBtn = (record: DeployedService) => { if ( record.serviceDeploymentState !== DeployedService.serviceDeploymentState.DEPLOYMENT_SUCCESSFUL && @@ -700,6 +751,12 @@ function MyServices(): React.JSX.Element { } }; + const closeRetryDeployResultAlert = () => { + setActiveRecord(undefined); + refreshData(); + setIsRetryDeployRequestSubmitted(false); + }; + const closeStartResultAlert = (isClose: boolean) => { if (isClose) { setActiveRecord(undefined); @@ -942,6 +999,17 @@ function MyServices(): React.JSX.Element { record.serviceDeploymentState = DeployedService.serviceDeploymentState.DESTROYING; } + function retryDeployment(record: DeployedService): void { + setIsRetryDeployRequestSubmitted(true); + setActiveRecord( + record.serviceHostingType === DeployedService.serviceHostingType.SELF + ? (record as DeployedServiceDetails) + : (record as VendorHostedDeployedServiceDetails) + ); + redeployFailedDeploymentQuery.mutate(record.serviceId); + record.serviceDeploymentState = DeployedService.serviceDeploymentState.DEPLOYING; + } + function start(record: DeployedService): void { setIsStartRequestSubmitted(true); setActiveRecord( @@ -1259,6 +1327,16 @@ function MyServices(): React.JSX.Element { return undefined; } + const retryRequest = () => { + if (activeRecord && activeRecord.serviceId.length > 0) { + redeployFailedDeploymentQuery.mutate(activeRecord.serviceId); + } + }; + + if (redeployFailedDeploymentQuery.isPending) { + void getServiceDetailsByIdQuery.refetch(); + } + return (
{isDestroyRequestSubmitted && activeRecord ? ( @@ -1271,6 +1349,20 @@ function MyServices(): React.JSX.Element { closeDestroyResultAlert={closeDestroyResultAlert} /> ) : null} + {isRetryDeployRequestSubmitted && activeRecord ? ( + + ) : null} {isStartRequestSubmitted && activeRecord ? ( { + return ( +
+ {' '} + } + showIcon + closable={true} + type={type} + action={ + <> + {contactServiceDetails !== undefined ? ( + + ) : ( + <> + )} + + + + } + />{' '} +
+ ); +}; diff --git a/src/components/content/order/common/ScaleOrModifySubmitStatusAlert.tsx b/src/components/content/order/common/ScaleOrModifySubmitStatusAlert.tsx index dbadef38..1f77598e 100644 --- a/src/components/content/order/common/ScaleOrModifySubmitStatusAlert.tsx +++ b/src/components/content/order/common/ScaleOrModifySubmitStatusAlert.tsx @@ -14,10 +14,10 @@ import { VendorHostedDeployedServiceDetails, } from '../../../../xpanse-api/generated'; import { convertStringArrayToUnorderedList } from '../../../utils/generateUnorderedList'; -import { OrderSubmitResult } from '../orderStatus/OrderSubmitResult'; import { ProcessingStatus } from '../orderStatus/ProcessingStatus'; import { useServiceDetailsPollingQuery } from '../orderStatus/useServiceDetailsPollingQuery'; import { OperationType } from '../types/OperationType'; +import { ScaleOrModifySubmitResult } from './ScaleOrModifySubmitResult'; function ScaleOrModifySubmitStatusAlert({ isSubmitFailed, @@ -145,7 +145,7 @@ function ScaleOrModifySubmitStatusAlert({ } return ( - (false); const uniqueRequestId = useRef(v4()); const submitDeploymentRequest = useDeployRequestSubmitQuery(); + const redeployFailedDeploymentQuery = useRedeployFailedDeploymentQuery(); const getServiceDetailsByIdQuery = useServiceDetailsPollingQuery( submitDeploymentRequest.data, submitDeploymentRequest.isSuccess, @@ -86,6 +93,20 @@ function OrderSubmit(state: OrderSubmitProps): React.JSX.Element { .concat('&latestVersion=', state.version) .concat('&billingMode=', state.billingMode); + const retryRequest = () => { + if (submitDeploymentRequest.isSuccess && submitDeploymentRequest.data.length > 0) { + redeployFailedDeploymentQuery.mutate(submitDeploymentRequest.data); + } + }; + + if (redeployFailedDeploymentQuery.isPending) { + void getServiceDetailsByIdQuery.refetch(); + } + + const onClose = () => { + navigate(servicesSubPageRoute.concat(state.category)); + }; + return ( <>
@@ -108,9 +129,13 @@ function OrderSubmit(state: OrderSubmitProps): React.JSX.Element { key={uniqueRequestId.current} uuid={submitDeploymentRequest.data} isSubmitFailed={submitDeploymentRequest.error} + isRetrySubmitIsPending={redeployFailedDeploymentQuery.isPending} + isRetrySubmitFailed={redeployFailedDeploymentQuery.error} deployedServiceDetails={getServiceDetailsByIdQuery.data} isPollingError={getServiceDetailsByIdQuery.isError} serviceProviderContactDetails={state.contactServiceDetails} + retryRequest={retryRequest} + onClose={onClose} /> ) : null}
Deploy diff --git a/src/components/content/order/orderStatus/OrderSubmitResult.tsx b/src/components/content/order/orderStatus/OrderSubmitResult.tsx index 62eb9d04..de7ed5f4 100644 --- a/src/components/content/order/orderStatus/OrderSubmitResult.tsx +++ b/src/components/content/order/orderStatus/OrderSubmitResult.tsx @@ -3,9 +3,10 @@ * SPDX-FileCopyrightText: Huawei Inc. */ -import { Alert } from 'antd'; -import React from 'react'; +import { Alert, Button, Row } from 'antd'; +import React, { useEffect } from 'react'; import { StopwatchResult } from 'react-timer-hook'; +import errorAlertStyles from '../../../../styles/error-alert.module.css'; import submitAlertStyles from '../../../../styles/submit-alert.module.css'; import { ServiceProviderContactDetails } from '../../../../xpanse-api/generated'; import { ContactDetailsShowType } from '../../common/ocl/ContactDetailsShowType'; @@ -19,13 +20,23 @@ export const OrderSubmitResult = ({ type, stopWatch, contactServiceDetails, + retryRequest, + onClose, }: { msg: string | React.JSX.Element; uuid: string; type: 'success' | 'error'; stopWatch: StopwatchResult; contactServiceDetails: ServiceProviderContactDetails | undefined; + retryRequest: () => void; + onClose: () => void; }): React.JSX.Element => { + useEffect(() => { + if (!stopWatch.isRunning) { + stopWatch.start(); + } + }, [stopWatch]); + return (
{' '} @@ -34,19 +45,38 @@ export const OrderSubmitResult = ({ description={} showIcon closable={true} + onClose={onClose} type={type} action={ <> + {type === 'error' ? ( + + + + ) : ( + <> + )} {contactServiceDetails !== undefined ? ( - + + + ) : ( <> )} - - + + + } />{' '} diff --git a/src/components/content/order/orderStatus/OrderSubmitStatusAlert.tsx b/src/components/content/order/orderStatus/OrderSubmitStatusAlert.tsx index ee6ef633..0f4a3522 100644 --- a/src/components/content/order/orderStatus/OrderSubmitStatusAlert.tsx +++ b/src/components/content/order/orderStatus/OrderSubmitStatusAlert.tsx @@ -19,20 +19,31 @@ import { ProcessingStatus } from './ProcessingStatus'; function OrderSubmitStatusAlert({ uuid, isSubmitFailed, + isRetrySubmitIsPending, + isRetrySubmitFailed, deployedServiceDetails, serviceProviderContactDetails, isPollingError, + retryRequest, + onClose, }: { uuid: string | undefined; isSubmitFailed: Error | null; + isRetrySubmitIsPending: boolean; + isRetrySubmitFailed: Error | null; deployedServiceDetails: DeployedServiceDetails | undefined; serviceProviderContactDetails: ServiceProviderContactDetails | undefined; isPollingError: boolean; + retryRequest: () => void; + onClose: () => void; }): React.JSX.Element { const stopWatch = useStopwatch({ autoStart: true, }); const msg = useMemo(() => { + if (isRetrySubmitIsPending) { + return 'Retry request submission in-progress'; + } if (deployedServiceDetails) { if ( uuid && @@ -57,6 +68,17 @@ function OrderSubmitStatusAlert({ } else { return getOrderSubmissionFailedDisplay([isSubmitFailed.message]); } + } else if (isRetrySubmitFailed) { + if ( + isRetrySubmitFailed instanceof ApiError && + isRetrySubmitFailed.body && + 'details' in isRetrySubmitFailed.body + ) { + const response: Response = isRetrySubmitFailed.body as Response; + return getOrderSubmissionFailedDisplay(response.details); + } else { + return getOrderSubmissionFailedDisplay([isRetrySubmitFailed.message]); + } } else if (uuid && isPollingError) { return 'Deployment status polling failed. Please visit MyServices page to check the status of the request.'; } else if (uuid) { @@ -65,12 +87,20 @@ function OrderSubmitStatusAlert({ return 'Request submission in-progress'; } return ''; - }, [deployedServiceDetails, uuid, isPollingError, isSubmitFailed]); + }, [deployedServiceDetails, uuid, isPollingError, isSubmitFailed, isRetrySubmitFailed, isRetrySubmitIsPending]); const alertType = useMemo(() => { if (isPollingError || isSubmitFailed) { return 'error'; } + if (isRetrySubmitFailed) { + return 'error'; + } + + if (isRetrySubmitIsPending) { + return 'success'; + } + if (deployedServiceDetails) { if ( deployedServiceDetails.serviceDeploymentState.toString() === @@ -82,7 +112,7 @@ function OrderSubmitStatusAlert({ } } return 'success'; - }, [deployedServiceDetails, isPollingError, isSubmitFailed]); + }, [deployedServiceDetails, isPollingError, isSubmitFailed, isRetrySubmitFailed, isRetrySubmitIsPending]); if (isPollingError || isSubmitFailed) { if (stopWatch.isRunning) { @@ -90,6 +120,10 @@ function OrderSubmitStatusAlert({ } } + if (isRetrySubmitFailed && stopWatch.isRunning) { + stopWatch.pause(); + } + if (deployedServiceDetails) { if ( deployedServiceDetails.serviceDeploymentState.toString() === @@ -105,6 +139,11 @@ function OrderSubmitStatusAlert({ } } + const reDeployService = () => { + stopWatch.reset(); + retryRequest(); + }; + function getOrderSubmissionFailedDisplay(reasons: string[]) { return (
@@ -121,6 +160,8 @@ function OrderSubmitStatusAlert({ type={alertType} stopWatch={stopWatch} contactServiceDetails={alertType !== 'success' ? serviceProviderContactDetails : undefined} + retryRequest={reDeployService} + onClose={onClose} /> ); } diff --git a/src/components/content/order/orderStatus/ProcessingStatus.tsx b/src/components/content/order/orderStatus/ProcessingStatus.tsx index 905de287..4fe5bc71 100644 --- a/src/components/content/order/orderStatus/ProcessingStatus.tsx +++ b/src/components/content/order/orderStatus/ProcessingStatus.tsx @@ -45,6 +45,13 @@ export const ProcessingStatus = ({
{response.resultMessage}
); + } else if (response.serviceDeploymentState === DeployedServiceDetails.serviceDeploymentState.ROLLBACK_FAILED) { + return ( +
+ {'Rollback Failed.'} +
{response.resultMessage}
+
+ ); } } diff --git a/src/components/content/order/retryDeployment/useRedeployFailedDeploymentQuery.ts b/src/components/content/order/retryDeployment/useRedeployFailedDeploymentQuery.ts new file mode 100644 index 00000000..788759c9 --- /dev/null +++ b/src/components/content/order/retryDeployment/useRedeployFailedDeploymentQuery.ts @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Huawei Inc. + */ + +import { useMutation } from '@tanstack/react-query'; +import { ServiceService } from '../../../../xpanse-api/generated'; + +export default function useRedeployFailedDeploymentQuery() { + return useMutation({ + mutationFn: (uuid: string) => { + return ServiceService.redeployFailedDeployment(uuid); + }, + }); +}