IOPSField.tsx129 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { UseFormReturn } from 'react-hook-form' |
| 3 | import { |
| 4 | Button, |
| 5 | FormControl, |
| 6 | FormField, |
| 7 | FormInputGroupInput, |
| 8 | InputGroup, |
| 9 | InputGroupAddon, |
| 10 | } from 'ui' |
| 11 | import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' |
| 12 | |
| 13 | import { DiskStorageSchemaType } from '../DiskManagement.schema' |
| 14 | import { |
| 15 | calculateComputeSizeRequiredForIops, |
| 16 | calculateIOPSPrice, |
| 17 | mapAddOnVariantIdToComputeSize, |
| 18 | } from '../DiskManagement.utils' |
| 19 | import { BillingChangeBadge } from '../ui/BillingChangeBadge' |
| 20 | import { ComputeSizeRecommendationSection } from '../ui/ComputeSizeRecommendationSection' |
| 21 | import { DiskType, RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3 } from '../ui/DiskManagement.constants' |
| 22 | import { DiskManagementIOPSReadReplicas } from '../ui/DiskManagementReadReplicas' |
| 23 | import { useDiskAttributesQuery } from '@/data/config/disk-attributes-query' |
| 24 | |
| 25 | type IOPSFieldProps = { |
| 26 | form: UseFormReturn<DiskStorageSchemaType> |
| 27 | disableInput: boolean |
| 28 | } |
| 29 | |
| 30 | export 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 | } |