ThroughputField.tsx 5.5 KB

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