ThroughputField.tsx146 lines · main
1import { useParams } from 'common'
2import { AnimatePresence, motion } from 'framer-motion'
3import { useEffect } from 'react'
4import { UseFormReturn } from 'react-hook-form'
5import {
6 FormControl,
7 FormField,
8 FormInputGroupInput,
9 InputGroup,
10 InputGroupAddon,
11 InputGroupText,
12} from 'ui'
13import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
14
15import { DiskStorageSchemaType } from '../DiskManagement.schema'
16import { calculateThroughputPrice } from '../DiskManagement.utils'
17import { BillingChangeBadge } from '../ui/BillingChangeBadge'
18import {
19 DISK_LIMITS,
20 DiskType,
21 RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3,
22} from '../ui/DiskManagement.constants'
23import { DiskManagementThroughputReadReplicas } from '../ui/DiskManagementReadReplicas'
24import { useDiskAttributesQuery } from '@/data/config/disk-attributes-query'
25
26type ThroughputFieldProps = {
27 form: UseFormReturn<DiskStorageSchemaType>
28 disableInput: boolean
29}
30
31export function ThroughputField({ form, disableInput }: ThroughputFieldProps) {
32 const { ref: projectRef } = useParams()
33
34 const { control, formState, setValue, getValues, watch } = form
35
36 const watchedStorageType = watch('storageType')
37 const watchedTotalSize = watch('totalSize')
38 const watchedComputeSize = watch('computeSize')
39 const throughput_mbps = formState.defaultValues?.throughput
40
41 useDiskAttributesQuery({ projectRef })
42
43 const throughputPrice = calculateThroughputPrice({
44 storageType: form.getValues('storageType') as DiskType,
45 newThroughput: form.getValues('throughput') || 0,
46 oldThroughput: form.formState.defaultValues?.throughput || 0,
47 })
48
49 const disableIopsInput =
50 RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3.includes(watchedComputeSize) && watchedStorageType === 'gp3'
51
52 // Watch storageType and allocatedStorage to adjust constraints dynamically
53 useEffect(() => {
54 if (watchedStorageType === 'io2') {
55 setValue('throughput', undefined) // Throughput is not configurable for 'io2'
56 } else if (watchedStorageType === 'gp3') {
57 // Ensure throughput is within the allowed range if it's greater than or equal to 400 GB
58 const currentThroughput = form.getValues('throughput')
59 const { minThroughput, maxThroughput } = DISK_LIMITS[DiskType.GP3]
60 if (
61 !currentThroughput ||
62 currentThroughput < minThroughput ||
63 currentThroughput > maxThroughput
64 ) {
65 setValue('throughput', minThroughput) // Reset to default if undefined or out of bounds
66 }
67 }
68 }, [watchedStorageType, watchedTotalSize, setValue, form])
69
70 return (
71 <AnimatePresence initial={false}>
72 {getValues('storageType') === 'gp3' && (
73 <motion.div
74 key="throughPutContainer"
75 initial={{ opacity: 0, x: -4, height: 0 }}
76 animate={{ opacity: 1, x: 0, height: 'auto' }}
77 exit={{ opacity: 0, x: -4, height: 0 }}
78 transition={{ duration: 0.1 }}
79 style={{ overflow: 'hidden' }}
80 >
81 <FormField
82 name="throughput"
83 control={control}
84 render={({ field }) => (
85 <FormItemLayout
86 label="Throughput"
87 layout="horizontal"
88 description={
89 <span className="flex flex-col gap-y-2">
90 <p>Higher throughput suits applications with high data transfer needs.</p>
91 {!formState.errors.throughput && (
92 <DiskManagementThroughputReadReplicas
93 isDirty={formState.dirtyFields.throughput !== undefined}
94 oldThroughput={throughput_mbps ?? 0}
95 newThroughput={field.value ?? 0}
96 oldStorageType={formState.defaultValues?.storageType as DiskType}
97 newStorageType={getValues('storageType') as DiskType}
98 />
99 )}
100 </span>
101 }
102 labelOptional={
103 <>
104 <BillingChangeBadge
105 show={
106 formState.isDirty &&
107 formState.dirtyFields.throughput &&
108 !formState.errors.throughput
109 }
110 beforePrice={Number(throughputPrice.oldPrice)}
111 afterPrice={Number(throughputPrice.newPrice)}
112 className="mb-2"
113 />
114 <p className="text-foreground-lighter">
115 Amount of data read/written per second.
116 </p>
117 </>
118 }
119 >
120 <FormControl className="max-w-32">
121 <InputGroup>
122 <FormInputGroupInput
123 type="number"
124 {...field}
125 value={field.value}
126 onChange={(e) => {
127 setValue('throughput', e.target.valueAsNumber, {
128 shouldDirty: true,
129 shouldValidate: true,
130 })
131 }}
132 disabled={disableInput || disableIopsInput || watchedStorageType === 'io2'}
133 />
134 <InputGroupAddon align="inline-end">
135 <InputGroupText>MB/s</InputGroupText>
136 </InputGroupAddon>
137 </InputGroup>
138 </FormControl>
139 </FormItemLayout>
140 )}
141 />
142 </motion.div>
143 )}
144 </AnimatePresence>
145 )
146}