diff --git a/packages/manager/.changeset/pr-9569-changed-1692553123699.md b/packages/manager/.changeset/pr-9569-changed-1692553123699.md new file mode 100644 index 00000000000..a2d0da2043e --- /dev/null +++ b/packages/manager/.changeset/pr-9569-changed-1692553123699.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Allow volumes to feature the dynamic pricing model ([#9569](https://github.com/linode/manager/pull/9569)) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx index 67c3bd2a6fc..50920585c41 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx @@ -35,7 +35,8 @@ import { openForEdit, openForResize, } from 'src/store/volumeForm'; -import { StyledTypography, StyledRootGrid } from './CommonLinodeStorage.styles'; + +import { StyledRootGrid, StyledTypography } from './CommonLinodeStorage.styles'; interface DispatchProps { openForClone: ( @@ -57,7 +58,8 @@ interface DispatchProps { openForResize: ( volumeId: number, volumeSize: number, - volumeLabel: string + volumeLabel: string, + volumeRegion: string ) => void; } diff --git a/packages/manager/src/features/Volumes/VolumeCreate/CreateVolumeForm.tsx b/packages/manager/src/features/Volumes/VolumeCreate/CreateVolumeForm.tsx index 9b6340d2bd1..835c1b7e8ad 100644 --- a/packages/manager/src/features/Volumes/VolumeCreate/CreateVolumeForm.tsx +++ b/packages/manager/src/features/Volumes/VolumeCreate/CreateVolumeForm.tsx @@ -19,6 +19,7 @@ import { Typography } from 'src/components/Typography'; import { MAX_VOLUME_SIZE } from 'src/constants'; import EUAgreementCheckbox from 'src/features/Account/Agreements/EUAgreementCheckbox'; import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect'; +import { useFlags } from 'src/hooks/useFlags'; import { reportAgreementSigningError, useAccountAgreements, @@ -44,6 +45,8 @@ import LabelField from '../VolumeDrawer/LabelField'; import NoticePanel from '../VolumeDrawer/NoticePanel'; import SizeField from '../VolumeDrawer/SizeField'; +export const SIZE_FIELD_WIDTH = 160; + const useStyles = makeStyles((theme: Theme) => ({ agreement: { maxWidth: '70%', @@ -65,7 +68,7 @@ const useStyles = makeStyles((theme: Theme) => ({ }, copy: { marginBottom: theme.spacing(), - maxWidth: 680, + maxWidth: 700, }, labelTooltip: { '& .MuiTooltip-tooltip': { @@ -90,7 +93,8 @@ const useStyles = makeStyles((theme: Theme) => ({ width: 320, }, size: { - width: 160, + position: 'relative', + width: SIZE_FIELD_WIDTH, }, tooltip: { '& .MuiTooltip-tooltip': { @@ -114,6 +118,8 @@ type CombinedProps = Props & StateProps; const CreateVolumeForm: React.FC = (props) => { const theme = useTheme(); const classes = useStyles(); + const flags = useFlags(); + const { dcSpecificPricing } = flags; const { history, onSuccess, origin } = props; const { data: profile } = useProfile(); @@ -273,9 +279,19 @@ const CreateVolumeForm: React.FC = (props) => { data-qa-volume-size-help variant="body1" > - A single Volume can range from 10 to {MAX_VOLUME_SIZE} GB in - size and costs $0.10/GB per month.
- Up to eight volumes can be attached to a single Linode. + {dcSpecificPricing ? ( + + A single Volume can range from 10 to 10240 GB in size. Up + to eight Volumes can be attached to a single Linode. + Select a Region to see cost per GB. + + ) : ( + + A single Volume can range from 10 to {MAX_VOLUME_SIZE} GB + in size and costs $0.10/GB per month.
+ Up to eight volumes can be attached to a single Linode. +
+ )} - - - { @@ -377,6 +382,20 @@ const CreateVolumeForm: React.FC = (props) => { width={320} /> + + + void; volumeId: number; volumeLabel: string; @@ -27,7 +30,14 @@ interface Props { const initialValues = { label: '' }; export const CloneVolumeForm = (props: Props) => { - const { onClose, volumeId, volumeLabel, volumeRegion, volumeSize } = props; + const { + flags, + onClose, + volumeId, + volumeLabel, + volumeRegion, + volumeSize, + } = props; const { mutateAsync: cloneVolume } = useCloneVolumeMutation(); @@ -87,7 +97,12 @@ export const CloneVolumeForm = (props: Props) => { onChange={handleChange} value={values.label} /> - + { resetForm(); diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/CreateVolumeForLinodeForm.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/CreateVolumeForLinodeForm.tsx index 6bfa6da6ced..72a1b47215b 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/CreateVolumeForLinodeForm.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/CreateVolumeForLinodeForm.tsx @@ -39,6 +39,8 @@ import SizeField from './SizeField'; import VolumesActionsPanel from './VolumesActionsPanel'; import { modes } from './modes'; +import type { FlagSet } from 'src/featureFlags'; + const useStyles = makeStyles((theme: Theme) => ({ textWrapper: { marginBottom: theme.spacing(1.25), @@ -46,6 +48,7 @@ const useStyles = makeStyles((theme: Theme) => ({ })); interface Props { + flags: FlagSet; linode_id: number; linodeLabel: string; linodeRegion: string; @@ -63,6 +66,7 @@ const CreateVolumeForm: React.FC = (props) => { const classes = useStyles(); const { actions, + flags, linode_id, linodeLabel, linodeRegion, @@ -210,10 +214,12 @@ const CreateVolumeForm: React.FC = (props) => { @@ -245,7 +251,12 @@ const CreateVolumeForm: React.FC = (props) => { value={values.tags} /> - + { diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/PricePanel.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/PricePanel.tsx index 52f97ec9435..a4e76cfbb31 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/PricePanel.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/PricePanel.tsx @@ -3,29 +3,40 @@ import * as React from 'react'; import { Box } from 'src/components/Box'; import { DisplayPrice } from 'src/components/DisplayPrice'; import { MAX_VOLUME_SIZE } from 'src/constants'; +import { getDynamicVolumePrice } from 'src/utilities/pricing/dynamicVolumePrice'; -const getPrice = (size: number) => { - return size * 0.1; -}; - -const getClampedPrice = (newSize: number, currentSize: number) => - newSize >= currentSize - ? newSize <= MAX_VOLUME_SIZE - ? getPrice(newSize) - : getPrice(MAX_VOLUME_SIZE) - : getPrice(currentSize); +import type { FlagSet } from 'src/featureFlags'; interface Props { currentSize: number; + flags: FlagSet; + regionId: string; value: number; } -export const PricePanel = ({ currentSize, value }: Props) => { +export const PricePanel = ({ currentSize, flags, regionId, value }: Props) => { + const getPrice = (size: number) => { + return getDynamicVolumePrice({ + flags, + regionId, + size, + }); + }; + + const getClampedPrice = ( + newSize: number, + currentSize: Props['currentSize'] + ) => + newSize >= currentSize + ? newSize <= MAX_VOLUME_SIZE + ? getPrice(newSize) + : getPrice(MAX_VOLUME_SIZE) + : getPrice(currentSize); const price = getClampedPrice(value, currentSize); return ( - + ); }; diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/ResizeVolumeForm.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/ResizeVolumeForm.tsx index 9c1d0363ade..e0fa9b7f40c 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/ResizeVolumeForm.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/ResizeVolumeForm.tsx @@ -15,22 +15,28 @@ import { PricePanel } from './PricePanel'; import SizeField from './SizeField'; import VolumesActionsPanel from './VolumesActionsPanel'; +import type { FlagSet } from 'src/featureFlags'; + interface Props { + flags: FlagSet; onClose: () => void; onSuccess: (volumeLabel: string, message?: string) => void; readOnly?: boolean; volumeId: number; volumeLabel: string; + volumeRegion: string; volumeSize: number; } export const ResizeVolumeForm = (props: Props) => { const { + flags, onClose, onSuccess, readOnly, volumeId, volumeLabel, + volumeRegion, volumeSize, } = props; @@ -96,16 +102,25 @@ export const ResizeVolumeForm = (props: Props) => { text={`You don't have permissions to edit ${volumeLabel}. Please contact an account administrator for details.`} /> )} + - + + { resetForm(); diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx index fb55928982b..962628f3545 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx @@ -2,26 +2,45 @@ import { Theme } from '@mui/material/styles'; import { makeStyles } from '@mui/styles'; import * as React from 'react'; +import { Box } from 'src/components/Box'; import { FormHelperText } from 'src/components/FormHelperText'; import { InputAdornment } from 'src/components/InputAdornment'; import { TextField } from 'src/components/TextField'; +import { Typography } from 'src/components/Typography'; import { MAX_VOLUME_SIZE } from 'src/constants'; +import { getDynamicVolumePrice } from 'src/utilities/pricing/dynamicVolumePrice'; + +import { SIZE_FIELD_WIDTH } from '../VolumeCreate/CreateVolumeForm'; + +import type { FlagSet } from 'src/featureFlags'; const useStyles = makeStyles((theme: Theme) => ({ createVolumeText: { display: 'block', - marginBottom: theme.spacing(), marginLeft: theme.spacing(1.5), }, + priceDisplay: { + '& p': { + lineHeight: 1, + marginTop: 4, + }, + + left: `calc(${SIZE_FIELD_WIDTH}px + ${theme.spacing(2)})`, + position: 'absolute', + top: 50, + }, })); interface Props { disabled?: boolean; error?: string; + flags: FlagSet; + hasSelectedRegion?: boolean; isFromLinode?: boolean; name: string; onBlur: (e: any) => void; onChange: (e: React.ChangeEvent) => void; + regionId: string; resize?: number; textFieldStyles?: string; value: number; @@ -34,21 +53,45 @@ const SizeField: React.FC = (props) => { const { error, + flags, + hasSelectedRegion, isFromLinode, name, onBlur, onChange, + regionId, resize, textFieldStyles, value, ...rest } = props; + const { dcSpecificPricing } = flags; const helperText = resize ? `This volume can range from ${resize} GB to ${MAX_VOLUME_SIZE} GB in size.` : undefined; - const price = value >= 10 ? (value / 10).toFixed(2) : '0.00'; + const price = getDynamicVolumePrice({ + flags, + regionId, + size: value, + }); + + const legacyHelperText = ( + + {resize || isFromLinode ? ( + 'The size of the new volume in GB.' + ) : ( + ${price}/month + )} + + ); + + const dynamicPricingHelperText = !resize && ( + + Select a Region to see cost per month. + + ); return ( <> @@ -69,13 +112,13 @@ const SizeField: React.FC = (props) => { value={value} {...rest} /> - - {resize || isFromLinode ? ( - 'The size of the new volume in GB.' - ) : ( - ${price}/month - )} - +
+ {dcSpecificPricing + ? hasSelectedRegion + ? legacyHelperText + : dynamicPricingHelperText + : legacyHelperText} +
); }; diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/VolumeDrawer.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/VolumeDrawer.tsx index 67033df188a..0e663aa978c 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/VolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/VolumeDrawer.tsx @@ -1,13 +1,13 @@ import { Grant } from '@linode/api-v4/lib/account'; import * as React from 'react'; import { MapDispatchToProps, connect } from 'react-redux'; -import { compose } from 'recompose'; import { Drawer } from 'src/components/Drawer'; import { WithProfileProps, withProfile, } from 'src/containers/profile.container'; +import { useFlags } from 'src/hooks/useFlags'; import { ApplicationState } from 'src/store'; import { MapState } from 'src/store/types'; import { @@ -27,122 +27,124 @@ import { modes } from './modes'; type CombinedProps = StateProps & DispatchProps & WithProfileProps; -class VolumeDrawer extends React.PureComponent { - render() { - const { - actions, - drawerTitle, - grants, - isOpen, - linodeId, - linodeLabel, - linodeRegion, - message, - mode, - profile, - volumeId, - volumeLabel, - volumePath, - volumeRegion, - volumeSize, - volumeTags, - } = this.props; - - const volumesPermissions = grants.data?.volume; - - const volumePermissions = volumesPermissions?.find( - (v: Grant) => v.id === volumeId - ); - const readOnly = - Boolean(profile.data?.restricted) && - volumePermissions && - volumePermissions.permissions === 'read_only'; - - return ( - - {mode === modes.EDITING && - volumeId !== undefined && - volumeLabel !== undefined && - volumeTags !== undefined && ( - ({ label: v, value: v }))} - /> - )} - {mode === modes.RESIZING && - volumeId !== undefined && - volumeSize !== undefined && - volumeLabel !== undefined && ( - - )} - {mode === modes.CLONING && - volumeId !== undefined && - volumeLabel !== undefined && - volumeRegion !== undefined && - volumeSize !== undefined && ( - - )} - {mode === modes.CREATING_FOR_LINODE && - linodeId !== undefined && - linodeLabel !== undefined && - linodeRegion !== undefined && ( - - )} - {mode === modes.ATTACHING && - linodeId !== undefined && - linodeRegion !== undefined && - linodeLabel !== undefined && ( - - )} - {mode === modes.VIEWING_CONFIG && - volumeLabel !== undefined && - volumePath !== undefined && ( - - )} - {mode === modes.VIEW_RESIZE_INSTRUCTIONS && - volumeLabel !== undefined && ( - - )} - - ); - } -} +const VolumeDrawer = (props: CombinedProps) => { + const { + actions, + drawerTitle, + grants, + isOpen, + linodeId, + linodeLabel, + linodeRegion, + message, + mode, + profile, + volumeId, + volumeLabel, + volumePath, + volumeRegion, + volumeSize, + volumeTags, + } = props; + + const flags = useFlags(); + const volumesPermissions = grants.data?.volume; + const volumePermissions = volumesPermissions?.find( + (v: Grant) => v.id === volumeId + ); + const readOnly = + Boolean(profile.data?.restricted) && + volumePermissions && + volumePermissions.permissions === 'read_only'; + + return ( + + {mode === modes.EDITING && + volumeId !== undefined && + volumeLabel !== undefined && + volumeTags !== undefined && ( + ({ label: v, value: v }))} + /> + )} + {mode === modes.RESIZING && + volumeId !== undefined && + volumeRegion !== undefined && + volumeSize !== undefined && + volumeLabel !== undefined && ( + + )} + {mode === modes.CLONING && + volumeId !== undefined && + volumeLabel !== undefined && + volumeRegion !== undefined && + volumeSize !== undefined && ( + + )} + {mode === modes.CREATING_FOR_LINODE && + linodeId !== undefined && + linodeLabel !== undefined && + linodeRegion !== undefined && ( + + )} + {mode === modes.ATTACHING && + linodeId !== undefined && + linodeRegion !== undefined && + linodeLabel !== undefined && ( + + )} + {mode === modes.VIEWING_CONFIG && + volumeLabel !== undefined && + volumePath !== undefined && ( + + )} + {mode === modes.VIEW_RESIZE_INSTRUCTIONS && volumeLabel !== undefined && ( + + )} + + ); +}; interface DispatchProps { actions: { @@ -251,4 +253,6 @@ const titleFromState = (state: ApplicationState['volumeDrawer']) => { const connected = connect(mapStateToProps, mapDispatchToProps); -export default compose(connected, withProfile)(VolumeDrawer); +const VolumeDrawerWithHOCs = withProfile(connected(VolumeDrawer)); + +export default VolumeDrawerWithHOCs; diff --git a/packages/manager/src/features/Volumes/VolumeTableRow.tsx b/packages/manager/src/features/Volumes/VolumeTableRow.tsx index 10e7cda7878..3d2aaf99f30 100644 --- a/packages/manager/src/features/Volumes/VolumeTableRow.tsx +++ b/packages/manager/src/features/Volumes/VolumeTableRow.tsx @@ -168,6 +168,7 @@ export const VolumeTableRow = React.memo((props: CombinedProps) => { size={size} volumeId={id} volumeLabel={label} + volumeRegion={region} volumeTags={tags} /> diff --git a/packages/manager/src/features/Volumes/VolumesActionMenu.test.tsx b/packages/manager/src/features/Volumes/VolumesActionMenu.test.tsx index 46768773556..da1e482f527 100644 --- a/packages/manager/src/features/Volumes/VolumesActionMenu.test.tsx +++ b/packages/manager/src/features/Volumes/VolumesActionMenu.test.tsx @@ -22,6 +22,7 @@ const props: Props = { size: 50, volumeId: 12345, volumeLabel: '', + volumeRegion: 'east-us', volumeTags: ['abc', 'def'], }; diff --git a/packages/manager/src/features/Volumes/VolumesActionMenu.tsx b/packages/manager/src/features/Volumes/VolumesActionMenu.tsx index aee3bc95dbe..125b841d1c8 100644 --- a/packages/manager/src/features/Volumes/VolumesActionMenu.tsx +++ b/packages/manager/src/features/Volumes/VolumesActionMenu.tsx @@ -4,7 +4,7 @@ import { useTheme } from '@mui/styles'; import { splitAt } from 'ramda'; import * as React from 'react'; -import { ActionMenu, Action } from 'src/components/ActionMenu'; +import { Action, ActionMenu } from 'src/components/ActionMenu'; import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction'; export interface ActionHandlers { @@ -32,7 +32,8 @@ export interface ActionHandlers { openForResize: ( volumeId: number, volumeSize: number, - volumeLabel: string + volumeLabel: string, + volumeRegion: string ) => void; } @@ -47,6 +48,7 @@ export interface Props extends ActionHandlers { size: number; volumeId: number; volumeLabel: string; + volumeRegion: string; volumeTags: string[]; } @@ -67,8 +69,8 @@ export const VolumesActionMenu = (props: Props) => { }; const handleResize = () => { - const { label, openForResize, size, volumeId } = props; - openForResize(volumeId, size, label); + const { label, openForResize, size, volumeId, volumeRegion } = props; + openForResize(volumeId, size, label, volumeRegion); }; const handleClone = () => { diff --git a/packages/manager/src/store/volumeForm/index.ts b/packages/manager/src/store/volumeForm/index.ts index be6a7d93bed..3d413a6ea08 100644 --- a/packages/manager/src/store/volumeForm/index.ts +++ b/packages/manager/src/store/volumeForm/index.ts @@ -116,14 +116,22 @@ interface Resizing extends Action { type: typeof RESIZING; volumeId: number; volumeLabel: string; + volumeRegion: string; volumeSize: number; } export const openForResize = ( volumeId: number, volumeSize: number, - volumeLabel: string -): Resizing => ({ type: RESIZING, volumeId, volumeLabel, volumeSize }); + volumeLabel: string, + volumeRegion: string +): Resizing => ({ + type: RESIZING, + volumeId, + volumeLabel, + volumeRegion, + volumeSize, +}); interface Cloning extends Action { type: typeof CLONING; @@ -191,6 +199,7 @@ export const defaultState: State = { mode: modes.CLOSED, volumeId: undefined, volumeLabel: undefined, + volumeRegion: undefined, volumeSize: undefined, }; @@ -277,6 +286,7 @@ export const volumeForm: Reducer = ( mode: modes.RESIZING, volumeId: action.volumeId, volumeLabel: action.volumeLabel, + volumeRegion: action.volumeRegion, volumeSize: action.volumeSize, }; diff --git a/packages/manager/src/utilities/pricing/dynamicPricing.test.tsx b/packages/manager/src/utilities/pricing/dynamicPricing.test.tsx index 13a3ff7a97c..207149591bb 100644 --- a/packages/manager/src/utilities/pricing/dynamicPricing.test.tsx +++ b/packages/manager/src/utilities/pricing/dynamicPricing.test.tsx @@ -1,35 +1,53 @@ import { getDCSpecificPrice } from './dynamicPricing'; -import { getVolumePrice } from './entityPricing'; +import { getDynamicVolumePrice } from './dynamicVolumePrice'; describe('getDCSpecificPricingDisplay', () => { it('calculates dynamic pricing for a region without an increase', () => { expect( getDCSpecificPrice({ - basePrice: getVolumePrice(20), + basePrice: 20, flags: { dcSpecificPricing: true }, regionId: 'us-east', }) - ).toBe('2.00'); + ).toBe('20.00'); }); it('calculates dynamic pricing for a region with an increase', () => { expect( getDCSpecificPrice({ - basePrice: getVolumePrice(20), + basePrice: 20, flags: { dcSpecificPricing: true }, regionId: 'id-cgk', }) + ).toBe('24.00'); + + expect( + getDCSpecificPrice({ + basePrice: 20, + flags: { dcSpecificPricing: true }, + regionId: 'br-gru', + }) + ).toBe('26.00'); + }); + + it('calculates dynamic pricing for a volumes based on size', () => { + expect( + getDynamicVolumePrice({ + flags: { dcSpecificPricing: true }, + regionId: 'id-cgk', + size: 20, + }) ).toBe('2.40'); }); it('does not apply dc specific pricing when flag is off', () => { expect( getDCSpecificPrice({ - basePrice: getVolumePrice(20), + basePrice: 20, flags: { dcSpecificPricing: false }, regionId: 'id-cgk', }) - ).toBe('2.00'); + ).toBe('20.00'); }); it('handles default case correctly', () => { diff --git a/packages/manager/src/utilities/pricing/dynamicPricing.ts b/packages/manager/src/utilities/pricing/dynamicPricing.ts index fa66c20f9d2..0a0cdec00e0 100644 --- a/packages/manager/src/utilities/pricing/dynamicPricing.ts +++ b/packages/manager/src/utilities/pricing/dynamicPricing.ts @@ -4,7 +4,7 @@ import type { FlagSet } from 'src/featureFlags'; export interface DataCenterPricingOptions { /** * The base price for an entity. - * @example 5.00 + * @example 5 or 5.50 */ basePrice: number; /** @@ -19,8 +19,7 @@ export interface DataCenterPricingOptions { regionId: Region['id'] | undefined; } -// The key is a region id and the value is the percentage -// increase in price. +// The key is a region id and the value is the percentage increase in price. const priceIncreaseMap = { 'br-gru': 0.3, // Sao Paulo 'id-cgk': 0.2, // Jakarta @@ -48,8 +47,7 @@ export const getDCSpecificPrice = ({ const increaseFactor = priceIncreaseMap[regionId] as number | undefined; if (increaseFactor !== undefined) { - // If increaseFactor is defined, it means the region has a price increase - // and we should apply it. + // If increaseFactor is defined, it means the region has a price increase and we should apply it. const increase = basePrice * increaseFactor; return (basePrice + increase).toFixed(2); diff --git a/packages/manager/src/utilities/pricing/dynamicVolumePrice.test.tsx b/packages/manager/src/utilities/pricing/dynamicVolumePrice.test.tsx new file mode 100644 index 00000000000..6e5732fc1f4 --- /dev/null +++ b/packages/manager/src/utilities/pricing/dynamicVolumePrice.test.tsx @@ -0,0 +1,13 @@ +import { getDynamicVolumePrice } from './dynamicVolumePrice'; + +describe('getDCSpecificPricingDisplay', () => { + it('calculates dynamic pricing for a volumes based on size', () => { + expect( + getDynamicVolumePrice({ + flags: { dcSpecificPricing: true }, + regionId: 'id-cgk', + size: 20, + }) + ).toBe('2.40'); + }); +}); diff --git a/packages/manager/src/utilities/pricing/dynamicVolumePrice.ts b/packages/manager/src/utilities/pricing/dynamicVolumePrice.ts new file mode 100644 index 00000000000..9e7444bbe95 --- /dev/null +++ b/packages/manager/src/utilities/pricing/dynamicVolumePrice.ts @@ -0,0 +1,25 @@ +import { getDCSpecificPrice } from './dynamicPricing'; + +import type { FlagSet } from 'src/featureFlags'; + +interface Options { + flags: FlagSet; + regionId: string; + size: number; +} + +/** + * Gets the base price of a volume based on its size. + * + * @param size The size of a Volume in GBs + * @returns the base price of a volume based on its size + */ +export const getDynamicVolumePrice = ({ flags, regionId, size }: Options) => { + const pricePerSize = size ? (size >= 10 ? size / 10 : 0) : 0; + + return getDCSpecificPrice({ + basePrice: pricePerSize, + flags, + regionId, + }); +}; diff --git a/packages/manager/src/utilities/pricing/entityPricing.ts b/packages/manager/src/utilities/pricing/entityPricing.ts deleted file mode 100644 index 465670ceeb3..00000000000 --- a/packages/manager/src/utilities/pricing/entityPricing.ts +++ /dev/null @@ -1,11 +0,0 @@ -// These utils will be used for pricing calculations based on a size variation - -/** - * Gets the base price of a volume based on its size. - * - * @param size The size of a Volume in GBs - * @returns the base price of a volume based on its size - */ -export const getVolumePrice = (size?: number) => { - return size ? (size >= 10 ? size / 10 : 0) : 0; -};