Files
supabase/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog.hooks.ts
Alaister YoungandAlaister Young 4a18670367 [FE-2417] fix(studio): update disk EBS UI copy to match new limits (#47646)
Updates the dashboard disk-management copy to match the new AWS EBS
modification limits (already reflected in the docs): from the old fixed
"4-hour cooldown / once every 4 hours" framing to "up to 4 modifications
within a rolling 24-hour window".

This is a copy-only change plus one small logic-constant alignment.
Timer/countdown behavior is unchanged — this only updates wording to
bring the dashboard in line with the docs.

**Changed:**
- `DiskSpaceBar` autoscaling tooltip, `DiskCountdownRadial` card,
`DiskSizeConfiguration` "Importing a lot of data?" alert,
`DiskSizeConfigurationModal` alert title + both countdown branches,
`DiskManagementReviewAndSubmitDialog` IOPS + disk-size row descriptions,
and two code comments — all reworded to the new "4 per rolling 24-hour
window" framing
- `DiskSizeConfigurationModal` countdown now derives from the shared
`COOLDOWN_DURATION` constant (4h) instead of a stale hardcoded `6 * 60`
(6h), so the legacy resize path matches the newer disk-attributes path

## To test

- Open a Pro AWS project → **Settings → Compute and Disk** → hover the
**Autoscaling** pill on the disk bar: tooltip should read "…limited to 4
within a rolling 24-hour window" (no "once every 4 hours")
- Change IOPS only → **Review changes** → IOPS row description shows the
new "rolling 24-hour window… as soon as the previous one completes" copy
- Change disk size → **Review changes** → Disk size row shows "You can
modify disk attributes up to 4 times within a rolling 24-hour window"
(not "For 4 hours after changes…")
- On a non-AWS Pro project → **Database → Settings → Increase disk
size**: modal title reads "Disk modifications are limited to 4 per
rolling 24-hour window"; any "resize again in ~X" countdown is bounded
by 4 hours, not 6
- Sanity: none of the old "4-hour cooldown" / "once every 4 hours"
strings appear anywhere in the disk UI

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated disk resizing messages across the app to reflect a rolling
24-hour limit instead of a fixed 4-hour cooldown.
* Clarified when disk size, IOPS, and throughput changes are available
again, including more accurate next-available timing.
* Improved warning copy in disk configuration and review dialogs so
limit messages are consistent and easier to understand.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-07-06 23:16:00 +08:00

207 lines
6.8 KiB
TypeScript

import { useMemo } from 'react'
import { UseFormReturn } from 'react-hook-form'
import { DiskStorageSchemaType } from '../DiskManagement.schema'
import { ComputeInstanceAddonVariantId } from '../DiskManagement.types'
import {
calculateComputeSizePrice,
calculateDiskSizePrice,
calculateIOPSPrice,
calculateThroughputPrice,
getAvailableComputeOptions,
mapAddOnVariantIdToComputeSize,
} from '../DiskManagement.utils'
import { DiskType } from '../ui/DiskManagement.constants'
import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import {
useIsAwsNimbusCloudProvider,
useSelectedProjectQuery,
} from '@/hooks/misc/useSelectedProject'
const COMPUTE_SIZES_BELOW_LARGE: Array<ComputeInstanceAddonVariantId> = [
'ci_nano',
'ci_micro',
'ci_small',
'ci_medium',
]
export function useDiskManagementReviewChanges(
form: UseFormReturn<DiskStorageSchemaType>,
numReplicas: number
) {
const { data: project } = useSelectedProjectQuery()
const { data: org } = useSelectedOrganizationQuery()
const isAwsNimbus = useIsAwsNimbusCloudProvider()
const { data: addons } = useProjectAddonsQuery({ projectRef: project?.ref })
const isAwsK8sProject = project?.cloud_provider === 'AWS_K8S'
const planId = org?.plan.id ?? 'free'
const availableAddons = useMemo(() => addons?.available_addons ?? [], [addons])
const availableOptions = useMemo(
() => getAvailableComputeOptions(availableAddons, project?.cloud_provider),
[availableAddons, project?.cloud_provider]
)
// --- Prices ---
const computeSizePrice = calculateComputeSizePrice({
availableOptions,
oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro',
newComputeSize: form.getValues('computeSize'),
plan: planId,
})
const diskSizePrice = calculateDiskSizePrice({
planId,
oldSize: form.formState.defaultValues?.totalSize || 0,
oldStorageType: form.formState.defaultValues?.storageType as DiskType,
newSize: form.getValues('totalSize'),
newStorageType: form.getValues('storageType') as DiskType,
numReplicas,
})
const iopsPrice = calculateIOPSPrice({
oldStorageType: form.formState.defaultValues?.storageType as DiskType,
oldProvisionedIOPS: form.formState.defaultValues?.provisionedIOPS || 0,
newStorageType: form.getValues('storageType') as DiskType,
newProvisionedIOPS: form.getValues('provisionedIOPS'),
numReplicas,
})
const throughputPrice = calculateThroughputPrice({
storageType: form.getValues('storageType') as DiskType,
newThroughput: form.getValues('throughput') || 0,
oldThroughput: form.formState.defaultValues?.throughput || 0,
numReplicas,
})
const totalBeforePrice =
Number(computeSizePrice.oldPrice) +
Number(diskSizePrice.oldPrice) +
Number(iopsPrice.oldPrice) +
Number(throughputPrice.oldPrice)
const totalAfterPrice =
Number(computeSizePrice.newPrice) +
Number(diskSizePrice.newPrice) +
Number(iopsPrice.newPrice) +
Number(throughputPrice.newPrice)
// --- Change flags ---
const hasComputeChanges =
form.formState.defaultValues?.computeSize !== form.getValues('computeSize')
const hasTotalSizeChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.totalSize !== form.getValues('totalSize')
const hasStorageTypeChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.storageType !== form.getValues('storageType')
const hasThroughputChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.throughput !== form.getValues('throughput')
const hasIOPSChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.provisionedIOPS !== form.getValues('provisionedIOPS')
const hasGrowthPercentChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.growthPercent !== form.getValues('growthPercent')
const hasMinIncrementChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.minIncrementGb !== form.getValues('minIncrementGb')
const hasMaxSizeChanges =
!isAwsK8sProject &&
!isAwsNimbus &&
form.formState.defaultValues?.maxSizeGb !== form.getValues('maxSizeGb')
// --- Derived predicates ---
const storageTypeAfter = form.getValues('storageType') as DiskType
// Show hero whenever any line-item price actually changes, not just compute
const anyBillableDiskChange =
Number(diskSizePrice.newPrice) !== Number(diskSizePrice.oldPrice) ||
Number(iopsPrice.newPrice) !== Number(iopsPrice.oldPrice) ||
Number(throughputPrice.newPrice) !== Number(throughputPrice.oldPrice)
// Show cooldown warning whenever any disk attribute that counts toward the 24-hour modification limit changes
const anyDiskAttributeChange = hasIOPSChanges || hasStorageTypeChanges || hasTotalSizeChanges
// Show extended downtime warning when resizing to/from a size below large
const hasExtendedDowntimeRisk =
hasComputeChanges &&
(COMPUTE_SIZES_BELOW_LARGE.includes(
(form.formState.defaultValues?.computeSize ?? 'ci_nano') as ComputeInstanceAddonVariantId
) ||
COMPUTE_SIZES_BELOW_LARGE.includes(
form.getValues('computeSize') as ComputeInstanceAddonVariantId
))
// Throughput is only a user-configurable, separately-billed attribute for GP3. For IO2 it is
// derived from provisioned IOPS (0.256 MiB/s per IOPS) and isn't surfaced as its own value, so
// the form clears it to 0 — rendering a misleading "→ 0 MB/s". Only show the row when the
// resulting storage type is GP3; any GP3→IO2 throughput price delta still lands in the total.
const showThroughputRow =
!isAwsK8sProject &&
!isAwsNimbus &&
storageTypeAfter === 'gp3' &&
(hasThroughputChanges || hasStorageTypeChanges)
const hasAnyBreakdownRows =
hasComputeChanges ||
hasStorageTypeChanges ||
hasIOPSChanges ||
showThroughputRow ||
hasTotalSizeChanges ||
hasGrowthPercentChanges ||
hasMinIncrementChanges ||
hasMaxSizeChanges
// --- Labels ---
const oldComputeLabel = mapAddOnVariantIdToComputeSize(
form.formState.defaultValues?.computeSize ?? 'ci_nano'
)
const newComputeLabel = mapAddOnVariantIdToComputeSize(form.getValues('computeSize'))
return {
// prices
computeSizePrice,
diskSizePrice,
iopsPrice,
throughputPrice,
totalBeforePrice,
totalAfterPrice,
// change flags
hasComputeChanges,
hasTotalSizeChanges,
hasStorageTypeChanges,
hasThroughputChanges,
hasIOPSChanges,
hasGrowthPercentChanges,
hasMinIncrementChanges,
hasMaxSizeChanges,
// derived predicates
anyBillableDiskChange,
anyDiskAttributeChange,
showThroughputRow,
hasAnyBreakdownRows,
hasExtendedDowntimeRisk,
// labels
oldComputeLabel,
newComputeLabel,
}
}