Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [M3-6971] Add dynamic pricing model to Volume creation flows #9569

Merged
merged 10 commits into from
Aug 22, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@linode/manager": Changed
"@linode/manager": Upcoming Features

---

Allow volumes to feature the dynamic pricing model ([#9569](https://github.com/linode/manager/pull/9569))
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand All @@ -57,7 +58,8 @@ interface DispatchProps {
openForResize: (
volumeId: number,
volumeSize: number,
volumeLabel: string
volumeLabel: string,
volumeRegion: string
) => void;
}

Expand Down
abailly-akamai marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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%',
Expand Down Expand Up @@ -90,7 +93,8 @@ const useStyles = makeStyles((theme: Theme) => ({
width: 320,
},
size: {
width: 160,
position: 'relative',
width: SIZE_FIELD_WIDTH,
},
tooltip: {
'& .MuiTooltip-tooltip': {
Expand All @@ -114,6 +118,8 @@ type CombinedProps = Props & StateProps;
const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
const theme = useTheme();
const classes = useStyles();
const flags = useFlags();
const { dcSpecificPricing } = flags;
const { history, onSuccess, origin } = props;

const { data: profile } = useProfile();
Expand Down Expand Up @@ -273,9 +279,20 @@ const CreateVolumeForm: React.FC<CombinedProps> = (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. <br />
Up to eight volumes can be attached to a single Linode.
{dcSpecificPricing ? (
<span>
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2023-08-21 at 2 01 57 PM
This is a nitpick, but with the addition of another sentence, the way that we're displaying our copy is a little strange now, with that line break separating "Linode" onto the next line instead of at the end of the sentence. Can we potentially adjust max-width to something like 700 here to make our helper text more scannable?

</span>
) : (
<span>
A single Volume can range from 10 to {MAX_VOLUME_SIZE} GB
in size and costs $0.10/GB per month. <br />
Up to eight volumes can be attached to a single Linode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Up to eight volumes can be attached to a single Linode.
Up to eight volumes can be attached to a single Linode

Screenshot 2023-08-21 at 2 05 05 PM

We've got a double period here:

</span>
)}
.
</Typography>
<LabelField
tooltipText="Use only ASCII letters, numbers,
Expand All @@ -290,17 +307,6 @@ const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
tooltipPosition="right"
value={values.label}
/>
<Box alignItems="flex-end" display="flex">
<SizeField
disabled={doesNotHavePermission}
error={touched.size ? errors.size : undefined}
name="size"
onBlur={handleBlur}
onChange={handleChange}
textFieldStyles={classes.size}
value={values.size}
/>
</Box>
<Box alignItems="flex-end" display="flex">
<RegionSelect
handleSelection={(value) => {
Expand Down Expand Up @@ -377,6 +383,20 @@ const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
width={320}
/>
</Box>
<Box alignItems="flex-end" display="flex" position="relative">
<SizeField
disabled={doesNotHavePermission}
error={touched.size ? errors.size : undefined}
flags={flags}
hasSelectedRegion={!isNilOrEmpty(values.region)}
name="size"
onBlur={handleBlur}
onChange={handleChange}
regionId={values.region}
textFieldStyles={classes.size}
value={values.size}
/>
</Box>
<Box
alignItems="center"
className={classes.buttonGroup}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import NoticePanel from './NoticePanel';
import { PricePanel } from './PricePanel';
import VolumesActionsPanel from './VolumesActionsPanel';

import type { FlagSet } from 'src/featureFlags';

interface Props {
flags: FlagSet;
onClose: () => void;
volumeId: number;
volumeLabel: string;
Expand All @@ -27,7 +30,14 @@ interface Props {
const initialValues = { label: '' };

export const CloneVolumeForm = (props: Props) => {
const { onClose, volumeId, volumeLabel, volumeRegion, volumeSize } = props;
const {
flags,
abailly-akamai marked this conversation as resolved.
Show resolved Hide resolved
onClose,
volumeId,
volumeLabel,
volumeRegion,
volumeSize,
} = props;

const { mutateAsync: cloneVolume } = useCloneVolumeMutation();

Expand Down Expand Up @@ -87,7 +97,12 @@ export const CloneVolumeForm = (props: Props) => {
onChange={handleChange}
value={values.label}
/>
<PricePanel currentSize={volumeSize} value={volumeSize} />
<PricePanel
currentSize={volumeSize}
flags={flags}
regionId={volumeRegion}
value={volumeSize}
/>
<VolumesActionsPanel
onCancel={() => {
resetForm();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ 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),
},
}));

interface Props {
flags: FlagSet;
linode_id: number;
linodeLabel: string;
linodeRegion: string;
Expand All @@ -63,6 +66,7 @@ const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
const classes = useStyles();
const {
actions,
flags,
linode_id,
linodeLabel,
linodeRegion,
Expand Down Expand Up @@ -210,10 +214,12 @@ const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
<SizeField
disabled={disabled}
error={touched.size ? errors.size : undefined}
flags={flags}
isFromLinode
name="size"
onBlur={handleBlur}
onChange={handleChange}
regionId={linodeRegion}
value={values.size}
/>

Expand Down Expand Up @@ -245,7 +251,12 @@ const CreateVolumeForm: React.FC<CombinedProps> = (props) => {
value={values.tags}
/>

<PricePanel currentSize={10} value={values.size} />
<PricePanel
currentSize={10}
flags={flags}
regionId={linodeRegion}
value={values.size}
/>

<VolumesActionsPanel
onCancel={() => {
Expand Down
35 changes: 23 additions & 12 deletions packages/manager/src/features/Volumes/VolumeDrawer/PricePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box marginTop={4}>
<DisplayPrice interval="mo" price={price} />
<DisplayPrice interval="mo" price={Number(price)} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistencies in the way price is passed to children resulting in some expecting a string and others a number, such as DisplayPrice & Currency. Just something to be aware when we'll get the values from the API, which is when we can perhaps try to consolidate

</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.`}
/>
)}

<SizeField
disabled={readOnly}
error={errors.size}
flags={flags}
name="size"
onBlur={handleBlur}
onChange={handleChange}
regionId={volumeRegion}
resize={volumeSize}
value={values.size}
/>
<PricePanel currentSize={volumeSize} value={values.size} />

<PricePanel
currentSize={volumeSize}
flags={flags}
regionId={volumeRegion}
value={values.size}
/>
<VolumesActionsPanel
onCancel={() => {
resetForm();
Expand Down
Loading