From 6272ca4f4460865bb4c1d56f2ec5dd77ae58d4d0 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Tue, 28 Jan 2025 10:20:14 +0000 Subject: [PATCH 01/16] feat: check form errors --- .../cluster-form/resources/constants.ts | 2 + .../steps/first/first-step.tsx | 58 ++------------ .../DatabaseFormSideDrawer.tsx | 11 ++- .../DatabaseFormSideDrawer.types.ts | 1 + .../database-form/database-form.constants.ts | 46 ------------ .../src/pages/database-form/database-form.tsx | 37 ++++++++- .../database-form/database-form.utils.ts | 50 +++++++++++-- .../database-preview/database-preview.tsx | 2 + .../database-preview.types.ts | 2 + .../database-preview/preview-section.tsx | 75 +++++++++++-------- .../useDatabaseFormDefaultValues.ts | 36 ++++----- 11 files changed, 164 insertions(+), 156 deletions(-) diff --git a/ui/apps/everest/src/components/cluster-form/resources/constants.ts b/ui/apps/everest/src/components/cluster-form/resources/constants.ts index 8ab6f962c..6bcb87c9c 100644 --- a/ui/apps/everest/src/components/cluster-form/resources/constants.ts +++ b/ui/apps/everest/src/components/cluster-form/resources/constants.ts @@ -267,6 +267,8 @@ export const resourcesFormSchema = ( : numberOfProxies; if (+intNrNodes > 1 && +intNrProxies === 1) { + console.log('intNrNodes', intNrNodes); + console.log('intNrProxies', intNrProxies); if (numberOfProxies === CUSTOM_NR_UNITS_INPUT_VALUE) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx b/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx index 6a15c10f2..906803573 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx @@ -15,7 +15,7 @@ import { Box, FormGroup, Stack, Tooltip } from '@mui/material'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { lt, valid } from 'semver'; import { DbType } from '@percona/types'; import { @@ -28,10 +28,6 @@ import { dbTypeToDbEngine } from '@percona/utils'; import { useKubernetesClusterInfo } from 'hooks/api/kubernetesClusters/useKubernetesClusterInfo'; import { useFormContext } from 'react-hook-form'; import { DbEngineToolStatus } from 'shared-types/dbEngines.types'; -import { - DB_WIZARD_DEFAULTS, - DEFAULT_NODES, -} from '../../../database-form.constants.ts'; import { StepProps } from '../../../database-form.types.ts'; import { DbWizardFormFields } from 'consts.ts'; import { useDatabasePageMode } from '../../../useDatabasePageMode.ts'; @@ -39,14 +35,10 @@ import { StepHeader } from '../step-header/step-header.tsx'; import { Messages } from './first-step.messages.ts'; import { filterAvailableDbVersionsForDbEngineEdition } from 'components/cluster-form/db-version/utils.ts'; import { useNamespacePermissionsForResource } from 'hooks/rbac'; -import { - NODES_DEFAULT_SIZES, - PROXIES_DEFAULT_SIZES, - ResourceSize, -} from 'components/cluster-form'; import { DbVersion } from 'components/cluster-form/db-version'; import { useDBEnginesForDbEngineTypes } from 'hooks/index.ts'; import { useDatabasePageDefaultValues } from 'pages/database-form/useDatabaseFormDefaultValues.ts'; +import { getDbWizardDefaultValues } from 'pages/database-form/database-form.utils'; export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { const mode = useDatabasePageMode(); @@ -179,55 +171,15 @@ export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { }, [dbVersion, dbEngineData, getFieldState, mode, setValue]); const onNamespaceChange = () => { + const defaults = getDbWizardDefaultValues(dbType); setValue( DbWizardFormFields.monitoringInstance, - DB_WIZARD_DEFAULTS.monitoringInstance + defaults.monitoringInstance ); - setValue(DbWizardFormFields.monitoring, DB_WIZARD_DEFAULTS.monitoring); + setValue(DbWizardFormFields.monitoring, defaults.monitoring); setValue(DbWizardFormFields.schedules, []); }; - const setDefaultsForDbType = useCallback((dbType: DbType) => { - setValue(DbWizardFormFields.numberOfNodes, DEFAULT_NODES[dbType]); - setValue(DbWizardFormFields.customNrOfNodes, DEFAULT_NODES[dbType]); - setValue(DbWizardFormFields.numberOfProxies, DEFAULT_NODES[dbType]); - setValue(DbWizardFormFields.resourceSizePerNode, ResourceSize.small); - setValue(DbWizardFormFields.resourceSizePerProxy, ResourceSize.small); - setValue(DbWizardFormFields.cpu, NODES_DEFAULT_SIZES[dbType].small.cpu); - setValue( - DbWizardFormFields.proxyCpu, - PROXIES_DEFAULT_SIZES[dbType].small.cpu - ); - setValue( - DbWizardFormFields.memory, - NODES_DEFAULT_SIZES[dbType].small.memory - ); - setValue( - DbWizardFormFields.proxyMemory, - PROXIES_DEFAULT_SIZES[dbType].small.memory - ); - setValue(DbWizardFormFields.disk, NODES_DEFAULT_SIZES[dbType].small.disk); - setValue(DbWizardFormFields.shardNr, DB_WIZARD_DEFAULTS.shardNr); - setValue( - DbWizardFormFields.shardConfigServers, - DB_WIZARD_DEFAULTS.shardConfigServers - ); - - resetField(DbWizardFormFields.numberOfProxies, { - keepTouched: false, - }); - resetField(DbWizardFormFields.shardNr, { - keepError: false, - }); - resetField(DbWizardFormFields.shardConfigServers, { - keepError: false, - }); - }, []); - - useEffect(() => { - setDefaultsForDbType(dbType); - }, [dbType, setDefaultsForDbType]); - return ( <> { const theme = useTheme(); const { isDesktop } = useActiveBreakpoint(); @@ -20,6 +21,7 @@ const DatabaseFormSideDrawer = ({ activeStep={activeStep} longestAchievedStep={longestAchievedStep} onSectionEdit={handleSectionEdit} + stepsWithErrors={stepsWithErrors} sx={{ mt: 2, ...(!isDesktop && { @@ -28,7 +30,14 @@ const DatabaseFormSideDrawer = ({ }} /> ), - [activeStep, longestAchievedStep, handleSectionEdit, isDesktop] + [ + disabled, + activeStep, + longestAchievedStep, + handleSectionEdit, + stepsWithErrors, + isDesktop, + ] ); if (isDesktop) { diff --git a/ui/apps/everest/src/pages/database-form/database-form-side-drawer/DatabaseFormSideDrawer.types.ts b/ui/apps/everest/src/pages/database-form/database-form-side-drawer/DatabaseFormSideDrawer.types.ts index 65fbd7bf0..67226534b 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-side-drawer/DatabaseFormSideDrawer.types.ts +++ b/ui/apps/everest/src/pages/database-form/database-form-side-drawer/DatabaseFormSideDrawer.types.ts @@ -2,5 +2,6 @@ export type DatabaseFormSideDrawerProps = { activeStep: number; longestAchievedStep: number; disabled: boolean; + stepsWithErrors: number[]; handleSectionEdit: (section: number) => void; }; diff --git a/ui/apps/everest/src/pages/database-form/database-form.constants.ts b/ui/apps/everest/src/pages/database-form/database-form.constants.ts index f66db4295..bc27e27f9 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.constants.ts +++ b/ui/apps/everest/src/pages/database-form/database-form.constants.ts @@ -14,55 +14,9 @@ // limitations under the License. import { DbType } from '@percona/types'; -import { DbWizardFormFields } from 'consts.ts'; -import { DbWizardType } from './database-form-schema.ts'; -import { - getDefaultNumberOfconfigServersByNumberOfNodes, - NODES_DEFAULT_SIZES, - PROXIES_DEFAULT_SIZES, - ResourceSize, -} from 'components/cluster-form/resources/constants.ts'; export const DEFAULT_NODES: Record = { [DbType.Mongo]: '3', [DbType.Mysql]: '3', [DbType.Postresql]: '2', }; - -export const DB_WIZARD_DEFAULTS: DbWizardType = { - // TODO should be changed to true after https://jira.percona.com/browse/EVEREST-509 - [DbWizardFormFields.schedules]: [], - [DbWizardFormFields.pitrEnabled]: false, - [DbWizardFormFields.pitrStorageLocation]: null, - // @ts-ignore - [DbWizardFormFields.storageLocation]: null, - [DbWizardFormFields.dbType]: '' as DbType, - [DbWizardFormFields.dbName]: '', - [DbWizardFormFields.dbVersion]: '', - [DbWizardFormFields.storageClass]: '', - [DbWizardFormFields.k8sNamespace]: null, - [DbWizardFormFields.externalAccess]: false, - [DbWizardFormFields.sourceRanges]: [{ sourceRange: '' }], - [DbWizardFormFields.engineParametersEnabled]: false, - [DbWizardFormFields.engineParameters]: '', - [DbWizardFormFields.monitoring]: false, - [DbWizardFormFields.monitoringInstance]: '', - [DbWizardFormFields.numberOfNodes]: '1', - [DbWizardFormFields.numberOfProxies]: '1', - [DbWizardFormFields.resourceSizePerNode]: ResourceSize.small, - [DbWizardFormFields.resourceSizePerProxy]: ResourceSize.small, - [DbWizardFormFields.customNrOfNodes]: '1', - [DbWizardFormFields.customNrOfProxies]: '1', - [DbWizardFormFields.cpu]: NODES_DEFAULT_SIZES.mongodb.small.cpu, - [DbWizardFormFields.proxyCpu]: PROXIES_DEFAULT_SIZES.mongodb.small.cpu, - [DbWizardFormFields.disk]: NODES_DEFAULT_SIZES.mongodb.small.disk, - [DbWizardFormFields.diskUnit]: 'Gi', - [DbWizardFormFields.memory]: NODES_DEFAULT_SIZES.mongodb.small.memory, - [DbWizardFormFields.proxyMemory]: PROXIES_DEFAULT_SIZES.mongodb.small.memory, - [DbWizardFormFields.sharding]: false, - [DbWizardFormFields.shardNr]: '2', - [DbWizardFormFields.shardConfigServers]: - getDefaultNumberOfconfigServersByNumberOfNodes( - parseInt(DEFAULT_NODES[DbType.Mongo], 10) - ), -}; diff --git a/ui/apps/everest/src/pages/database-form/database-form.tsx b/ui/apps/everest/src/pages/database-form/database-form.tsx index 0abf38ca1..e791279e4 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form.tsx @@ -22,7 +22,7 @@ import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { useCreateDbCluster } from 'hooks/api/db-cluster/useCreateDbCluster'; import { useActiveBreakpoint } from 'hooks/utils/useActiveBreakpoint'; import { steps } from './database-form-body/steps'; -import { DbWizardType } from './database-form-schema'; +import { DbWizardType, getDBWizardSchema } from './database-form-schema'; import ConfirmationScreen from './database-form-body/steps/confirmation-screen'; import { useDatabasePageDefaultValues } from './useDatabaseFormDefaultValues'; import { useDatabasePageMode } from './useDatabasePageMode'; @@ -36,6 +36,7 @@ export const DatabasePage = () => { const [activeStep, setActiveStep] = useState(0); const [longestAchievedStep, setLongestAchievedStep] = useState(0); const [formSubmitted, setFormSubmitted] = useState(false); + const [stepsWithErrors, setStepsWithErrors] = useState([]); const { mutate: addDbCluster, isPending: isCreating } = useCreateDbCluster(); const location = useLocation(); const navigate = useNavigate(); @@ -63,6 +64,7 @@ export const DatabasePage = () => { clearErrors, trigger, handleSubmit, + watch, } = methods; const blocker = useBlocker( @@ -147,6 +149,38 @@ export const DatabasePage = () => { } }; + useEffect(() => { + const { unsubscribe } = watch((values) => { + if (loadingClusterValues) { + return; + } + + const newStepsWithErrors = steps.reduce((result, _, idx) => { + const schema = getDBWizardSchema(idx, defaultValues, mode); + const hasBeenReached = longestAchievedStep >= idx || mode === 'edit'; + const { error } = schema.safeParse(values); + + if (error && hasBeenReached) { + console.log('error@', idx, error); + return [...result, idx]; + } + + return result; + }, [] as number[]); + setStepsWithErrors(newStepsWithErrors); + }); + return () => unsubscribe(); + }, [ + activeStep, + defaultValues, + loadingClusterValues, + longestAchievedStep, + mode, + watch, + ]); + + console.log('stepsWithErrors', stepsWithErrors); + useEffect(() => { // We disable the inputs on first step to make sure user doesn't change anything before all data is loaded // When users change the inputs, it means all data was loaded and we should't change the defaults anymore at this point @@ -190,6 +224,7 @@ export const DatabasePage = () => { activeStep={activeStep} longestAchievedStep={longestAchievedStep} handleSectionEdit={handleSectionEdit} + stepsWithErrors={stepsWithErrors} /> diff --git a/ui/apps/everest/src/pages/database-form/database-form.utils.ts b/ui/apps/everest/src/pages/database-form/database-form.utils.ts index aebb4b6a4..f497d4964 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.utils.ts +++ b/ui/apps/everest/src/pages/database-form/database-form.utils.ts @@ -22,7 +22,7 @@ import { dbEngineToDbType } from '@percona/utils'; import { cpuParser, memoryParser } from 'utils/k8ResourceParser'; import { MAX_DB_CLUSTER_NAME_LENGTH } from 'consts'; import { DbWizardType } from './database-form-schema.ts'; -import { DB_WIZARD_DEFAULTS } from './database-form.constants.ts'; +import { DEFAULT_NODES } from './database-form.constants.ts'; import { generateShortUID } from 'utils/generateShortUID'; import { CUSTOM_NR_UNITS_INPUT_VALUE, @@ -36,6 +36,44 @@ import { import { isProxy } from 'utils/db.tsx'; import { advancedConfigurationModalDefaultValues } from 'components/cluster-form/advanced-configuration/advanced-configuration.utils.ts'; +export const getDbWizardDefaultValues = (dbType: DbType): DbWizardType => ({ + // TODO should be changed to true after https://jira.percona.com/browse/EVEREST-509 + [DbWizardFormFields.schedules]: [], + [DbWizardFormFields.pitrEnabled]: false, + [DbWizardFormFields.pitrStorageLocation]: null, + // @ts-ignore + [DbWizardFormFields.storageLocation]: null, + [DbWizardFormFields.dbType]: dbType, + [DbWizardFormFields.dbName]: `${dbType}-${generateShortUID()}`, + [DbWizardFormFields.dbVersion]: '', + [DbWizardFormFields.storageClass]: '', + [DbWizardFormFields.k8sNamespace]: null, + [DbWizardFormFields.externalAccess]: false, + [DbWizardFormFields.sourceRanges]: [{ sourceRange: '' }], + [DbWizardFormFields.engineParametersEnabled]: false, + [DbWizardFormFields.engineParameters]: '', + [DbWizardFormFields.monitoring]: false, + [DbWizardFormFields.monitoringInstance]: '', + [DbWizardFormFields.numberOfNodes]: DEFAULT_NODES[dbType], + [DbWizardFormFields.numberOfProxies]: DEFAULT_NODES[dbType], + [DbWizardFormFields.resourceSizePerNode]: ResourceSize.small, + [DbWizardFormFields.resourceSizePerProxy]: ResourceSize.small, + [DbWizardFormFields.customNrOfNodes]: DEFAULT_NODES[dbType], + [DbWizardFormFields.customNrOfProxies]: DEFAULT_NODES[dbType], + [DbWizardFormFields.cpu]: NODES_DEFAULT_SIZES[dbType].small.cpu, + [DbWizardFormFields.proxyCpu]: PROXIES_DEFAULT_SIZES[dbType].small.cpu, + [DbWizardFormFields.disk]: NODES_DEFAULT_SIZES[dbType].small.disk, + [DbWizardFormFields.diskUnit]: 'Gi', + [DbWizardFormFields.memory]: NODES_DEFAULT_SIZES[dbType].small.memory, + [DbWizardFormFields.proxyMemory]: PROXIES_DEFAULT_SIZES[dbType].small.memory, + [DbWizardFormFields.sharding]: false, + [DbWizardFormFields.shardNr]: '2', + [DbWizardFormFields.shardConfigServers]: + getDefaultNumberOfconfigServersByNumberOfNodes( + parseInt(DEFAULT_NODES[DbType.Mongo], 10) + ), +}); + const replicasToNodes = (replicas: string, dbType: DbType): string => { const nodeOptions = NODES_DB_TYPE_MAP[dbType]; const replicasString = replicas.toString(); @@ -52,6 +90,9 @@ export const DbClusterPayloadToFormValues = ( mode: DbWizardMode, namespace: string ): DbWizardType => { + const defaults = getDbWizardDefaultValues( + dbEngineToDbType(dbCluster.spec.engine.type) + ); const backup = dbCluster?.spec?.backup; const replicas = dbCluster?.spec?.engine?.replicas.toString(); const proxies = ( @@ -70,7 +111,7 @@ export const DbClusterPayloadToFormValues = ( return { //basic info [DbWizardFormFields.k8sNamespace]: - namespace || DB_WIZARD_DEFAULTS[DbWizardFormFields.k8sNamespace], + namespace || defaults[DbWizardFormFields.k8sNamespace], [DbWizardFormFields.dbType]: dbEngineToDbType( dbCluster?.spec?.engine?.type ), @@ -108,8 +149,7 @@ export const DbClusterPayloadToFormValues = ( sharding?.configServer?.replicas || getDefaultNumberOfconfigServersByNumberOfNodes(+numberOfNodes), [DbWizardFormFields.shardNr]: ( - sharding?.shards || - (DB_WIZARD_DEFAULTS[DbWizardFormFields.shardNr] as string) + sharding?.shards || (defaults[DbWizardFormFields.shardNr] as string) ).toString(), [DbWizardFormFields.cpu]: cpuParser( dbCluster?.spec?.engine?.resources?.cpu.toString() || '0' @@ -142,7 +182,7 @@ export const DbClusterPayloadToFormValues = ( [DbWizardFormFields.pitrStorageLocation]: (backup?.pitr?.enabled && mode === 'new') || mode === 'edit' ? backup?.pitr?.backupStorageName || null - : DB_WIZARD_DEFAULTS[DbWizardFormFields.pitrStorageLocation], + : defaults[DbWizardFormFields.pitrStorageLocation], [DbWizardFormFields.schedules]: backup?.schedules || [], //advanced configuration diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx index 557d33e66..5aa36c57a 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx @@ -13,6 +13,7 @@ export const DatabasePreview = ({ longestAchievedStep, onSectionEdit = () => {}, disabled, + stepsWithErrors, sx, ...stackProps }: DatabasePreviewProps) => { @@ -39,6 +40,7 @@ export const DatabasePreview = ({ order={idx + 1} title={Messages.preview[idx]} hasBeenReached={longestAchievedStep >= idx || mode === 'edit'} + hasError={stepsWithErrors.includes(idx)} active={activeStep === idx} disabled={disabled || Object.values(errors).length != 0} onEditClick={() => onSectionEdit(idx + 1)} diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.types.ts b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.types.ts index 9333e10e6..f03b74ebe 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.types.ts +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.types.ts @@ -20,6 +20,7 @@ export type DatabasePreviewProps = { longestAchievedStep: number; onSectionEdit?: (order: number) => void; disabled?: boolean; + stepsWithErrors: number[]; } & StackProps; export type PreviewSectionProps = { @@ -29,6 +30,7 @@ export type PreviewSectionProps = { active?: boolean; hasBeenReached?: boolean; disabled?: boolean; + hasError?: boolean; onEditClick?: () => void; } & StackProps; diff --git a/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx b/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx index faeda01dd..40994e946 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx @@ -1,4 +1,5 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import { IconButton, Stack, Typography, useTheme } from '@mui/material'; import { useActiveBreakpoint } from 'hooks/utils/useActiveBreakpoint'; import { @@ -15,6 +16,7 @@ export const PreviewSection = ({ hasBeenReached = false, active = false, disabled = false, + hasError = false, sx, ...stackProps }: PreviewSectionProps) => { @@ -44,39 +46,52 @@ export const PreviewSection = ({ }} {...stackProps} > - - {`${order}. ${title}`} - {showEdit && ( - - + + {`${order}. ${title}`} + {showEdit && ( + - + color="primary" + size="small" + disabled={disabled} + onClick={onEditClick} + data-testid={`button-edit-preview-${kebabize( + title.replace(/\s/g, '') + )}`} + > + + + )} + + {hasError && ( + )} - + {hasBeenReached && children} ); diff --git a/ui/apps/everest/src/pages/database-form/useDatabaseFormDefaultValues.ts b/ui/apps/everest/src/pages/database-form/useDatabaseFormDefaultValues.ts index 1409b9f93..bd061d391 100644 --- a/ui/apps/everest/src/pages/database-form/useDatabaseFormDefaultValues.ts +++ b/ui/apps/everest/src/pages/database-form/useDatabaseFormDefaultValues.ts @@ -17,22 +17,14 @@ import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { useDbCluster } from 'hooks/api/db-cluster/useDbCluster'; import { DbCluster } from 'shared-types/dbCluster.types'; -import { DB_WIZARD_DEFAULTS } from './database-form.constants'; import { DbWizardMode } from './database-form.types'; -import { DbWizardFormFields } from 'consts.ts'; -import { DbClusterPayloadToFormValues } from './database-form.utils'; + +import { + DbClusterPayloadToFormValues, + getDbWizardDefaultValues, +} from './database-form.utils'; import { DbWizardType } from './database-form-schema.ts'; import { dbEngineToDbType } from '@percona/utils'; -import { DbType } from '@percona/types'; -import { generateShortUID } from 'utils/generateShortUID.ts'; - -const getNewDbDefaults = (dbType: DbType) => { - return { - ...DB_WIZARD_DEFAULTS, - dbType: dbType, - [DbWizardFormFields.dbName]: `${dbType}-${generateShortUID()}`, - }; -}; export const useDatabasePageDefaultValues = ( mode: DbWizardMode @@ -55,14 +47,18 @@ export const useDatabasePageDefaultValues = ( enabled: shouldRetrieveDbClusterData, }); - const [defaultValues, setDefaultValues] = useState( - // @ts-ignore - mode === 'new' - ? getNewDbDefaults(dbEngineToDbType(state?.selectedDbEngine)) - : dbClusterRequestStatus === 'success' + const [defaultValues, setDefaultValues] = useState(() => { + const dbType = dbEngineToDbType(state?.selectedDbEngine); + const defaults = getDbWizardDefaultValues(dbType); + + if (mode === 'new') { + return getDbWizardDefaultValues(dbType); + } else { + return dbClusterRequestStatus === 'success' ? DbClusterPayloadToFormValues(dbCluster, mode, namespace) - : { ...DB_WIZARD_DEFAULTS, [DbWizardFormFields.dbVersion]: '' } - ); + : defaults; + } + }); useEffect(() => { // dbClusterRequestStatus === 'success' when the request is enabled, which only happens if shouldRetrieveDbClusterData === true From 36cce34513856bb3d8e21498746ec8a50495839f Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Tue, 28 Jan 2025 10:22:50 +0000 Subject: [PATCH 02/16] chore: remove logs --- .../src/components/cluster-form/resources/constants.ts | 2 -- .../database-preview/database-preview.test.tsx | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/apps/everest/src/components/cluster-form/resources/constants.ts b/ui/apps/everest/src/components/cluster-form/resources/constants.ts index 6bcb87c9c..8ab6f962c 100644 --- a/ui/apps/everest/src/components/cluster-form/resources/constants.ts +++ b/ui/apps/everest/src/components/cluster-form/resources/constants.ts @@ -267,8 +267,6 @@ export const resourcesFormSchema = ( : numberOfProxies; if (+intNrNodes > 1 && +intNrProxies === 1) { - console.log('intNrNodes', intNrNodes); - console.log('intNrProxies', intNrProxies); if (numberOfProxies === CUSTOM_NR_UNITS_INPUT_VALUE) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx index a80a1c337..ea269dc16 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx @@ -2,21 +2,22 @@ import React from 'react'; import { screen, render, fireEvent, waitFor } from '@testing-library/react'; import { FormProvider, useForm, useFormContext } from 'react-hook-form'; import { DbType } from '@percona/types'; -import { DB_WIZARD_DEFAULTS } from '../database-form.constants'; import { TestWrapper } from 'utils/test'; import { DatabasePreview } from './database-preview'; import { DbWizardType } from '../database-form-schema.ts'; +import { getDbWizardDefaultValues } from '../database-form.utils'; const FormProviderWrapper = ({ children, + dbType = DbType.Mongo, values = {}, }: { children: React.ReactNode; + dbType?: DbType; values?: Partial; }) => { const methods = useForm({ - // @ts-ignore - defaultValues: { ...DB_WIZARD_DEFAULTS, ...values }, + defaultValues: { ...getDbWizardDefaultValues(dbType), ...values }, }); return ( From 85840c784d762beded7f36e0661c24deaeab0bed Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Tue, 28 Jan 2025 10:29:07 +0000 Subject: [PATCH 03/16] feat: prevent errors from going back and forth --- .../database-form-body/DatabaseFormBody.tsx | 4 +-- .../database-form/database-form-body/types.ts | 14 +++++----- .../src/pages/database-form/database-form.tsx | 27 +++++-------------- .../database-preview/database-preview.tsx | 7 ++--- 4 files changed, 17 insertions(+), 35 deletions(-) diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormBody.tsx b/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormBody.tsx index f3c6857dc..8e33f1b88 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormBody.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormBody.tsx @@ -24,7 +24,6 @@ import DatabaseFormStepControllers from './DatabaseFormStepControllers'; const DatabaseFormBody = ({ activeStep, longestAchievedStep, - disableNext, isSubmitting, hasErrors, onCancel, @@ -52,8 +51,7 @@ const DatabaseFormBody = ({ })} void; @@ -11,12 +11,12 @@ export type DatabaseFormBodyProps = { }; export type DatabaseFormStepControllersProps = { - disableBack: boolean; - disableNext: boolean; - disableSubmit: boolean; - disableCancel: boolean; - showSubmit: boolean; - editMode: boolean; + disableBack?: boolean; + disableNext?: boolean; + disableSubmit?: boolean; + disableCancel?: boolean; + showSubmit?: boolean; + editMode?: boolean; onPreviousClick: () => void; onNextClick: () => void; onCancel: () => void; diff --git a/ui/apps/everest/src/pages/database-form/database-form.tsx b/ui/apps/everest/src/pages/database-form/database-form.tsx index e791279e4..750263649 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form.tsx @@ -29,7 +29,6 @@ import { useDatabasePageMode } from './useDatabasePageMode'; import { useDbValidationSchema } from './useDbValidationSchema'; import DatabaseFormCancelDialog from './database-form-cancel-dialog/index'; import DatabaseFormBody from './database-form-body'; -import { DbWizardFormFields } from 'consts.ts'; import DatabaseFormSideDrawer from './database-form-side-drawer'; export const DatabasePage = () => { @@ -62,7 +61,6 @@ export const DatabasePage = () => { reset, formState: { isDirty, errors }, clearErrors, - trigger, handleSubmit, watch, } = methods; @@ -104,24 +102,14 @@ export const DatabasePage = () => { const handleNext = async () => { if (activeStep < steps.length - 1) { - let isStepValid; + setActiveStep((prevActiveStep) => { + const newStep = prevActiveStep + 1; - if (errors[DbWizardFormFields.disk] && activeStep === 1) { - isStepValid = false; - } else { - isStepValid = await trigger(); - } - - if (isStepValid) { - setActiveStep((prevActiveStep) => { - const newStep = prevActiveStep + 1; - - if (newStep > longestAchievedStep) { - setLongestAchievedStep(newStep); - } - return newStep; - }); - } + if (newStep > longestAchievedStep) { + setLongestAchievedStep(newStep); + } + return newStep; + }); } }; @@ -211,7 +199,6 @@ export const DatabasePage = () => { { - const { - getValues, - formState: { errors }, - } = useFormContext(); + const { getValues } = useFormContext(); const mode = useDatabasePageMode(); // Under normal circumstances, useWatch should return the right values @@ -42,7 +39,7 @@ export const DatabasePreview = ({ hasBeenReached={longestAchievedStep >= idx || mode === 'edit'} hasError={stepsWithErrors.includes(idx)} active={activeStep === idx} - disabled={disabled || Object.values(errors).length != 0} + disabled={disabled} onEditClick={() => onSectionEdit(idx + 1)} sx={{ mt: idx === 0 ? 2 : 0, From 35730e19bf929ea4292160b61c2a072b17de6247 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Wed, 29 Jan 2025 11:02:12 +0000 Subject: [PATCH 04/16] feat: use resolver to check step errors --- .../advanced-configuration.tsx | 3 +- .../steps/first/first-step.tsx | 14 +++-- .../src/pages/database-form/database-form.tsx | 62 ++++++++----------- .../database-preview/database-preview.tsx | 2 +- 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx b/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx index 5bc4a815c..c085e48fd 100644 --- a/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx +++ b/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx @@ -28,7 +28,8 @@ interface AdvancedConfigurationFormProps { export const AdvancedConfigurationForm = ({ dbType, }: AdvancedConfigurationFormProps) => { - const { watch } = useFormContext(); + const { watch, formState } = useFormContext(); + console.log('form state.errors', formState.errors); const [externalAccess, engineParametersEnabled] = watch([ AdvancedConfigurationFields.externalAccess, AdvancedConfigurationFields.engineParametersEnabled, diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx b/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx index 906803573..3126777a4 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form-body/steps/first/first-step.tsx @@ -46,8 +46,7 @@ export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { const { defaultValues: { [DbWizardFormFields.dbVersion]: defaultDbVersion }, } = useDatabasePageDefaultValues(mode); - const { watch, setValue, getFieldState, resetField, trigger } = - useFormContext(); + const { watch, setValue, getFieldState, resetField } = useFormContext(); const { data: clusterInfo, isFetching: clusterInfoFetching } = useKubernetesClusterInfo(['wizard-k8-info']); @@ -108,7 +107,8 @@ export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { ) { setValue( DbWizardFormFields.storageClass, - clusterInfo?.storageClassNames[0] + clusterInfo?.storageClassNames[0], + { shouldValidate: true } ); } }, [clusterInfo]); @@ -133,8 +133,9 @@ export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { filteredNamespaces.length > 0 && !isFetching ) { - setValue(DbWizardFormFields.k8sNamespace, filteredNamespaces[0]); - trigger(DbWizardFormFields.k8sNamespace); + setValue(DbWizardFormFields.k8sNamespace, filteredNamespaces[0], { + shouldValidate: true, + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [mode, isFetching, filteredNamespaces.length]); @@ -165,7 +166,8 @@ export const FirstStep = ({ loadingDefaultsForEdition }: StepProps) => { DbWizardFormFields.dbVersion, recommendedVersion ? recommendedVersion.version - : dbEngineData.availableVersions.engine[0].version + : dbEngineData.availableVersions.engine[0].version, + { shouldValidate: true } ); } }, [dbVersion, dbEngineData, getFieldState, mode, setValue]); diff --git a/ui/apps/everest/src/pages/database-form/database-form.tsx b/ui/apps/everest/src/pages/database-form/database-form.tsx index 750263649..bc5202b94 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form.tsx @@ -22,7 +22,7 @@ import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { useCreateDbCluster } from 'hooks/api/db-cluster/useCreateDbCluster'; import { useActiveBreakpoint } from 'hooks/utils/useActiveBreakpoint'; import { steps } from './database-form-body/steps'; -import { DbWizardType, getDBWizardSchema } from './database-form-schema'; +import { DbWizardType } from './database-form-schema'; import ConfirmationScreen from './database-form-body/steps/confirmation-screen'; import { useDatabasePageDefaultValues } from './useDatabaseFormDefaultValues'; import { useDatabasePageMode } from './useDatabasePageMode'; @@ -52,17 +52,37 @@ export const DatabasePage = () => { const methods = useForm({ mode: 'onChange', - resolver: zodResolver(validationSchema), + resolver: async (data, context, options) => { + const result = await zodResolver(validationSchema)( + data, + context, + options + ); + if (Object.keys(result.errors).length > 0) { + console.log('RESOLVER', result.errors); + setStepsWithErrors((prev) => { + if (!prev.includes(activeStep)) { + return [...prev, activeStep]; + } + return prev; + }); + } else { + setStepsWithErrors((prev) => + prev.filter((step) => step !== activeStep) + ); + } + return result; + }, // @ts-ignore defaultValues, }); const { reset, - formState: { isDirty, errors }, + formState: { isDirty }, clearErrors, handleSubmit, - watch, + trigger, } = methods; const blocker = useBlocker( @@ -72,8 +92,6 @@ export const DatabasePage = () => { currentLocation.pathname !== nextLocation.pathname ); - const formHasErrors = Object.values(errors).length > 0; - const onSubmit: SubmitHandler = (data) => { if (mode === 'new' || mode === 'restoreFromBackup') { addDbCluster( @@ -138,34 +156,8 @@ export const DatabasePage = () => { }; useEffect(() => { - const { unsubscribe } = watch((values) => { - if (loadingClusterValues) { - return; - } - - const newStepsWithErrors = steps.reduce((result, _, idx) => { - const schema = getDBWizardSchema(idx, defaultValues, mode); - const hasBeenReached = longestAchievedStep >= idx || mode === 'edit'; - const { error } = schema.safeParse(values); - - if (error && hasBeenReached) { - console.log('error@', idx, error); - return [...result, idx]; - } - - return result; - }, [] as number[]); - setStepsWithErrors(newStepsWithErrors); - }); - return () => unsubscribe(); - }, [ - activeStep, - defaultValues, - loadingClusterValues, - longestAchievedStep, - mode, - watch, - ]); + trigger(); + }, [activeStep, trigger]); console.log('stepsWithErrors', stepsWithErrors); @@ -200,7 +192,7 @@ export const DatabasePage = () => { activeStep={activeStep} longestAchievedStep={longestAchievedStep} isSubmitting={isCreating} - hasErrors={formHasErrors} + hasErrors={stepsWithErrors.length > 0} onSubmit={handleSubmit(onSubmit)} onCancel={() => navigate('/databases')} handleNextStep={handleNext} diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx index c0b350318..27640ac93 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.tsx @@ -37,7 +37,7 @@ export const DatabasePreview = ({ order={idx + 1} title={Messages.preview[idx]} hasBeenReached={longestAchievedStep >= idx || mode === 'edit'} - hasError={stepsWithErrors.includes(idx)} + hasError={stepsWithErrors.includes(idx) && activeStep !== idx} active={activeStep === idx} disabled={disabled} onEditClick={() => onSectionEdit(idx + 1)} From f595f59e815d4c6b0f1fbe005c018cc00acbfb41 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 13:26:09 +0000 Subject: [PATCH 05/16] chore: remove unused code --- .../database-form-body/DatabaseFormStepControllers.tsx | 2 -- .../src/pages/database-form/database-form-body/types.ts | 1 - ui/apps/everest/src/pages/database-form/database-form.tsx | 3 --- 3 files changed, 6 deletions(-) diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormStepControllers.tsx b/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormStepControllers.tsx index 584943ec4..4243fb5ed 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormStepControllers.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form-body/DatabaseFormStepControllers.tsx @@ -20,7 +20,6 @@ import { DatabaseFormStepControllersProps } from './types'; const DatabaseFormStepControllers = ({ disableBack, - disableNext, disableSubmit, disableCancel, showSubmit, @@ -66,7 +65,6 @@ const DatabaseFormStepControllers = ({ onClick={onNextClick} variant="contained" data-testid="db-wizard-continue-button" - disabled={disableNext} > {Messages.continue} diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/types.ts b/ui/apps/everest/src/pages/database-form/database-form-body/types.ts index d2c01818a..6ec44e518 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/types.ts +++ b/ui/apps/everest/src/pages/database-form/database-form-body/types.ts @@ -12,7 +12,6 @@ export type DatabaseFormBodyProps = { export type DatabaseFormStepControllersProps = { disableBack?: boolean; - disableNext?: boolean; disableSubmit?: boolean; disableCancel?: boolean; showSubmit?: boolean; diff --git a/ui/apps/everest/src/pages/database-form/database-form.tsx b/ui/apps/everest/src/pages/database-form/database-form.tsx index bc5202b94..98222a696 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form.tsx @@ -59,7 +59,6 @@ export const DatabasePage = () => { options ); if (Object.keys(result.errors).length > 0) { - console.log('RESOLVER', result.errors); setStepsWithErrors((prev) => { if (!prev.includes(activeStep)) { return [...prev, activeStep]; @@ -159,8 +158,6 @@ export const DatabasePage = () => { trigger(); }, [activeStep, trigger]); - console.log('stepsWithErrors', stepsWithErrors); - useEffect(() => { // We disable the inputs on first step to make sure user doesn't change anything before all data is loaded // When users change the inputs, it means all data was loaded and we should't change the defaults anymore at this point From 3b14e8c4b023c05a1bd00c6d401796509a7bb5da Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 13:26:30 +0000 Subject: [PATCH 06/16] chore: remove log --- .../advanced-configuration/advanced-configuration.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx b/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx index c085e48fd..5bc4a815c 100644 --- a/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx +++ b/ui/apps/everest/src/components/cluster-form/advanced-configuration/advanced-configuration.tsx @@ -28,8 +28,7 @@ interface AdvancedConfigurationFormProps { export const AdvancedConfigurationForm = ({ dbType, }: AdvancedConfigurationFormProps) => { - const { watch, formState } = useFormContext(); - console.log('form state.errors', formState.errors); + const { watch } = useFormContext(); const [externalAccess, engineParametersEnabled] = watch([ AdvancedConfigurationFields.externalAccess, AdvancedConfigurationFields.engineParametersEnabled, From 0e4d490b8b3729cee3467830fe139709c8ceed61 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 13:33:33 +0000 Subject: [PATCH 07/16] chore: add missing props to unit tests --- .../database-preview.test.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx index ea269dc16..cde769ba7 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx @@ -32,7 +32,11 @@ describe('DatabasePreview', () => { render( - + ); @@ -52,7 +56,11 @@ describe('DatabasePreview', () => { }} > - + ); @@ -75,7 +83,11 @@ describe('DatabasePreview', () => { }} > - + ); @@ -114,7 +126,11 @@ describe('DatabasePreview', () => { > - + ); From 81d497439f7a4b0e7b90c0c8f4ffb3cb8ff0d185 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 16:07:43 +0000 Subject: [PATCH 08/16] fix: unit tests --- .../database-preview.test.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx index cde769ba7..ae7104b92 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx @@ -9,15 +9,15 @@ import { getDbWizardDefaultValues } from '../database-form.utils'; const FormProviderWrapper = ({ children, - dbType = DbType.Mongo, - values = {}, + values = { + dbType: DbType.Mongo, + }, }: { children: React.ReactNode; - dbType?: DbType; values?: Partial; }) => { const methods = useForm({ - defaultValues: { ...getDbWizardDefaultValues(dbType), ...values }, + defaultValues: { ...getDbWizardDefaultValues(values.dbType!), ...values }, }); return ( @@ -27,7 +27,7 @@ const FormProviderWrapper = ({ ); }; -describe('DatabasePreview', () => { +describe.only('DatabasePreview', () => { it('should show all sections', () => { render( @@ -72,7 +72,7 @@ describe('DatabasePreview', () => { expect(screen.queryByText('Nº nodes: 1')).not.toBeInTheDocument(); }); - it('should show values from previous steps', () => { + it.only('should show values from previous steps', async () => { render( { ); + await waitFor(() => + expect(screen.getByText('CPU: 0.60 CPU')).toBeInTheDocument() + ); + expect(screen.getByText('Name: myDB')).toBeInTheDocument(); expect(screen.getByText('Type: MySQL')).toBeInTheDocument(); expect(screen.getByText('Version: 1.0.0')).toBeInTheDocument(); - - expect(screen.getByText('Nº nodes: 1')).toBeInTheDocument(); - expect(screen.getAllByText('CPU: 1.00 CPU').length).toBeGreaterThan(1); - expect(screen.getByText('Disk: 30.00 Gi')).toBeInTheDocument(); + expect(screen.getByText('Nº nodes: 3')).toBeInTheDocument(); + expect(screen.getByText('Memory: 6.00 GB')).toBeInTheDocument(); + expect(screen.getByText('CPU: 3.00 CPU')).toBeInTheDocument(); + expect(screen.getByText('Disk: 90.00 Gi')).toBeInTheDocument(); }); it('should get updated form values', async () => { From 32872dd3f65c18b76ff361eea682b2680fd3ff6b Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 17:56:35 +0000 Subject: [PATCH 09/16] test: e2e for wizard errors --- .../create-db-cluster/errors-handling.e2e.ts | 54 +++++++++++++++---- ui/apps/everest/.e2e/utils/db-wizard.ts | 10 ++-- .../database-preview/preview-section.tsx | 6 +-- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/errors-handling.e2e.ts b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/errors-handling.e2e.ts index 999d9bc79..c7b5b749e 100644 --- a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/errors-handling.e2e.ts +++ b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/errors-handling.e2e.ts @@ -18,9 +18,7 @@ import { goToStep, moveForward } from '@e2e/utils/db-wizard'; import { selectDbEngine } from '../db-wizard-utils'; test.describe('DB Cluster creation', () => { - test('Blocking the edit buttons when an error occurs in the form', async ({ - page, - }) => { + test('Wizard form errors', async ({ page }) => { await page.goto('/databases'); await selectDbEngine(page, 'pxc'); @@ -47,18 +45,56 @@ test.describe('DB Cluster creation', () => { page.getByTestId('button-edit-preview-backups') ).not.toBeDisabled(); + // Introduce an error on resources step await page.getByTestId('text-input-memory').fill(''); + await expect(page.getByTestId('preview-error-resources')).not.toBeVisible(); - await expect(page.getByTestId('db-wizard-previous-button')).toBeDisabled(); - await expect(page.getByTestId('db-wizard-continue-button')).toBeDisabled(); await expect( page.getByTestId('db-wizard-cancel-button') ).not.toBeDisabled(); + + // Backups step + await moveForward(page); + // Advanced Configurations step + await moveForward(page); + + await page + .getByTestId('switch-input-external-access') + .getByRole('checkbox') + .check(); + await page.getByTestId('add-text-input-button').click(); + // Introduce an error on advanced configs step: two invalid IPs + await page + .getByTestId('text-input-source-ranges.0.source-range') + .fill('invalid-ip'); + await page + .getByTestId('text-input-source-ranges.1.source-range') + .fill('another-invalid-ip'); await expect( - page.getByTestId('button-edit-preview-basic-information') - ).toBeDisabled(); + page.getByTestId('preview-error-advanced-configurations') + ).not.toBeVisible(); + + // Monitoring step + await moveForward(page); + await expect(page.getByTestId('db-wizard-submit-button')).toBeDisabled(); + await expect(page.getByTestId('preview-error-resources')).toBeVisible(); await expect( - page.getByTestId('button-edit-preview-backups') - ).toBeDisabled(); + page.getByTestId('preview-error-advanced-configurations') + ).toBeVisible(); + await goToStep(page, 'resources'); + await page.getByTestId('text-input-memory').fill('1'); + await goToStep(page, 'advanced-configurations'); + await page.getByTestId('delete-text-input-1-button').click(); + await page + .getByTestId('text-input-source-ranges.0.source-range') + .fill('192.168.1.1'); + await goToStep(page, 'monitoring'); + await expect( + page.getByTestId('db-wizard-submit-button') + ).not.toBeDisabled(); + await expect(page.getByTestId('preview-error-resources')).not.toBeVisible(); + await expect( + page.getByTestId('preview-error-advanced-configurations') + ).not.toBeVisible(); }); }); diff --git a/ui/apps/everest/.e2e/utils/db-wizard.ts b/ui/apps/everest/.e2e/utils/db-wizard.ts index 6ab3857d3..b2331080f 100644 --- a/ui/apps/everest/.e2e/utils/db-wizard.ts +++ b/ui/apps/everest/.e2e/utils/db-wizard.ts @@ -16,10 +16,14 @@ export const storageLocationAutocompleteEmptyValidationCheck = async ( }; export const moveForward = async (page: Page) => { - await expect( - page.getByTestId('db-wizard-continue-button') - ).not.toBeDisabled(); + const currHeader = await page.getByTestId('step-header').textContent(); await page.getByTestId('db-wizard-continue-button').click(); + + do { + page.waitForTimeout(200); + } while ( + (await page.getByTestId('step-header').textContent()) === currHeader + ); }; export const moveBack = (page: Page) => diff --git a/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx b/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx index 40994e946..2d296ca57 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/preview-section.tsx @@ -23,6 +23,7 @@ export const PreviewSection = ({ const theme = useTheme(); const showEdit = !active && hasBeenReached; const { isDesktop } = useActiveBreakpoint(); + const kebabizedTitle = kebabize(title.replace(/\s/g, '')); return ( {hasError && ( Date: Thu, 30 Jan 2025 17:58:58 +0000 Subject: [PATCH 10/16] chore: remove misused ".only" --- .../database-form/database-preview/database-preview.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx index ae7104b92..d3b0ddd5d 100644 --- a/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx +++ b/ui/apps/everest/src/pages/database-form/database-preview/database-preview.test.tsx @@ -27,7 +27,7 @@ const FormProviderWrapper = ({ ); }; -describe.only('DatabasePreview', () => { +describe('DatabasePreview', () => { it('should show all sections', () => { render( @@ -72,7 +72,7 @@ describe.only('DatabasePreview', () => { expect(screen.queryByText('Nº nodes: 1')).not.toBeInTheDocument(); }); - it.only('should show values from previous steps', async () => { + it('should show values from previous steps', async () => { render( Date: Thu, 30 Jan 2025 19:05:08 +0000 Subject: [PATCH 11/16] fix: moveForward --- ui/apps/everest/.e2e/utils/db-wizard.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/apps/everest/.e2e/utils/db-wizard.ts b/ui/apps/everest/.e2e/utils/db-wizard.ts index b2331080f..abbd689ae 100644 --- a/ui/apps/everest/.e2e/utils/db-wizard.ts +++ b/ui/apps/everest/.e2e/utils/db-wizard.ts @@ -20,10 +20,11 @@ export const moveForward = async (page: Page) => { await page.getByTestId('db-wizard-continue-button').click(); do { + if ((await page.getByTestId('step-header').textContent()) !== currHeader) { + break; + } page.waitForTimeout(200); - } while ( - (await page.getByTestId('step-header').textContent()) === currHeader - ); + } while (1); }; export const moveBack = (page: Page) => From 9b96fdc5426f22902115fd631d96e1945322eaa9 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Thu, 30 Jan 2025 23:54:48 +0000 Subject: [PATCH 12/16] test: remove misused assertion --- .../pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts index 195ecba92..c881d8514 100644 --- a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts +++ b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts @@ -154,7 +154,6 @@ test.describe('Sharding (psmdb)', () => { await page.getByTestId('shard-config-servers-1').click(); expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); - expect(page.getByTestId('db-wizard-continue-button')).toBeDisabled(); await page.getByTestId('toggle-button-nodes-1').click(); expect(page.getByTestId('shard-config-servers-error')).not.toBeVisible(); @@ -162,7 +161,6 @@ test.describe('Sharding (psmdb)', () => { await page.getByTestId('toggle-button-nodes-3').click(); expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); - expect(page.getByTestId('db-wizard-continue-button')).toBeDisabled(); await page.getByTestId('shard-config-servers-3').click(); expect(page.getByTestId('shard-config-servers-error')).not.toBeVisible(); @@ -180,7 +178,6 @@ test.describe('Sharding (psmdb)', () => { await page.getByTestId('text-input-shard-nr').fill('0'); expect(page.getByText('The value cannot be less than 1')).toBeVisible(); - expect(page.getByTestId('db-wizard-continue-button')).toBeDisabled(); await page.getByTestId('text-input-shard-nr').fill('1'); expect(page.getByTestId('db-wizard-continue-button')).not.toBeDisabled(); }); From 080f748ee4581e2279e0d161f5ef287a7479ad76 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Fri, 31 Jan 2025 01:00:23 +0000 Subject: [PATCH 13/16] fix: wrong e2e test --- .../db-wizard/create-db-cluster/create-db-cluster.e2e.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts index d5ae914b3..403089e5d 100644 --- a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts +++ b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts @@ -227,9 +227,9 @@ test.describe('DB Cluster creation', () => { expect(addedCluster).not.toBeUndefined(); expect(addedCluster?.spec.engine.type).toBe('psmdb'); expect(addedCluster?.spec.engine.replicas).toBe(3); - expect(addedCluster?.spec.engine.resources?.cpu.toString()).toBe('1'); - expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe('4G'); - expect(addedCluster?.spec.engine.storage.size.toString()).toBe('25Gi'); + expect(addedCluster?.spec.engine.resources?.cpu.toString()).toBe('600m'); + expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe('1G'); + expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); // TODO commented, because we use only psmdb in this test // expect(addedCluster?.spec.proxy.replicas).toBe(1); From e63beb4f7a4913bb597c845c798c4a57a4fbd14a Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Fri, 31 Jan 2025 15:59:36 +0000 Subject: [PATCH 14/16] fix: add missing awaits to e2e tests --- .../create-db-cluster/sharding.e2e.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts index c881d8514..f2b215baf 100644 --- a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts +++ b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/sharding.e2e.ts @@ -153,18 +153,26 @@ test.describe('Sharding (psmdb)', () => { ); await page.getByTestId('shard-config-servers-1').click(); - expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); + await expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); await page.getByTestId('toggle-button-nodes-1').click(); - expect(page.getByTestId('shard-config-servers-error')).not.toBeVisible(); - expect(page.getByTestId('db-wizard-continue-button')).not.toBeDisabled(); + await expect( + page.getByTestId('shard-config-servers-error') + ).not.toBeVisible(); + await expect( + page.getByTestId('db-wizard-continue-button') + ).not.toBeDisabled(); await page.getByTestId('toggle-button-nodes-3').click(); - expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); + await expect(page.getByTestId('shard-config-servers-error')).toBeVisible(); await page.getByTestId('shard-config-servers-3').click(); - expect(page.getByTestId('shard-config-servers-error')).not.toBeVisible(); - expect(page.getByTestId('db-wizard-continue-button')).not.toBeDisabled(); + await expect( + page.getByTestId('shard-config-servers-error') + ).not.toBeVisible(); + await expect( + page.getByTestId('db-wizard-continue-button') + ).not.toBeDisabled(); }); test('0 value of the Nº of shards causes an error', async ({ page }) => { @@ -177,9 +185,13 @@ test.describe('Sharding (psmdb)', () => { await moveForward(page); await page.getByTestId('text-input-shard-nr').fill('0'); - expect(page.getByText('The value cannot be less than 1')).toBeVisible(); + await expect( + page.getByText('The value cannot be less than 1') + ).toBeVisible(); await page.getByTestId('text-input-shard-nr').fill('1'); - expect(page.getByTestId('db-wizard-continue-button')).not.toBeDisabled(); + await expect( + page.getByTestId('db-wizard-continue-button') + ).not.toBeDisabled(); }); test('Changing the Nº of nodes causes changing the confing servers Nº automatically, until the user touches confing servers Nº', async ({ From 5c1d68425baa8db0a691671279c18ecd9f3f2298 Mon Sep 17 00:00:00 2001 From: Fabio Silva Date: Fri, 31 Jan 2025 22:31:40 +0000 Subject: [PATCH 15/16] fix: re-render when errors change for config servers --- .../cluster-form/resources/resources.tsx | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/ui/apps/everest/src/components/cluster-form/resources/resources.tsx b/ui/apps/everest/src/components/cluster-form/resources/resources.tsx index 60c2caf07..2ac546fb3 100644 --- a/ui/apps/everest/src/components/cluster-form/resources/resources.tsx +++ b/ui/apps/everest/src/components/cluster-form/resources/resources.tsx @@ -43,6 +43,7 @@ import { getProxyUnitNamesFromDbType } from './utils'; import { ResourcesTogglesProps, ResourceInputProps } from './resources.types'; import { Messages } from './messages'; +import { DbWizardType } from 'pages/database-form/database-form-schema'; const humanizeResourceSizeMap = (type: ResourceSize): string => humanizedResourceSizeMap[type]; @@ -443,22 +444,23 @@ const ResourcesForm = ({ const [expanded, setExpanded] = useState<'nodes' | 'proxies' | false>( 'nodes' ); - const { watch, getFieldState, setValue, trigger, clearErrors } = - useFormContext(); + const { + watch, + getFieldState, + setValue, + trigger, + clearErrors, + formState: { errors }, + } = useFormContext(); const numberOfNodes: string = watch(DbWizardFormFields.numberOfNodes); const sharding: boolean = watch(DbWizardFormFields.sharding); - const shardConfigServers: number = watch( - DbWizardFormFields.shardConfigServers - ); - const { error: shardConfigServersError } = getFieldState( - DbWizardFormFields.shardConfigServers - ); + const shardConfigServers = watch(DbWizardFormFields.shardConfigServers); const numberOfProxies: string = watch(DbWizardFormFields.numberOfProxies); - const customNrOfNodes: string = watch(DbWizardFormFields.customNrOfNodes); - const customNrOfProxies: string = watch(DbWizardFormFields.customNrOfProxies); + const customNrOfNodes = watch(DbWizardFormFields.customNrOfNodes); + const customNrOfProxies = watch(DbWizardFormFields.customNrOfProxies); const proxyUnitNames = getProxyUnitNamesFromDbType(dbType); const nodesAccordionSummaryNumber = numberOfNodes === CUSTOM_NR_UNITS_INPUT_VALUE @@ -526,7 +528,9 @@ const ResourcesForm = ({ clearErrors(DbWizardFormFields.shardConfigServers); setValue( DbWizardFormFields.shardConfigServers, - getDefaultNumberOfconfigServersByNumberOfNodes(+customNrOfNodes) + getDefaultNumberOfconfigServersByNumberOfNodes( + +(customNrOfNodes || '') + ) ); } } @@ -579,7 +583,7 @@ const ResourcesForm = ({ > { - setValue(DbWizardFormFields.shardConfigServers, value); - trigger(DbWizardFormFields.shardConfigServers); + setValue(DbWizardFormFields.shardConfigServers, value, { + shouldValidate: true, + }); }, }} > @@ -674,12 +679,12 @@ const ResourcesForm = ({ ))} - {shardConfigServersError && ( + {errors.shardConfigServers && ( - {shardConfigServersError?.message} + {errors.shardConfigServers.message} )} From 0a1aac695d0b36dfcd50f8287872b0fd7451c8c3 Mon Sep 17 00:00:00 2001 From: percona-robot <61465387+percona-robot@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:09:44 +0000 Subject: [PATCH 16/16] chore: lint/format --- ui/apps/everest/.e2e/utils/db-cmd-line.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/apps/everest/.e2e/utils/db-cmd-line.ts b/ui/apps/everest/.e2e/utils/db-cmd-line.ts index f53c7084c..ac2e7966c 100644 --- a/ui/apps/everest/.e2e/utils/db-cmd-line.ts +++ b/ui/apps/everest/.e2e/utils/db-cmd-line.ts @@ -82,7 +82,10 @@ export const getPGPassword = async (cluster: string, namespace: string) => { } }; -export const getPSMDBShardingStatus = async (cluster: string, namespace: string) => { +export const getPSMDBShardingStatus = async ( + cluster: string, + namespace: string +) => { try { const command = `kubectl get --namespace ${namespace} DatabaseClusters ${cluster} -ojsonpath='{.spec.sharding.enabled}'`; const output = execSync(command).toString(); @@ -123,7 +126,8 @@ export const queryPSMDB = async ( const clientPod = await getDBClientPod('psmdb', 'db-client'); // Enable replicaSet option if sharding is enabled - const isShardingEnabled = (await getPSMDBShardingStatus(cluster, namespace)) === 'true'; + const isShardingEnabled = + (await getPSMDBShardingStatus(cluster, namespace)) === 'true'; const replicaSetOption = isShardingEnabled ? '' : '&replicaSet=rs0'; try {