mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
r2np: new cost (#32632)
* add new cost estimate * fix api types * update api types * restore api.d.ts * fix types * fix utils * clean utils getComputeHourlyPrice --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
1 parent
2781fb9285
commit
0e930cf940
6 files changed
+118
-34
No files matched your search
+21
-31
@@ -1,32 +1,10 @@
|
||||
import { instanceSizeSpecs } from 'data/projects/new-project.constants'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
|
||||
export const AdditionalMonthlySpend = () => {
|
||||
const { project } = useProjectContext()
|
||||
|
||||
/**
|
||||
* New project will have the same compute size and disk size as the original project
|
||||
*/
|
||||
function getAdditionalMonthlySpend() {
|
||||
const currentProjectComputeSize = project?.infra_compute_size
|
||||
if (!currentProjectComputeSize) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (currentProjectComputeSize === 'nano') {
|
||||
return null
|
||||
}
|
||||
|
||||
const additionalMonthlySpend = instanceSizeSpecs[currentProjectComputeSize]
|
||||
|
||||
return additionalMonthlySpend
|
||||
}
|
||||
|
||||
const additionalMonthlySpend = getAdditionalMonthlySpend()
|
||||
if (!additionalMonthlySpend) {
|
||||
return null
|
||||
}
|
||||
import { NewProjectPrice } from './RestoreToNewProject.utils'
|
||||
|
||||
export const AdditionalMonthlySpend = ({
|
||||
additionalMonthlySpend,
|
||||
}: {
|
||||
additionalMonthlySpend: NewProjectPrice
|
||||
}) => {
|
||||
return (
|
||||
<div className="text-sm text-foreground-lighter border-t p-5">
|
||||
<p>
|
||||
@@ -36,9 +14,21 @@ export const AdditionalMonthlySpend = () => {
|
||||
Project Settings > Compute and Disk
|
||||
</span>
|
||||
</p>
|
||||
<div className="flex justify-between text-foreground mt-2">
|
||||
<p>Additional Monthly Compute + Disk Cost</p>
|
||||
<p className="font-mono text-right text-brand">${additionalMonthlySpend.priceMonthly}</p>
|
||||
<div className="flex flex-col gap-2 text-foreground mt-4">
|
||||
<div className="flex justify-between">
|
||||
<p>Additional Monthly Compute</p>
|
||||
<p className="font-mono text-right text-light">${additionalMonthlySpend.computePrice}</p>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p>Additional Monthly Disk</p>
|
||||
<p className="font-mono text-right text-light">${additionalMonthlySpend.diskPrice}</p>
|
||||
</div>
|
||||
<div className="flex justify-between border-t pt-2">
|
||||
<p>Total</p>
|
||||
<p className="font-mono text-right text-brand">
|
||||
${additionalMonthlySpend.computePrice + additionalMonthlySpend.diskPrice}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+4
-1
@@ -11,17 +11,20 @@ import {
|
||||
DialogTitle,
|
||||
} from 'ui'
|
||||
import { AdditionalMonthlySpend } from './AdditionalMonthlySpend'
|
||||
import { NewProjectPrice } from './RestoreToNewProject.utils'
|
||||
|
||||
interface ConfirmRestoreDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (value: boolean) => void
|
||||
onSelectContinue: () => void
|
||||
additionalMonthlySpend: NewProjectPrice
|
||||
}
|
||||
|
||||
export const ConfirmRestoreDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSelectContinue,
|
||||
additionalMonthlySpend,
|
||||
}: ConfirmRestoreDialogProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const organization = useSelectedOrganization()
|
||||
@@ -64,7 +67,7 @@ export const ConfirmRestoreDialog = ({
|
||||
</ul>
|
||||
</ul>
|
||||
</DialogSection>
|
||||
<AdditionalMonthlySpend />
|
||||
<AdditionalMonthlySpend additionalMonthlySpend={additionalMonthlySpend} />
|
||||
<DialogFooter>
|
||||
<Button type="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
|
||||
+4
-1
@@ -30,6 +30,7 @@ import {
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { AdditionalMonthlySpend } from './AdditionalMonthlySpend'
|
||||
import { NewProjectPrice } from './RestoreToNewProject.utils'
|
||||
|
||||
interface CreateNewProjectDialogProps {
|
||||
open: boolean
|
||||
@@ -37,6 +38,7 @@ interface CreateNewProjectDialogProps {
|
||||
recoveryTimeTarget: number | null
|
||||
onOpenChange: (value: boolean) => void
|
||||
onCloneSuccess: () => void
|
||||
additionalMonthlySpend: NewProjectPrice
|
||||
}
|
||||
|
||||
export const CreateNewProjectDialog = ({
|
||||
@@ -45,6 +47,7 @@ export const CreateNewProjectDialog = ({
|
||||
recoveryTimeTarget,
|
||||
onOpenChange,
|
||||
onCloneSuccess,
|
||||
additionalMonthlySpend,
|
||||
}: CreateNewProjectDialogProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const organization = useSelectedOrganization()
|
||||
@@ -188,7 +191,7 @@ export const CreateNewProjectDialog = ({
|
||||
)}
|
||||
/>
|
||||
</DialogSection>
|
||||
<AdditionalMonthlySpend />
|
||||
<AdditionalMonthlySpend additionalMonthlySpend={additionalMonthlySpend} />
|
||||
<DialogFooter>
|
||||
<Button htmlType="reset" type="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { InfraInstanceSize } from 'components/interfaces/DiskManagement/DiskManagement.types'
|
||||
import {
|
||||
calculateComputeSizePrice,
|
||||
calculateDiskSizePrice,
|
||||
} from 'components/interfaces/DiskManagement/DiskManagement.utils'
|
||||
import { DiskType } from 'components/interfaces/DiskManagement/ui/DiskManagement.constants'
|
||||
import { instanceSizeSpecs } from 'data/projects/new-project.constants'
|
||||
import { PlanId } from 'data/subscriptions/types'
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Calculates the monthly price for a new project based on the target volume size and compute size
|
||||
*
|
||||
* @param targetVolumeSizeGb - The target volume size in GB
|
||||
* @param targetComputeSize - The target compute size
|
||||
* @returns The disk price and compute price for the new project
|
||||
*/
|
||||
|
||||
export type NewProjectPrice = {
|
||||
diskPrice: number
|
||||
computePrice: number
|
||||
}
|
||||
export function projectSpecToMonthlyPrice({
|
||||
targetVolumeSizeGb,
|
||||
targetComputeSize,
|
||||
planId,
|
||||
storageType,
|
||||
}: {
|
||||
targetVolumeSizeGb: number
|
||||
targetComputeSize: InfraInstanceSize
|
||||
planId: PlanId
|
||||
storageType: DiskType
|
||||
}): NewProjectPrice {
|
||||
const diskPrice = calculateDiskSizePrice({
|
||||
planId,
|
||||
oldSize: 0,
|
||||
oldStorageType: storageType,
|
||||
newSize: targetVolumeSizeGb,
|
||||
newStorageType: storageType,
|
||||
numReplicas: 0,
|
||||
})
|
||||
|
||||
const computePrice = calculateComputeSizePrice({
|
||||
availableOptions: [
|
||||
{ identifier: targetComputeSize, price: getComputeHourlyPrice(targetComputeSize) },
|
||||
],
|
||||
oldComputeSize: 'nano', // not used for r2np
|
||||
newComputeSize: targetComputeSize,
|
||||
plan: planId,
|
||||
})
|
||||
|
||||
return {
|
||||
diskPrice: Number(diskPrice.newPrice) || 0,
|
||||
computePrice: Number(computePrice.newPrice) || 0,
|
||||
}
|
||||
}
|
||||
|
||||
function getComputeHourlyPrice(computeSize: InfraInstanceSize): number {
|
||||
if (computeSize === 'nano') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return instanceSizeSpecs[computeSize]?.priceHourly
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { get, handleError } from 'data/fetchers'
|
||||
import { projectKeys } from './keys'
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ResponseError } from 'types'
|
||||
import { InfraInstanceSize } from 'components/interfaces/DiskManagement/DiskManagement.types'
|
||||
|
||||
export async function getCloneBackups(projectRef?: string) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
@@ -17,7 +18,12 @@ export async function getCloneBackups(projectRef?: string) {
|
||||
export type CloneBackupsData = Awaited<ReturnType<typeof getCloneBackups>>
|
||||
export type CloneBackupsError = ResponseError
|
||||
|
||||
export const useCloneBackupsQuery = <TData = CloneBackupsData>(
|
||||
export const useCloneBackupsQuery = <
|
||||
TData = CloneBackupsData & {
|
||||
target_volume_size_gb: number
|
||||
target_compute_size: InfraInstanceSize
|
||||
},
|
||||
>(
|
||||
{ projectRef }: { projectRef?: string },
|
||||
options: UseQueryOptions<CloneBackupsData, CloneBackupsError, TData> = {}
|
||||
) => {
|
||||
|
||||
@@ -30,6 +30,10 @@ import type { NextPageWithLayout } from 'types'
|
||||
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Badge, Button } from 'ui'
|
||||
import { Admonition, TimestampInfo } from 'ui-patterns'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { projectSpecToMonthlyPrice } from 'components/interfaces/Database/Backups/RestoreToNewProject/RestoreToNewProject.utils'
|
||||
import { useDiskAttributesQuery } from 'data/config/disk-attributes-query'
|
||||
import { DiskType } from 'components/interfaces/DiskManagement/ui/DiskManagement.constants'
|
||||
import { InfraInstanceSize } from 'components/interfaces/DiskManagement/DiskManagement.types'
|
||||
|
||||
const RestoreToNewProjectPage: NextPageWithLayout = () => {
|
||||
return (
|
||||
@@ -86,6 +90,11 @@ const RestoreToNewProject = () => {
|
||||
const PHYSICAL_BACKUPS_ENABLED = project?.is_physical_backups_enabled
|
||||
const dbVersion = getDatabaseMajorVersion(project?.dbVersion ?? '')
|
||||
const IS_PG15_OR_ABOVE = dbVersion >= 15
|
||||
const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb
|
||||
const targetComputeSize = cloneBackups?.target_compute_size
|
||||
const planId = subscription?.plan?.id ?? 'free'
|
||||
const { data } = useDiskAttributesQuery({ projectRef: project?.ref })
|
||||
const storageType = data?.attributes?.type ?? 'gp3'
|
||||
|
||||
const {
|
||||
data: cloneStatus,
|
||||
@@ -310,6 +319,13 @@ const RestoreToNewProject = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const additionalMonthlySpend = projectSpecToMonthlyPrice({
|
||||
targetVolumeSizeGb: targetVolumeSizeGb ?? 0,
|
||||
targetComputeSize: targetComputeSize ?? 'nano',
|
||||
planId: planId ?? 'free',
|
||||
storageType: storageType as DiskType,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ConfirmRestoreDialog
|
||||
@@ -319,11 +335,13 @@ const RestoreToNewProject = () => {
|
||||
setShowConfirmationDialog(false)
|
||||
setShowNewProjectDialog(true)
|
||||
}}
|
||||
additionalMonthlySpend={additionalMonthlySpend}
|
||||
/>
|
||||
<CreateNewProjectDialog
|
||||
open={showNewProjectDialog}
|
||||
selectedBackupId={selectedBackupId}
|
||||
recoveryTimeTarget={recoveryTimeTarget}
|
||||
additionalMonthlySpend={additionalMonthlySpend}
|
||||
onOpenChange={setShowNewProjectDialog}
|
||||
onCloneSuccess={() => {
|
||||
refetchCloneStatus()
|
||||
|
||||
Reference in new issue
Block a user