IOPSField.tsx 4.8 KB

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