IOPSField.tsx129 lines · main
1import { useParams } from 'common'
2import { UseFormReturn } from 'react-hook-form'
3import {
4 Button,
5 FormControl,
6 FormField,
7 FormInputGroupInput,
8 InputGroup,
9 InputGroupAddon,
10} from 'ui'
11import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
12
13import { DiskStorageSchemaType } from '../DiskManagement.schema'
14import {
15 calculateComputeSizeRequiredForIops,
16 calculateIOPSPrice,
17 mapAddOnVariantIdToComputeSize,
18} from '../DiskManagement.utils'
19import { BillingChangeBadge } from '../ui/BillingChangeBadge'
20import { ComputeSizeRecommendationSection } from '../ui/ComputeSizeRecommendationSection'
21import { DiskType, RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3 } from '../ui/DiskManagement.constants'
22import { DiskManagementIOPSReadReplicas } from '../ui/DiskManagementReadReplicas'
23import { useDiskAttributesQuery } from '@/data/config/disk-attributes-query'
24
25type IOPSFieldProps = {
26 form: UseFormReturn<DiskStorageSchemaType>
27 disableInput: boolean
28}
29
30export function IOPSField({ form, disableInput }: IOPSFieldProps) {
31 const { ref: projectRef } = useParams()
32 const { control, formState, setValue, trigger, getValues, watch } = form
33
34 const watchedStorageType = watch('storageType')
35 const watchedComputeSize = watch('computeSize')
36 const watchedIOPS = watch('provisionedIOPS') ?? 0
37
38 const { isError } = useDiskAttributesQuery({ projectRef })
39
40 const iopsPrice = calculateIOPSPrice({
41 oldStorageType: formState.defaultValues?.storageType as DiskType,
42 oldProvisionedIOPS: formState.defaultValues?.provisionedIOPS || 0,
43 newStorageType: getValues('storageType') as DiskType,
44 newProvisionedIOPS: getValues('provisionedIOPS'),
45 })
46
47 const disableIopsInput =
48 RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3.includes(watchedComputeSize) && watchedStorageType === 'gp3'
49
50 return (
51 <FormField
52 control={control}
53 name="provisionedIOPS"
54 render={({ field }) => {
55 const reccomendedComputeSize = calculateComputeSizeRequiredForIops(watchedIOPS)
56 return (
57 <FormItemLayout
58 layout="horizontal"
59 label="IOPS"
60 id={field.name}
61 description={
62 <span className="flex flex-col gap-y-2">
63 <p>Use higher IOPS for high-throughput apps.</p>
64 <ComputeSizeRecommendationSection
65 form={form}
66 actions={
67 <Button
68 type="default"
69 onClick={() => {
70 setValue('computeSize', reccomendedComputeSize ?? 'ci_nano')
71 trigger('provisionedIOPS')
72 }}
73 >
74 Update to {mapAddOnVariantIdToComputeSize(reccomendedComputeSize)}
75 </Button>
76 }
77 />
78 {!formState.errors.provisionedIOPS && (
79 <DiskManagementIOPSReadReplicas
80 isDirty={formState.dirtyFields.provisionedIOPS !== undefined}
81 oldIOPS={formState.defaultValues?.provisionedIOPS ?? 0}
82 newIOPS={field.value}
83 oldStorageType={formState.defaultValues?.storageType as DiskType}
84 newStorageType={getValues('storageType') as DiskType}
85 />
86 )}
87 </span>
88 }
89 labelOptional={
90 <>
91 <BillingChangeBadge
92 show={
93 (watchedStorageType !== formState.defaultValues?.storageType ||
94 (watchedStorageType === 'gp3' &&
95 field.value !== formState.defaultValues?.provisionedIOPS)) &&
96 !formState.errors.provisionedIOPS &&
97 !disableIopsInput
98 }
99 beforePrice={Number(iopsPrice.oldPrice)}
100 afterPrice={Number(iopsPrice.newPrice)}
101 className="mb-2"
102 />
103 <p className="text-foreground-lighter">Input/output operations per second.</p>
104 </>
105 }
106 >
107 <FormControl className="max-w-32">
108 <InputGroup>
109 <FormInputGroupInput
110 type="number"
111 {...field}
112 value={field.value}
113 disabled={disableInput || disableIopsInput || isError}
114 onChange={(e) => {
115 setValue('provisionedIOPS', e.target.valueAsNumber, {
116 shouldDirty: true,
117 shouldValidate: true,
118 })
119 }}
120 />
121 <InputGroupAddon align="inline-end">IOPS</InputGroupAddon>
122 </InputGroup>
123 </FormControl>
124 </FormItemLayout>
125 )
126 }}
127 />
128 )
129}