From ec8c3ae410ad2b66e3d1f40eab1df9feade136f9 Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 2 Apr 2026 14:06:28 +0200 Subject: [PATCH 1/7] feat: add "deploy and apply immediately" action for managed databases --- .../src/lib/domains-services-data-access.ts | 5 +- .../need-redeploy-flag/need-redeploy-flag.tsx | 54 ++++++++++++++----- .../service-action-toolbar.tsx | 45 +++++++++++++++- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts index afc5f92374e..e5250273db0 100644 --- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts +++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts @@ -716,6 +716,7 @@ type DeployRequest = | { serviceId: string serviceType: DatabaseType + applyImmediately?: boolean } | { serviceId: string @@ -1015,8 +1016,8 @@ export const mutations = { mutation: containerActionsApi.deployContainer.bind(containerActionsApi, serviceId, request), serviceType, })) - .with({ serviceType: 'DATABASE' }, ({ serviceId, serviceType }) => ({ - mutation: databaseActionsApi.deployDatabase.bind(databaseActionsApi, serviceId), + .with({ serviceType: 'DATABASE' }, ({ serviceId, serviceType, applyImmediately = false }) => ({ + mutation: databaseActionsApi.deployDatabase.bind(databaseActionsApi, serviceId, applyImmediately), serviceType, })) .with({ serviceType: 'JOB' }, ({ serviceId, serviceType, forceEvent, request }) => ({ diff --git a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx index e03f6133c79..2d21b3f7a2f 100644 --- a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx +++ b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx @@ -1,7 +1,8 @@ +import { twMerge } from 'libs/shared/util-js/src/lib/custom-tw-merge' import { ServiceDeploymentStatusEnum } from 'qovery-typescript-axios' import { useNavigate, useParams } from 'react-router-dom' import { DEPLOYMENT_LOGS_VERSION_URL, ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes' -import { Banner } from '@qovery/shared/ui' +import { Banner, Button, Icon, Tooltip } from '@qovery/shared/ui' import { useDeployService } from '../hooks/use-deploy-service/use-deploy-service' import { useDeploymentStatus } from '../hooks/use-deployment-status/use-deployment-status' import { useService } from '../hooks/use-service/use-service' @@ -11,6 +12,7 @@ export function NeedRedeployFlag() { const navigate = useNavigate() const { data: service } = useService({ environmentId, serviceId: applicationId || databaseId }) + const { data: serviceDeploymentStatus } = useDeploymentStatus({ environmentId, serviceId: service?.id, @@ -23,6 +25,8 @@ export function NeedRedeployFlag() { if (!serviceDeploymentStatus) return null + const renderRedeployImmediately = service?.serviceType === 'DATABASE' && service.mode === 'MANAGED' + const serviceDeploymentStatusState = serviceDeploymentStatus?.service_deployment_status ?? ServiceDeploymentStatusEnum.NEVER_DEPLOYED @@ -44,19 +48,43 @@ export function NeedRedeployFlag() { return ( - {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.NEVER_DEPLOYED ? ( -

This service is not running

- ) : ( -

- This service needs to be{' '} - {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.OUT_OF_DATE ? 'redeployed' : 'deployed'} to - apply the configuration changes -

- )} +
+ {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.NEVER_DEPLOYED ? ( +

This service is not running

+ ) : ( +

+ This service needs to be{' '} + {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.OUT_OF_DATE ? 'redeployed' : 'deployed'} to + apply the configuration changes +

+ )} +
+ + {renderRedeployImmediately && ( + + + + )} +
+
) } diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index 16bdf74058f..0b7c9f8331e 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -46,6 +46,7 @@ import { DropdownMenu, Icon, Link, + ModalConfirmation, Skeleton, Tooltip, useModal, @@ -138,7 +139,14 @@ function MenuManageDeployment({ const tooltipServiceNeedUpdate = displayYellowColor && tooltipService('Configuration has changed and needs to be applied') - const mutationDeploy = () => deployService({ serviceId: service.id, serviceType: service.serviceType }) + const mutationDeploy = (applyImmediately = false) => { + if (service.serviceType === 'DATABASE') { + deployService({ serviceId: service.id, serviceType: service.serviceType, applyImmediately }) + } else { + deployService({ serviceId: service.id, serviceType: service.serviceType }) + } + } + const mutationTerraformAction = ( action: 'plan' | 'plan_and_apply' | 'destroy' | 'force_unlock' | 'migrate_state' ) => { @@ -382,6 +390,28 @@ function MenuManageDeployment({ }) } + const openImmediatelyRedeployDatabaseModal = () => { + openModalConfirmation({ + mode: EnvironmentModeEnum.PRODUCTION, + title: 'Deploy and apply immediately', + description: ( +
+ + This will redeploy your database and apply changes immediately, without waiting for the next maintenance + window. + + Your database may be unavailable for a few minutes during this process. + To confirm, type "immediately". This action cannot be undone. +
+ ), + confirmationMethod: 'action', + confirmationAction: 'immediately', + action: () => { + console.log('Destroying service with id:', service.id) + }, + }) + } + return ( @@ -461,7 +491,7 @@ function MenuManageDeployment({ {isDeployAvailable(state) && ( } - onSelect={mutationDeploy} + onSelect={() => mutationDeploy(false)} className="relative" color={displayYellowColor ? 'yellow' : 'brand'} > @@ -469,6 +499,17 @@ function MenuManageDeployment({ {tooltipServiceNeedUpdate} )} + {isDeployAvailable(state) && service.serviceType === 'DATABASE' && service.mode === 'MANAGED' && ( + } + onSelect={openImmediatelyRedeployDatabaseModal} + className="relative" + color={displayYellowColor ? 'yellow' : 'brand'} + > + Deploy and apply now + {tooltipService('Apply changes immediately (do not wait for the next maintenance window)')} + + )} {isRedeployAvailable(state) && ( } From 796f15454986bac361950db1db226a00d0933f8c Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 2 Apr 2026 17:06:19 +0200 Subject: [PATCH 2/7] Use 2-choices confirm modal --- .../service-action-toolbar.tsx | 142 ++++++++++++++---- 1 file changed, 110 insertions(+), 32 deletions(-) diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index 0b7c9f8331e..947be681608 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -78,7 +78,7 @@ import { SelectCommitModal } from '../select-commit-modal/select-commit-modal' import { SelectVersionModal } from '../select-version-modal/select-version-modal' import { ServiceAvatar } from '../service-avatar/service-avatar' import { ServiceCloneModal } from '../service-clone-modal/service-clone-modal' -import useServiceRemoveModal from '../service-remove-modal/use-service-remove-modal/use-service-remove-modal' +import { useServiceRemoveModal } from '../service-remove-modal/use-service-remove-modal/use-service-remove-modal' type ActionToolbarVariant = 'default' | 'deployment' @@ -95,6 +95,7 @@ function MenuManageDeployment({ }) { const { openModal, closeModal } = useModal() const { openModalConfirmation } = useModalConfirmation() + const { openServiceRemoveModal } = useServiceRemoveModal() const { data: runningState } = useRunningStatus({ environmentId: environment.id, serviceId: service.id }) const { mutate: deployService } = useDeployService({ @@ -390,26 +391,114 @@ function MenuManageDeployment({ }) } - const openImmediatelyRedeployDatabaseModal = () => { - openModalConfirmation({ - mode: EnvironmentModeEnum.PRODUCTION, - title: 'Deploy and apply immediately', - description: ( -
- - This will redeploy your database and apply changes immediately, without waiting for the next maintenance - window. - - Your database may be unavailable for a few minutes during this process. - To confirm, type "immediately". This action cannot be undone. -
- ), - confirmationMethod: 'action', - confirmationAction: 'immediately', - action: () => { - console.log('Destroying service with id:', service.id) - }, + const openRedeployDatabaseModal = () => { + openServiceRemoveModal({ + title: `Deploy database and apply changes`, + description: 'Choose how to deploy your changes', + entities: [], + actions: [ + { + id: 'next', + title: 'Next maintenance window', + description: ( +
+
+ Redeploy your database and apply changes during the next maintenance window. +
+ + {/* + Stop and remove the services but keep all Qovery configuration, data and settings. +
+ You can easily reinstall or redeploy later with the same configuration. +
+
+ What's deleted: +
    +
  • All services data
  • +
+
+
+ What's kept: +
    +
  • Qovery configuration
  • +
  • Environment variables
  • +
  • Network settings
  • +
+
*/} +
+ ), + icon: 'calendar-clock', + color: 'brand', + callback: async () => { + try { + mutationDeploy(false) + closeModal() + } catch (error) { + console.error(error) + } + }, + }, + { + id: 'immediately', + title: 'Immediately', + description: ( +
+
+ + Redeploy your database and apply changes immediately, without waiting for the next maintenance window. + + Your database may be unavailable for a few minutes during this process. +
+ {/* + Permanently remove the services and all associated data. +
+ This action cannot be undone. +
+
+ What's deleted: +
    +
  • All services data
  • +
  • Qovery configuration
  • +
  • Logs and history
  • +
  • Environment variables
  • +
  • Network settings
  • +
+
*/} +
+ ), + icon: 'timer', + color: 'red', + callback: async () => { + try { + mutationDeploy(true) + closeModal() + } catch (error) { + console.error(error) + } + }, + }, + ], + isDelete: true, }) + // openModalConfirmation({ + // mode: EnvironmentModeEnum.PRODUCTION, + // title: 'Deploy and apply immediately', + // description: ( + //
+ // + // This will redeploy your database and apply changes immediately, without waiting for the next maintenance + // window. + // + // Your database may be unavailable for a few minutes during this process. + // To confirm, type "immediately". This action cannot be undone. + //
+ // ), + // confirmationMethod: 'action', + // confirmationAction: 'immediately', + // action: () => { + // console.log('Destroying service with id:', service.id) + // }, + // }) } return ( @@ -491,7 +580,7 @@ function MenuManageDeployment({ {isDeployAvailable(state) && ( } - onSelect={() => mutationDeploy(false)} + onSelect={openRedeployDatabaseModal} className="relative" color={displayYellowColor ? 'yellow' : 'brand'} > @@ -499,17 +588,6 @@ function MenuManageDeployment({ {tooltipServiceNeedUpdate} )} - {isDeployAvailable(state) && service.serviceType === 'DATABASE' && service.mode === 'MANAGED' && ( - } - onSelect={openImmediatelyRedeployDatabaseModal} - className="relative" - color={displayYellowColor ? 'yellow' : 'brand'} - > - Deploy and apply now - {tooltipService('Apply changes immediately (do not wait for the next maintenance window)')} - - )} {isRedeployAvailable(state) && ( } From 3bdde43e60bb6238443296141d360d8bcbe3f073 Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 2 Apr 2026 18:39:40 +0200 Subject: [PATCH 3/7] feat(deployment): add DatabaseDeployModal and implement 2 entry points --- libs/domains/services/feature/src/index.ts | 1 + .../database-deploy-modal.tsx | 108 +++++++++++++++ .../use-database-deploy-modal.tsx | 59 +++++++++ .../need-redeploy-flag/need-redeploy-flag.tsx | 124 +++++++++++------- .../service-action-toolbar.tsx | 91 ++++--------- 5 files changed, 269 insertions(+), 114 deletions(-) create mode 100644 libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx create mode 100644 libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx diff --git a/libs/domains/services/feature/src/index.ts b/libs/domains/services/feature/src/index.ts index 84ea1cbd87f..9ee5b72bb7b 100644 --- a/libs/domains/services/feature/src/index.ts +++ b/libs/domains/services/feature/src/index.ts @@ -65,3 +65,4 @@ export * from './lib/service-access-modal/service-access-modal' export * from './lib/service-deployment-list/service-deployment-list' export * from './lib/pod-details/pod-details' export * from './lib/force-unlock-modal/force-unlock-modal' +export * from './lib/database-deploy-modal/database-deploy-modal' diff --git a/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx new file mode 100644 index 00000000000..a8e0a38819e --- /dev/null +++ b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx @@ -0,0 +1,108 @@ +import { type PropsWithChildren, type ReactNode } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { Button, Icon, InputTextSmall, RadioGroup, useModal } from '@qovery/shared/ui' +import { twMerge } from '@qovery/shared/util-js' +import { type ActionItem, type DatabaseDeployModalData } from './use-database-deploy-modal/use-database-deploy-modal' + +export interface DatabaseDeployModalProps extends PropsWithChildren { + title: string + description?: ReactNode + actions: ActionItem[] + name?: string + entities?: ReactNode[] + submitButtonText?: string +} + +export function DatabaseDeployModal({ + title, + description, + actions, + children, + entities, + submitButtonText, +}: DatabaseDeployModalProps) { + const { + handleSubmit, + control, + watch, + formState: { isValid }, + } = useForm({ + mode: 'onChange', + defaultValues: { + name: '', + action: actions[0]?.id, + }, + }) + const { closeModal } = useModal() + + const selectedActionId = watch('action') + const selectedAction = actions.find((action) => action.id === selectedActionId) + + const onSubmit = handleSubmit((data) => { + if (data) { + closeModal() + selectedAction?.callback?.(data) + } + }) + + return ( +
+

{title}

+
+ {description &&
{description}
} + {entities &&
{entities.map((entity) => entity)}
} +
+ +
+
+
+ ( + + {actions.map((action) => ( + + ))} + + )} + /> +
+
+ + {children} + +
+ + +
+
+
+ ) +} + +export default DatabaseDeployModal diff --git a/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx b/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx new file mode 100644 index 00000000000..fd30a9d4819 --- /dev/null +++ b/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx @@ -0,0 +1,59 @@ +import { type IconName } from '@fortawesome/fontawesome-common-types' +import { type EnvironmentModeEnum } from 'qovery-typescript-axios' +import { type ReactNode, useEffect, useState } from 'react' +import { useModal } from '@qovery/shared/ui' +import { DatabaseDeployModal } from '../database-deploy-modal' + +export type DatabaseDeployModalData = { + action: string + name: string +} + +export interface ActionItem { + id: string // Also used as the text the user has to type to confirm + title: string + callback: (data: DatabaseDeployModalData) => void + description?: ReactNode + icon?: IconName + color?: 'brand' | 'red' | 'yellow' | 'green' | 'neutral' +} + +export interface UseDatabaseDeployModalProps { + title: string + actions: ActionItem[] + entities?: ReactNode[] + description?: ReactNode + name?: string + submitButtonText?: string + mode?: keyof typeof EnvironmentModeEnum | string | undefined + warning?: ReactNode +} + +export function useDatabaseDeployModal() { + const [databaseDeployModal, openDatabaseDeployModal] = useState() + const { openModal } = useModal() + + useEffect(() => { + if (databaseDeployModal) { + openModal({ + content: ( + + ), + options: { + width: 740, + }, + }) + } + }, [databaseDeployModal, openModal]) + + return { openDatabaseDeployModal } +} + +export default useDatabaseDeployModal diff --git a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx index 2d21b3f7a2f..d473468840a 100644 --- a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx +++ b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx @@ -1,8 +1,8 @@ -import { twMerge } from 'libs/shared/util-js/src/lib/custom-tw-merge' import { ServiceDeploymentStatusEnum } from 'qovery-typescript-axios' import { useNavigate, useParams } from 'react-router-dom' import { DEPLOYMENT_LOGS_VERSION_URL, ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes' -import { Banner, Button, Icon, Tooltip } from '@qovery/shared/ui' +import { Banner, useModal } from '@qovery/shared/ui' +import { useDatabaseDeployModal } from '../database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal' import { useDeployService } from '../hooks/use-deploy-service/use-deploy-service' import { useDeploymentStatus } from '../hooks/use-deployment-status/use-deployment-status' import { useService } from '../hooks/use-service/use-service' @@ -10,6 +10,8 @@ import { useService } from '../hooks/use-service/use-service' export function NeedRedeployFlag() { const { organizationId = '', projectId = '', environmentId = '', applicationId = '', databaseId = '' } = useParams() const navigate = useNavigate() + const { closeModal } = useModal() + const { openDatabaseDeployModal } = useDatabaseDeployModal() const { data: service } = useService({ environmentId, serviceId: applicationId || databaseId }) @@ -25,8 +27,6 @@ export function NeedRedeployFlag() { if (!serviceDeploymentStatus) return null - const renderRedeployImmediately = service?.serviceType === 'DATABASE' && service.mode === 'MANAGED' - const serviceDeploymentStatusState = serviceDeploymentStatus?.service_deployment_status ?? ServiceDeploymentStatusEnum.NEVER_DEPLOYED @@ -35,9 +35,9 @@ export function NeedRedeployFlag() { const buttonLabel = (serviceDeploymentStatusState === ServiceDeploymentStatusEnum.OUT_OF_DATE ? 'Redeploy' : 'Deploy') + ' now' - const mutationDeployService = () => { + const mutationDeployService = (applyImmediately = false) => { if (service) { - deployService({ serviceId: service.id, serviceType: service.serviceType }) + deployService({ serviceId: service.id, serviceType: service.serviceType, applyImmediately }) navigate( ENVIRONMENT_LOGS_URL(organizationId, projectId, environmentId) + DEPLOYMENT_LOGS_VERSION_URL(service.id, 'latest') @@ -45,46 +45,80 @@ export function NeedRedeployFlag() { } } + const handleDatabaseDeployModal = () => { + openDatabaseDeployModal({ + title: `Deploy database`, + description: 'Choose when to deploy and apply your changes', + entities: [], + submitButtonText: 'Confirm', + actions: [ + { + id: 'next', + title: 'Next maintenance window', + description: ( +
+ Redeploy your database and apply changes during the next maintenance window. +
+ ), + icon: 'calendar-clock', + color: 'brand', + callback: async () => { + try { + mutationDeployService(false) + closeModal() + } catch (error) { + console.error(error) + } + }, + }, + { + id: 'immediately', + title: 'Immediately', + description: ( +
+
+ Redeploy your database and apply changes immediately. +

+ Be careful, + your database may be unavailable for a few minutes during this process. +

+
+
+ ), + icon: 'timer', + color: 'red', + callback: async () => { + try { + mutationDeployService(true) + closeModal() + } catch (error) { + console.error(error) + } + }, + }, + ], + }) + } + + const handleDeploy = () => { + if (service?.serviceType === 'DATABASE' && service.mode === 'MANAGED') { + handleDatabaseDeployModal() + } else { + mutationDeployService() + } + } + return ( - -
- {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.NEVER_DEPLOYED ? ( -

This service is not running

- ) : ( -

- This service needs to be{' '} - {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.OUT_OF_DATE ? 'redeployed' : 'deployed'} to - apply the configuration changes -

- )} -
- - {renderRedeployImmediately && ( - - - - )} -
-
+ + {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.NEVER_DEPLOYED ? ( +

This service is not running

+ ) : ( +

+ This service needs to be{' '} + {serviceDeploymentStatusState === ServiceDeploymentStatusEnum.OUT_OF_DATE ? 'redeployed' : 'deployed'} to + apply the configuration changes +

+ )}
) } diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index 947be681608..68f1d4093ee 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -46,7 +46,6 @@ import { DropdownMenu, Icon, Link, - ModalConfirmation, Skeleton, Tooltip, useModal, @@ -63,6 +62,7 @@ import { urlCodeEditor, } from '@qovery/shared/util-js' import { ConfirmationCancelLifecycleModal } from '../confirmation-cancel-lifecycle-modal/confirmation-cancel-lifecycle-modal' +import useDatabaseDeployModal from '../database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal' import { ForceUnlockModal } from '../force-unlock-modal/force-unlock-modal' import { useCancelDeploymentService } from '../hooks/use-cancel-deployment-service/use-cancel-deployment-service' import { useDeleteService } from '../hooks/use-delete-service/use-delete-service' @@ -95,7 +95,7 @@ function MenuManageDeployment({ }) { const { openModal, closeModal } = useModal() const { openModalConfirmation } = useModalConfirmation() - const { openServiceRemoveModal } = useServiceRemoveModal() + const { openDatabaseDeployModal } = useDatabaseDeployModal() const { data: runningState } = useRunningStatus({ environmentId: environment.id, serviceId: service.id }) const { mutate: deployService } = useDeployService({ @@ -391,40 +391,19 @@ function MenuManageDeployment({ }) } - const openRedeployDatabaseModal = () => { - openServiceRemoveModal({ - title: `Deploy database and apply changes`, - description: 'Choose how to deploy your changes', + const handleDatabaseDeployModal = () => { + openDatabaseDeployModal({ + title: `Deploy database`, + description: 'Choose when to deploy and apply your changes', entities: [], + submitButtonText: 'Confirm', actions: [ { id: 'next', title: 'Next maintenance window', description: (
-
- Redeploy your database and apply changes during the next maintenance window. -
- - {/* - Stop and remove the services but keep all Qovery configuration, data and settings. -
- You can easily reinstall or redeploy later with the same configuration. -
-
- What's deleted: -
    -
  • All services data
  • -
-
-
- What's kept: -
    -
  • Qovery configuration
  • -
  • Environment variables
  • -
  • Network settings
  • -
-
*/} + Redeploy your database and apply changes during the next maintenance window.
), icon: 'calendar-clock', @@ -444,26 +423,12 @@ function MenuManageDeployment({ description: (
- - Redeploy your database and apply changes immediately, without waiting for the next maintenance window. - - Your database may be unavailable for a few minutes during this process. + Redeploy your database and apply changes immediately. +

+ Be careful, + your database may be unavailable for a few minutes during this process. +

- {/* - Permanently remove the services and all associated data. -
- This action cannot be undone. -
-
- What's deleted: -
    -
  • All services data
  • -
  • Qovery configuration
  • -
  • Logs and history
  • -
  • Environment variables
  • -
  • Network settings
  • -
-
*/}
), icon: 'timer', @@ -478,27 +443,15 @@ function MenuManageDeployment({ }, }, ], - isDelete: true, }) - // openModalConfirmation({ - // mode: EnvironmentModeEnum.PRODUCTION, - // title: 'Deploy and apply immediately', - // description: ( - //
- // - // This will redeploy your database and apply changes immediately, without waiting for the next maintenance - // window. - // - // Your database may be unavailable for a few minutes during this process. - // To confirm, type "immediately". This action cannot be undone. - //
- // ), - // confirmationMethod: 'action', - // confirmationAction: 'immediately', - // action: () => { - // console.log('Destroying service with id:', service.id) - // }, - // }) + } + + const handleDeploy = () => { + if (service.serviceType === 'DATABASE' && service.mode === 'MANAGED') { + handleDatabaseDeployModal() + } else { + mutationDeploy() + } } return ( @@ -580,7 +533,7 @@ function MenuManageDeployment({ {isDeployAvailable(state) && ( } - onSelect={openRedeployDatabaseModal} + onSelect={handleDeploy} className="relative" color={displayYellowColor ? 'yellow' : 'brand'} > From 3d3d4f4cb128b37e8b3554328a4c5b8b2c3e1583 Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 2 Apr 2026 18:57:24 +0200 Subject: [PATCH 4/7] Remove useless condition --- .../src/lib/need-redeploy-flag/need-redeploy-flag.tsx | 2 +- .../lib/service-action-toolbar/service-action-toolbar.tsx | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx index d473468840a..fa527d4633b 100644 --- a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx +++ b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx @@ -104,7 +104,7 @@ export function NeedRedeployFlag() { if (service?.serviceType === 'DATABASE' && service.mode === 'MANAGED') { handleDatabaseDeployModal() } else { - mutationDeployService() + mutationDeployService(false) } } diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index 68f1d4093ee..b1e7fa39422 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -141,11 +141,7 @@ function MenuManageDeployment({ displayYellowColor && tooltipService('Configuration has changed and needs to be applied') const mutationDeploy = (applyImmediately = false) => { - if (service.serviceType === 'DATABASE') { - deployService({ serviceId: service.id, serviceType: service.serviceType, applyImmediately }) - } else { - deployService({ serviceId: service.id, serviceType: service.serviceType }) - } + deployService({ serviceId: service.id, serviceType: service.serviceType, applyImmediately }) } const mutationTerraformAction = ( @@ -450,7 +446,7 @@ function MenuManageDeployment({ if (service.serviceType === 'DATABASE' && service.mode === 'MANAGED') { handleDatabaseDeployModal() } else { - mutationDeploy() + mutationDeploy(false) } } From 17632950ff7ab9795be1a8d812a34c1d22e22183 Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 2 Apr 2026 19:45:07 +0200 Subject: [PATCH 5/7] Post-review fixes --- .../lib/database-deploy-modal/database-deploy-modal.tsx | 6 +++--- .../src/lib/need-redeploy-flag/need-redeploy-flag.tsx | 8 +++----- .../lib/service-action-toolbar/service-action-toolbar.tsx | 8 +++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx index a8e0a38819e..b748d0de543 100644 --- a/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx +++ b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx @@ -1,6 +1,6 @@ import { type PropsWithChildren, type ReactNode } from 'react' import { Controller, useForm } from 'react-hook-form' -import { Button, Icon, InputTextSmall, RadioGroup, useModal } from '@qovery/shared/ui' +import { Button, Icon, RadioGroup, useModal } from '@qovery/shared/ui' import { twMerge } from '@qovery/shared/util-js' import { type ActionItem, type DatabaseDeployModalData } from './use-database-deploy-modal/use-database-deploy-modal' @@ -76,7 +76,7 @@ export function DatabaseDeployModal({ )} {action.title} - {action.description} +
{action.description}
))} @@ -94,7 +94,7 @@ export function DatabaseDeployModal({ - + ) } diff --git a/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx b/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx deleted file mode 100644 index fd30a9d4819..00000000000 --- a/libs/domains/services/feature/src/lib/database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { type IconName } from '@fortawesome/fontawesome-common-types' -import { type EnvironmentModeEnum } from 'qovery-typescript-axios' -import { type ReactNode, useEffect, useState } from 'react' -import { useModal } from '@qovery/shared/ui' -import { DatabaseDeployModal } from '../database-deploy-modal' - -export type DatabaseDeployModalData = { - action: string - name: string -} - -export interface ActionItem { - id: string // Also used as the text the user has to type to confirm - title: string - callback: (data: DatabaseDeployModalData) => void - description?: ReactNode - icon?: IconName - color?: 'brand' | 'red' | 'yellow' | 'green' | 'neutral' -} - -export interface UseDatabaseDeployModalProps { - title: string - actions: ActionItem[] - entities?: ReactNode[] - description?: ReactNode - name?: string - submitButtonText?: string - mode?: keyof typeof EnvironmentModeEnum | string | undefined - warning?: ReactNode -} - -export function useDatabaseDeployModal() { - const [databaseDeployModal, openDatabaseDeployModal] = useState() - const { openModal } = useModal() - - useEffect(() => { - if (databaseDeployModal) { - openModal({ - content: ( - - ), - options: { - width: 740, - }, - }) - } - }, [databaseDeployModal, openModal]) - - return { openDatabaseDeployModal } -} - -export default useDatabaseDeployModal diff --git a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx index c295711c064..07e7d7ba8bd 100644 --- a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx +++ b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx @@ -2,7 +2,7 @@ import { ServiceDeploymentStatusEnum } from 'qovery-typescript-axios' import { useNavigate, useParams } from 'react-router-dom' import { DEPLOYMENT_LOGS_VERSION_URL, ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes' import { Banner, useModal } from '@qovery/shared/ui' -import { useDatabaseDeployModal } from '../database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal' +import DatabaseDeployModal from '../database-deploy-modal/database-deploy-modal' import { useDeployService } from '../hooks/use-deploy-service/use-deploy-service' import { useDeploymentStatus } from '../hooks/use-deployment-status/use-deployment-status' import { useService } from '../hooks/use-service/use-service' @@ -10,8 +10,7 @@ import { useService } from '../hooks/use-service/use-service' export function NeedRedeployFlag() { const { organizationId = '', projectId = '', environmentId = '', applicationId = '', databaseId = '' } = useParams() const navigate = useNavigate() - const { closeModal } = useModal() - const { openDatabaseDeployModal } = useDatabaseDeployModal() + const { openModal } = useModal() const { data: service } = useService({ environmentId, serviceId: applicationId || databaseId }) @@ -46,55 +45,61 @@ export function NeedRedeployFlag() { } const handleDatabaseDeployModal = () => { - openDatabaseDeployModal({ - title: `Deploy database`, - description: 'Choose when to deploy and apply your changes', - entities: [], - submitButtonText: 'Confirm', - actions: [ - { - id: 'next', - title: 'Next maintenance window', - description: ( -
- Redeploy your database and apply changes during the next maintenance window. -
- ), - icon: 'calendar-clock', - color: 'brand', - callback: () => { - try { - mutationDeployService(false) - } catch (error) { - console.error(error) - } - }, - }, - { - id: 'immediately', - title: 'Immediately', - description: ( -
-
- Redeploy your database and apply changes immediately. -

- Be careful, - your database may be unavailable for a few minutes during this process. -

-
-
- ), - icon: 'timer', - color: 'brand', - callback: () => { - try { - mutationDeployService(true) - } catch (error) { - console.error(error) - } - }, - }, - ], + openModal({ + content: ( + + Redeploy your database and apply changes during the next maintenance window. + + ), + icon: 'calendar-clock', + color: 'brand', + callback: () => { + try { + mutationDeployService(false) + } catch (error) { + console.error(error) + } + }, + }, + { + id: 'immediately', + title: 'Immediately', + description: ( +
+
+ Redeploy your database and apply changes immediately. +

+ Be careful, + your database may be unavailable for a few minutes during this process. +

+
+
+ ), + icon: 'timer', + color: 'brand', + callback: () => { + try { + mutationDeployService(true) + } catch (error) { + console.error(error) + } + }, + }, + ]} + submitButtonText="Confirm" + /> + ), + options: { + width: 740, + }, }) } diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index fa387bccc2e..e3b3787143f 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -62,7 +62,7 @@ import { urlCodeEditor, } from '@qovery/shared/util-js' import { ConfirmationCancelLifecycleModal } from '../confirmation-cancel-lifecycle-modal/confirmation-cancel-lifecycle-modal' -import useDatabaseDeployModal from '../database-deploy-modal/use-database-deploy-modal/use-database-deploy-modal' +import { DatabaseDeployModal } from '../database-deploy-modal/database-deploy-modal' import { ForceUnlockModal } from '../force-unlock-modal/force-unlock-modal' import { useCancelDeploymentService } from '../hooks/use-cancel-deployment-service/use-cancel-deployment-service' import { useDeleteService } from '../hooks/use-delete-service/use-delete-service' @@ -95,7 +95,6 @@ function MenuManageDeployment({ }) { const { openModal, closeModal } = useModal() const { openModalConfirmation } = useModalConfirmation() - const { openDatabaseDeployModal } = useDatabaseDeployModal() const { data: runningState } = useRunningStatus({ environmentId: environment.id, serviceId: service.id }) const { mutate: deployService } = useDeployService({ @@ -388,55 +387,61 @@ function MenuManageDeployment({ } const handleDatabaseDeployModal = () => { - openDatabaseDeployModal({ - title: `Deploy database`, - description: 'Choose when to deploy and apply your changes', - entities: [], - submitButtonText: 'Confirm', - actions: [ - { - id: 'next', - title: 'Next maintenance window', - description: ( -
- Redeploy your database and apply changes during the next maintenance window. -
- ), - icon: 'calendar-clock', - color: 'brand', - callback: () => { - try { - mutationDeploy(false) - } catch (error) { - console.error(error) - } - }, - }, - { - id: 'immediately', - title: 'Immediately', - description: ( -
-
- Redeploy your database and apply changes immediately. -

- Be careful, - your database may be unavailable for a few minutes during this process. -

-
-
- ), - icon: 'timer', - color: 'brand', - callback: () => { - try { - mutationDeploy(true) - } catch (error) { - console.error(error) - } - }, - }, - ], + openModal({ + content: ( + + Redeploy your database and apply changes during the next maintenance window. + + ), + icon: 'calendar-clock', + color: 'brand', + callback: () => { + try { + mutationDeploy(false) + } catch (error) { + console.error(error) + } + }, + }, + { + id: 'immediately', + title: 'Immediately', + description: ( +
+
+ Redeploy your database and apply changes immediately. +

+ Be careful, + your database may be unavailable for a few minutes during this process. +

+
+
+ ), + icon: 'timer', + color: 'brand', + callback: () => { + try { + mutationDeploy(true) + } catch (error) { + console.error(error) + } + }, + }, + ]} + submitButtonText="Confirm" + /> + ), + options: { + width: 740, + }, }) } From 19a6c84569ba91bdcbbeb1a5916a920501a1cd4f Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Mon, 27 Apr 2026 15:47:41 +0200 Subject: [PATCH 7/7] Remove useless component's prop --- .../lib/database-deploy-modal/database-deploy-modal.tsx | 8 +------- .../src/lib/need-redeploy-flag/need-redeploy-flag.tsx | 2 -- .../lib/service-action-toolbar/service-action-toolbar.tsx | 2 -- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx index a982a017c96..a1a55c5b446 100644 --- a/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx +++ b/libs/domains/services/feature/src/lib/database-deploy-modal/database-deploy-modal.tsx @@ -16,7 +16,6 @@ interface ActionItem { callback: (data: DatabaseDeployModalData) => void description?: ReactNode icon?: IconName - color?: 'brand' | 'red' | 'yellow' | 'green' | 'neutral' } export interface DatabaseDeployModalProps extends PropsWithChildren { @@ -107,12 +106,7 @@ export function DatabaseDeployModal({ - diff --git a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx index 07e7d7ba8bd..eda13091490 100644 --- a/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx +++ b/libs/domains/services/feature/src/lib/need-redeploy-flag/need-redeploy-flag.tsx @@ -60,7 +60,6 @@ export function NeedRedeployFlag() { ), icon: 'calendar-clock', - color: 'brand', callback: () => { try { mutationDeployService(false) @@ -84,7 +83,6 @@ export function NeedRedeployFlag() { ), icon: 'timer', - color: 'brand', callback: () => { try { mutationDeployService(true) diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx index e3b3787143f..e81bb895b30 100644 --- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx +++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx @@ -402,7 +402,6 @@ function MenuManageDeployment({ ), icon: 'calendar-clock', - color: 'brand', callback: () => { try { mutationDeploy(false) @@ -426,7 +425,6 @@ function MenuManageDeployment({ ), icon: 'timer', - color: 'brand', callback: () => { try { mutationDeploy(true)