From 052ac0e2925ba6fc0a14ff43d0bd9159d0e34606 Mon Sep 17 00:00:00 2001 From: Guillaume Da Silva Date: Fri, 30 Jan 2026 12:01:23 +0100 Subject: [PATCH 1/6] feat: add apply immediately checkbox for managed databases Added checkbox option to apply database changes immediately instead of waiting for maintenance window. Changes: - Created ApplyImmediatelyCheckbox component with integrated warning messages - Integrated checkbox in Settings > General (version changes) - Integrated checkbox in Settings > Resources (instance type and storage changes) - Single checkbox appears when any managed database setting is changed - Warning message adapts dynamically when checkbox is checked - Added apply_immediately field to form payloads UX: Single warning callout that expands with additional warning when "apply immediately" is checked. --- .../page-settings-general-feature.tsx | 1 + .../page-settings-resources-feature.tsx | 6 +- .../page-settings-general.tsx | 19 +----- .../page-settings-resources.tsx | 8 ++- libs/shared/console-shared/src/index.ts | 1 + .../apply-immediately-checkbox.spec.tsx | 31 +++++++++ .../apply-immediately-checkbox.tsx | 65 +++++++++++++++++++ .../database-settings-resources.tsx | 19 +----- .../setting-resources-instance-types.tsx | 55 +++++----------- 9 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx create mode 100644 libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx diff --git a/libs/pages/database/src/lib/feature/page-settings-general-feature/page-settings-general-feature.tsx b/libs/pages/database/src/lib/feature/page-settings-general-feature/page-settings-general-feature.tsx index 77c8a9aba2b..2ab4d58a2ce 100644 --- a/libs/pages/database/src/lib/feature/page-settings-general-feature/page-settings-general-feature.tsx +++ b/libs/pages/database/src/lib/feature/page-settings-general-feature/page-settings-general-feature.tsx @@ -41,6 +41,7 @@ export function PageSettingsGeneralFeature() { accessibility: database?.accessibility, annotations_groups: database?.annotations_groups?.map((group) => group.id), labels_groups: database?.labels_groups?.map((group) => group.id), + apply_immediately: false, }, }) diff --git a/libs/pages/database/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx b/libs/pages/database/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx index cb3326f1bd0..59acb368a69 100644 --- a/libs/pages/database/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx +++ b/libs/pages/database/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx @@ -14,7 +14,10 @@ export const handleSubmit = (data: FieldValues, database: Database) => { cloneDatabase.storage = Number(data['storage']) cloneDatabase.instance_type = data['instance_type'] - return cloneDatabase + return { + ...cloneDatabase, + apply_immediately: data['apply_immediately'], + } } export function PageSettingsResourcesFeature() { @@ -36,6 +39,7 @@ export function PageSettingsResourcesFeature() { storage: database?.storage, cpu: database?.cpu || 10, instance_type: database?.instance_type, + apply_immediately: false, }, }) diff --git a/libs/pages/database/src/lib/ui/page-settings-general/page-settings-general.tsx b/libs/pages/database/src/lib/ui/page-settings-general/page-settings-general.tsx index ff19ba8f84c..1e5b9e86923 100644 --- a/libs/pages/database/src/lib/ui/page-settings-general/page-settings-general.tsx +++ b/libs/pages/database/src/lib/ui/page-settings-general/page-settings-general.tsx @@ -5,7 +5,7 @@ import { match } from 'ts-pattern' import { AnnotationSetting, LabelSetting } from '@qovery/domains/organizations/feature' import { type Database } from '@qovery/domains/services/data-access' import { GeneralSetting } from '@qovery/domains/services/feature' -import { SettingsHeading } from '@qovery/shared/console-shared' +import { ApplyImmediatelyCheckbox, SettingsHeading } from '@qovery/shared/console-shared' import { type Value } from '@qovery/shared/interfaces' import { Button, @@ -150,22 +150,7 @@ export function PageSettingsGeneral({ )} {databaseMode === DatabaseModeEnum.MANAGED && formState.dirtyFields['version'] && ( - - - - - - Once triggered, the update will be managed by your cloud provider and applied during the - configured maintenance window. Moreover, the operation might cause a service interruption.{' '} - - Have a look at the documentation first - - - + )} )} diff --git a/libs/pages/database/src/lib/ui/page-settings-resources/page-settings-resources.tsx b/libs/pages/database/src/lib/ui/page-settings-resources/page-settings-resources.tsx index cc293380da0..c0501ee0cdb 100644 --- a/libs/pages/database/src/lib/ui/page-settings-resources/page-settings-resources.tsx +++ b/libs/pages/database/src/lib/ui/page-settings-resources/page-settings-resources.tsx @@ -2,8 +2,7 @@ import { DatabaseModeEnum } from 'qovery-typescript-axios' import { type FormEventHandler } from 'react' import { useFormContext } from 'react-hook-form' import { type Database } from '@qovery/domains/services/data-access' -import { DatabaseSettingsResources } from '@qovery/shared/console-shared' -import { SettingsHeading } from '@qovery/shared/console-shared' +import { ApplyImmediatelyCheckbox, DatabaseSettingsResources, SettingsHeading } from '@qovery/shared/console-shared' import { Button, Callout, Heading, Icon, Section } from '@qovery/shared/ui' export interface PageSettingsResourcesProps { @@ -55,6 +54,11 @@ export function PageSettingsResources(props: PageSettingsResourcesProps) { displayStorageWarning={displayStorageWarning} isSetting /> + {(displayInstanceTypesWarning || displayStorageWarning) && ( +
+ +
+ )}
diff --git a/libs/shared/console-shared/src/index.ts b/libs/shared/console-shared/src/index.ts index 3ce58cb8069..abd3d85a62c 100644 --- a/libs/shared/console-shared/src/index.ts +++ b/libs/shared/console-shared/src/index.ts @@ -29,3 +29,4 @@ export * from './lib/application-settings/ui/application-settings-resources/appl export * from './lib/application-settings/ui/application-settings-healthchecks/application-settings-healthchecks' export * from './lib/application-settings/utils/probe-formatted' export * from './lib/settings-heading/settings-heading' +export * from './lib/apply-immediately-checkbox/apply-immediately-checkbox' diff --git a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx new file mode 100644 index 00000000000..9ef21a5639c --- /dev/null +++ b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx @@ -0,0 +1,31 @@ +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { ApplyImmediatelyCheckbox } from './apply-immediately-checkbox' + +describe('ApplyImmediatelyCheckbox', () => { + it('should render successfully', () => { + const { baseElement } = renderWithProviders() + expect(baseElement).toBeTruthy() + }) + + it('should display warning when checkbox is checked', async () => { + const { userEvent } = renderWithProviders() + + const checkbox = screen.getByRole('checkbox') + await userEvent.click(checkbox) + + expect(screen.getByTestId('apply-immediately-warning')).toBeInTheDocument() + }) + + it('should not display warning when checkbox is unchecked', () => { + renderWithProviders() + + expect(screen.queryByTestId('apply-immediately-warning')).not.toBeInTheDocument() + }) + + it('should be disabled when disabled prop is true', () => { + renderWithProviders() + + const checkbox = screen.getByRole('checkbox') + expect(checkbox).toBeDisabled() + }) +}) diff --git a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx new file mode 100644 index 00000000000..ee2d81721e2 --- /dev/null +++ b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx @@ -0,0 +1,65 @@ +import { Controller, useFormContext } from 'react-hook-form' +import { Callout, Checkbox, ExternalLink, Icon } from '@qovery/shared/ui' + +export interface ApplyImmediatelyCheckboxProps { + disabled?: boolean +} + +export function ApplyImmediatelyCheckbox({ disabled = false }: ApplyImmediatelyCheckboxProps) { + const { control, watch } = useFormContext() + + const applyImmediately = watch('apply_immediately') + + return ( +
+ ( + + )} + /> + + + + + + +
+
+ Once triggered, the update will be managed by your cloud provider and applied during the configured + maintenance window. Moreover, the operation might cause a service interruption.{' '} + + Have a look at the documentation first + +
+ {applyImmediately && ( +
+ Warning: Applying changes immediately may cause a brief service + interruption. The operation will be performed outside of your configured maintenance window. +
+ )} +
+
+
+
+ ) +} + +export default ApplyImmediatelyCheckbox diff --git a/libs/shared/console-shared/src/lib/database-settings-resources/ui/database-settings-resources/database-settings-resources.tsx b/libs/shared/console-shared/src/lib/database-settings-resources/ui/database-settings-resources/database-settings-resources.tsx index f04e963b0cb..a73637cb82f 100644 --- a/libs/shared/console-shared/src/lib/database-settings-resources/ui/database-settings-resources/database-settings-resources.tsx +++ b/libs/shared/console-shared/src/lib/database-settings-resources/ui/database-settings-resources/database-settings-resources.tsx @@ -5,7 +5,7 @@ import { match } from 'ts-pattern' import { useEnvironment } from '@qovery/domains/environments/feature' import { type Database } from '@qovery/domains/services/data-access' import { CLUSTER_SETTINGS_RESOURCES_URL, CLUSTER_SETTINGS_URL, CLUSTER_URL } from '@qovery/shared/routes' -import { Callout, ExternalLink, Icon, InputText, Link, inputSizeUnitRules } from '@qovery/shared/ui' +import { InputText, Link, inputSizeUnitRules } from '@qovery/shared/ui' import SettingsResourcesInstanceTypesFeature from '../../feature/settings-resources-instance-types-feature/setting-resources-instance-types-feature' export interface DatabaseSettingsResourcesProps { @@ -176,23 +176,6 @@ export function DatabaseSettingsResources({ /> )} /> - {displayStorageWarning && ( - - - - - - Once triggered, the update will be managed by your cloud provider and applied during the configured - maintenance window. Moreover, the operation might cause a service interruption.{' '} - - Have a look at the documentation first - - - - )} ) } diff --git a/libs/shared/console-shared/src/lib/database-settings-resources/ui/settings-resources-instance-types/setting-resources-instance-types.tsx b/libs/shared/console-shared/src/lib/database-settings-resources/ui/settings-resources-instance-types/setting-resources-instance-types.tsx index 4bb53587a07..2a464ef961c 100644 --- a/libs/shared/console-shared/src/lib/database-settings-resources/ui/settings-resources-instance-types/setting-resources-instance-types.tsx +++ b/libs/shared/console-shared/src/lib/database-settings-resources/ui/settings-resources-instance-types/setting-resources-instance-types.tsx @@ -1,6 +1,6 @@ import { Controller, useFormContext } from 'react-hook-form' import { type Value } from '@qovery/shared/interfaces' -import { Callout, ExternalLink, Icon, InputSelect } from '@qovery/shared/ui' +import { InputSelect } from '@qovery/shared/ui' export interface SettingsResourcesInstanceTypesProps { databaseInstanceTypes?: Value[] @@ -14,43 +14,24 @@ export function SettingsResourcesInstanceTypes({ const { control } = useFormContext() return ( - <> - ( - - )} - /> - {displayWarning && ( - - - - - - Once triggered, the update will be managed by your cloud provider and applied during the configured - maintenance window. Moreover, the operation might cause a service interruption.{' '} - - Have a look at the documentation first - - - + ( + )} - + /> ) } From bbaa81d0f53c4129e0f4f4e841847f72878b5a5b Mon Sep 17 00:00:00 2001 From: Guillaume Da Silva Date: Fri, 30 Jan 2026 13:34:34 +0100 Subject: [PATCH 2/6] fix: add missing data-testid for apply-immediately warning --- .../apply-immediately-checkbox/apply-immediately-checkbox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx index ee2d81721e2..15600109dc0 100644 --- a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx +++ b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx @@ -50,7 +50,7 @@ export function ApplyImmediatelyCheckbox({ disabled = false }: ApplyImmediatelyC
{applyImmediately && ( -
+
Warning: Applying changes immediately may cause a brief service interruption. The operation will be performed outside of your configured maintenance window.
From fe25a311cfb812337888b35e51491af33e150360 Mon Sep 17 00:00:00 2001 From: Guillaume Da Silva Date: Fri, 30 Jan 2026 13:37:47 +0100 Subject: [PATCH 3/6] fix: wrap ApplyImmediatelyCheckbox tests in FormProvider The component uses useFormContext() which requires a FormProvider to be present in tests. --- .../apply-immediately-checkbox.spec.tsx | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx index 9ef21a5639c..6affd626eb8 100644 --- a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx +++ b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.spec.tsx @@ -1,14 +1,34 @@ +import { FormProvider, useForm } from 'react-hook-form' import { renderWithProviders, screen } from '@qovery/shared/util-tests' import { ApplyImmediatelyCheckbox } from './apply-immediately-checkbox' +function Wrapper({ children, defaultValues = {} }: { children: React.ReactNode; defaultValues?: any }) { + const methods = useForm({ + defaultValues: { + apply_immediately: false, + ...defaultValues, + }, + }) + + return {children} +} + describe('ApplyImmediatelyCheckbox', () => { it('should render successfully', () => { - const { baseElement } = renderWithProviders() + const { baseElement } = renderWithProviders( + + + + ) expect(baseElement).toBeTruthy() }) it('should display warning when checkbox is checked', async () => { - const { userEvent } = renderWithProviders() + const { userEvent } = renderWithProviders( + + + + ) const checkbox = screen.getByRole('checkbox') await userEvent.click(checkbox) @@ -17,13 +37,21 @@ describe('ApplyImmediatelyCheckbox', () => { }) it('should not display warning when checkbox is unchecked', () => { - renderWithProviders() + renderWithProviders( + + + + ) expect(screen.queryByTestId('apply-immediately-warning')).not.toBeInTheDocument() }) it('should be disabled when disabled prop is true', () => { - renderWithProviders() + renderWithProviders( + + + + ) const checkbox = screen.getByRole('checkbox') expect(checkbox).toBeDisabled() From 4a6c2dc22daf2ce3cd80c09de88545e7e6dfc4ae Mon Sep 17 00:00:00 2001 From: Guillaume Da Silva Date: Fri, 30 Jan 2026 13:48:19 +0100 Subject: [PATCH 4/6] Fix formatting in apply-immediately-checkbox component --- .../apply-immediately-checkbox.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx index 15600109dc0..af8d2feb4a7 100644 --- a/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx +++ b/libs/shared/console-shared/src/lib/apply-immediately-checkbox/apply-immediately-checkbox.tsx @@ -17,12 +17,7 @@ export function ApplyImmediatelyCheckbox({ disabled = false }: ApplyImmediatelyC control={control} render={({ field }) => (