Skip to content

Commit

Permalink
Merge branch 'develop' into feat-m3-7227
Browse files Browse the repository at this point in the history
  • Loading branch information
coliu-akamai committed Oct 20, 2023
2 parents 2847db8 + e159bc6 commit 029a75e
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 165 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-9813-added-1697655032681.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Helper text for DC-specific pricing Object Storage overages ([#9813](https://github.com/linode/manager/pull/9813))

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const options: { flag: keyof Flags; label: string }[] = [
{ flag: 'aglb', label: 'AGLB' },
{ flag: 'unifiedMigrations', label: 'Unified Migrations' },
{ flag: 'dcSpecificPricing', label: 'DC-Specific Pricing' },
{ flag: 'objDcSpecificPricing', label: 'OBJ Storage DC-Specific Pricing' },
{ flag: 'selfServeBetas', label: 'Self Serve Betas' },
{ flag: 'soldOutTokyo', label: 'Sold Out Tokyo' },
];
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface Flags {
linodeCreateWithFirewall: boolean;
mainContentBanner: MainContentBanner;
metadata: boolean;
objDcSpecificPricing: boolean;
oneClickApps: OneClickApp;
oneClickAppsDocsOverride: Record<string, Doc[]>;
productInformationBanners: ProductInformationBannerFlag[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('CreateBucketDrawer', () => {
getByTestId,
} = renderWithTheme(<CreateBucketDrawer {...props} />, { queryClient });

userEvent.type(getByLabelText('Label'), 'my-test-bucket');
userEvent.type(getByLabelText('Label', { exact: false }), 'my-test-bucket');

// We must waitFor because we need to load region and cluster data from the API
await waitFor(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Drawer } from 'src/components/Drawer';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
import EUAgreementCheckbox from 'src/features/Account/Agreements/EUAgreementCheckbox';
import { useFlags } from 'src/hooks/useFlags';
import {
useAccountAgreements,
useMutateAccountAgreements,
Expand All @@ -25,6 +26,7 @@ import { isEURegion } from 'src/utilities/formatRegion';

import { EnableObjectStorageModal } from '../EnableObjectStorageModal';
import ClusterSelect from './ClusterSelect';
import { OveragePricing } from './OveragePricing';

interface Props {
isOpen: boolean;
Expand All @@ -51,6 +53,8 @@ export const CreateBucketDrawer = (props: Props) => {
false
);

const flags = useFlags();

const formik = useFormik({
initialValues: {
cluster: '',
Expand Down Expand Up @@ -129,6 +133,7 @@ export const CreateBucketDrawer = (props: Props) => {
name="label"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
required
value={formik.values.label}
/>
<ClusterSelect
Expand All @@ -140,6 +145,9 @@ export const CreateBucketDrawer = (props: Props) => {
required
selectedCluster={formik.values.cluster}
/>
{flags.objDcSpecificPricing && clusterRegion?.[0]?.id && (
<OveragePricing regionId={clusterRegion?.[0]?.id} />
)}
{showAgreement ? (
<StyledEUAgreementCheckbox
onChange={(e) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { fireEvent } from '@testing-library/react';
import React from 'react';

import { OBJ_STORAGE_PRICE } from 'src/utilities/pricing/constants';
import { objectStoragePriceIncreaseMap } from 'src/utilities/pricing/dynamicPricing';
import { renderWithTheme } from 'src/utilities/testHelpers';

import {
DC_SPECIFIC_TRANSFER_POOLS_TOOLTIP_TEXT,
GLOBAL_TRANSFER_POOL_TOOLTIP_TEXT,
OveragePricing,
} from './OveragePricing';

describe('OveragePricing', () => {
it('Renders base overage pricing for a region without price increases', () => {
const { getByText } = renderWithTheme(
<OveragePricing regionId="us-east" />
);
getByText(`$${OBJ_STORAGE_PRICE.storage_overage} per GB`, { exact: false });
getByText(`$${OBJ_STORAGE_PRICE.transfer_overage} per GB`, {
exact: false,
});
});

it('Renders DC-specific overage pricing for a region with price increases', () => {
const { getByText } = renderWithTheme(<OveragePricing regionId="br-gru" />);
getByText(
`$${objectStoragePriceIncreaseMap['br-gru'].storage_overage} per GB`,
{ exact: false }
);
getByText(
`$${objectStoragePriceIncreaseMap['br-gru'].transfer_overage} per GB`,
{ exact: false }
);
});

it('Renders a tooltip for DC-specific transfer pools for a region with price increases', async () => {
const { findByRole, getByText } = renderWithTheme(
<OveragePricing regionId="br-gru" />
);

fireEvent.mouseEnter(getByText('network transfer pool for this region'));

const tooltip = await findByRole(/tooltip/);

expect(tooltip).toBeInTheDocument();
expect(getByText(DC_SPECIFIC_TRANSFER_POOLS_TOOLTIP_TEXT)).toBeVisible();
});

it('Renders a tooltip for global network transfer pools for a region without price increases', async () => {
const { findByRole, getByText } = renderWithTheme(
<OveragePricing regionId="us-east" />
);

fireEvent.mouseEnter(getByText('global network transfer pool'));

const tooltip = await findByRole(/tooltip/);

expect(tooltip).toBeInTheDocument();
expect(getByText(GLOBAL_TRANSFER_POOL_TOOLTIP_TEXT)).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Region } from '@linode/api-v4';
import { styled } from '@mui/material/styles';
import React from 'react';

import { TextTooltip } from 'src/components/TextTooltip';
import { Typography } from 'src/components/Typography';
import { OBJ_STORAGE_PRICE } from 'src/utilities/pricing/constants';
import { objectStoragePriceIncreaseMap } from 'src/utilities/pricing/dynamicPricing';

interface Props {
regionId: Region['id'];
}

export const DC_SPECIFIC_TRANSFER_POOLS_TOOLTIP_TEXT =
'For this region, monthly network transfer is calculated and tracked independently and is not part of your global network transfer pool.';
export const GLOBAL_TRANSFER_POOL_TOOLTIP_TEXT =
'Your global network transfer pool adds up all the included transfer associated with active Linode services on your account and is prorated based on service creation.';

export const OveragePricing = (props: Props) => {
const { regionId } = props;
const isDcSpecificPricingRegion = objectStoragePriceIncreaseMap.hasOwnProperty(
regionId
);

return (
<>
<StyledTypography>
For this region, additional storage costs{' '}
<strong>
$
{isDcSpecificPricingRegion
? objectStoragePriceIncreaseMap[regionId].storage_overage
: OBJ_STORAGE_PRICE.storage_overage}{' '}
per GB
</strong>
.
</StyledTypography>
<StyledTypography>
Outbound transfer will cost{' '}
<strong>
$
{isDcSpecificPricingRegion
? objectStoragePriceIncreaseMap[regionId].transfer_overage
: OBJ_STORAGE_PRICE.transfer_overage}{' '}
per GB
</strong>{' '}
if it exceeds{' '}
{isDcSpecificPricingRegion ? (
<>
the{' '}
<TextTooltip
displayText="network transfer pool for this region"
tooltipText={DC_SPECIFIC_TRANSFER_POOLS_TOOLTIP_TEXT}
/>
</>
) : (
<>
your{' '}
<TextTooltip
displayText="global network transfer pool"
tooltipText={GLOBAL_TRANSFER_POOL_TOOLTIP_TEXT}
/>
</>
)}
.
</StyledTypography>
</>
);
};

const StyledTypography = styled(Typography, {
label: 'StyledTypography',
})(({ theme }) => ({
margin: `${theme.spacing(2)} 0`,
}));
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
ENABLE_OBJ_ACCESS_KEYS_MESSAGE,
OBJ_STORAGE_PRICE,
} from 'src/utilities/pricing/constants';
import { objectStoragePriceIncreaseMap } from 'src/utilities/pricing/dynamicPricing';
import { wrapWithTheme } from 'src/utilities/testHelpers';

import {
EnableObjectStorageModal,
EnableObjectStorageProps,
OBJ_STORAGE_NETWORK_TRANSFER_AMT,
OBJ_STORAGE_STORAGE_AMT,
} from './EnableObjectStorageModal';

const DC_SPECIFIC_PRICING_REGION_LABEL = 'Jakarta, ID';
Expand All @@ -29,114 +30,103 @@ const props: EnableObjectStorageProps = {

describe('EnableObjectStorageModal', () => {
it('includes a header', () => {
const { getByText } = render(
const { getAllByText } = render(
wrapWithTheme(<EnableObjectStorageModal {...props} />)
);
getByText('Just to confirm...');
getAllByText('Enable Object Storage');
});

// TODO: DC Pricing - M3-7073: Remove this test once dcSpecificPricing flag is cleaned up
it('displays base prices for a region without price increases when the DC-specific pricing feature flag is off', () => {
// TODO: DC Pricing - M3-7063: Delete this test when feature flags are cleaned up.
it('displays flat rate pricing and storage for a region without price increases with the OBJ DC-specific pricing flag off', () => {
const { getByText } = render(
wrapWithTheme(
<EnableObjectStorageModal {...props} regionId={BASE_PRICING_REGION} />,
{ flags: { dcSpecificPricing: false } }
{
flags: { objDcSpecificPricing: false },
}
)
);
getByText(`$${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(`$${OBJ_STORAGE_PRICE.storage_overage} per GB`, { exact: false });
});

// TODO: DC Pricing - M3-7073: Remove this test once dcSpecificPricing flag is cleaned up
it('displays base prices for a region with price increases when the DC-specific pricing feature flag is off', () => {
// TODO: DC Pricing - M3-7063: Delete this test when feature flags are cleaned up.
it('displays beta message with region label for a region with price increases when the OBJ DC-specific pricing flag is off', () => {
const { getByText } = render(
wrapWithTheme(
<EnableObjectStorageModal
{...props}
regionId={DC_SPECIFIC_PRICING_REGION}
/>,
{
flags: { dcSpecificPricing: false },
flags: { objDcSpecificPricing: false },
}
)
);
getByText(`${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(`$${OBJ_STORAGE_PRICE.storage_overage} per GB`, { exact: false });
});

it('displays base prices for a region without price increases when the DC-specific pricing feature flag is on', () => {
const { getByText } = render(
wrapWithTheme(
<EnableObjectStorageModal {...props} regionId={BASE_PRICING_REGION} />,
{ flags: { dcSpecificPricing: true } }
)
getByText(
`Object Storage for ${DC_SPECIFIC_PRICING_REGION_LABEL} is currently in beta. During the beta period, Object Storage in these regions is free. After the beta period, customers will be notified before charges for this service begin.`
);
getByText(`$${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(`$${OBJ_STORAGE_PRICE.storage_overage} per GB`, { exact: false });
});

// TODO: DC Pricing - M3-6973: delete this test and replace it with the one below it when beta pricing ends.
it('displays beta message with region label for a region with price increases when the DC-specific pricing flag is on', () => {
it('displays flat rate pricing, storage, and network transfer amounts in a region without price increases with the OBJ DC-specific pricing flag on', () => {
const { getByText } = render(
wrapWithTheme(
<EnableObjectStorageModal
{...props}
regionId={DC_SPECIFIC_PRICING_REGION}
/>,
{
flags: { dcSpecificPricing: true },
flags: { objDcSpecificPricing: true },
}
)
);
getByText(
`Object Storage for ${DC_SPECIFIC_PRICING_REGION_LABEL} is currently in beta. During the beta period, Object Storage in these regions is free. After the beta period, customers will be notified before charges for this service begin.`
);
getByText(`$${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(OBJ_STORAGE_STORAGE_AMT, { exact: false });
getByText(OBJ_STORAGE_NETWORK_TRANSFER_AMT, { exact: false });
});

// TODO: DC Pricing - M3-6973: Unskip this test when beta pricing ends.
it.skip('displays DC-specific pricing for a region with price increases when the DC-specific pricing flag is on', () => {
it('displays flat rate pricing, storage, and network transfer amounts in a price increase region with the OBJ DC-specific pricing flag on', () => {
const { getByText } = render(
wrapWithTheme(
<EnableObjectStorageModal
{...props}
regionId={DC_SPECIFIC_PRICING_REGION}
/>,
{
flags: { dcSpecificPricing: true },
flags: { objDcSpecificPricing: true },
}
)
);
getByText(
`$${objectStoragePriceIncreaseMap[DC_SPECIFIC_PRICING_REGION].monthly}/month`,
{
exact: false,
}
);
getByText(
`$${objectStoragePriceIncreaseMap[DC_SPECIFIC_PRICING_REGION].storage_overage} per GB`,
{ exact: false }
);
getByText(
`$${objectStoragePriceIncreaseMap[DC_SPECIFIC_PRICING_REGION].transfer_overage} per GB`,
{ exact: false }
);
getByText(`$${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(OBJ_STORAGE_STORAGE_AMT, { exact: false });
getByText(OBJ_STORAGE_NETWORK_TRANSFER_AMT, { exact: false });
});

it('displays a message without pricing if no region exists (e.g. access key flow) when the DC-specific pricing flag is on', () => {
// TODO: DC Pricing - M3-7063: Delete this test when feature flags are cleaned up.
it('displays a message without pricing if no region exists (e.g. access key flow) when the OBJ DC-specific pricing flag is off', () => {
const { getByText } = render(
wrapWithTheme(<EnableObjectStorageModal {...props} />, {
flags: { dcSpecificPricing: true },
flags: { objDcSpecificPricing: false },
})
);
getByText(ENABLE_OBJ_ACCESS_KEYS_MESSAGE);
});

it('includes a link to linode.com/pricing when the DC-specific pricing flag is on', () => {
it('displays flat rate pricing, storage, and network transfer amounts when a regionId is not defined and OBJ DC-specific pricing flag is on', () => {
const { getByText } = render(
wrapWithTheme(<EnableObjectStorageModal {...props} />, {
flags: { dcSpecificPricing: true },
flags: { objDcSpecificPricing: true },
})
);
getByText(`$${OBJ_STORAGE_PRICE.monthly}/month`, { exact: false });
getByText(OBJ_STORAGE_STORAGE_AMT, { exact: false });
getByText(OBJ_STORAGE_NETWORK_TRANSFER_AMT, { exact: false });
});

it('includes a link to linode.com/pricing', () => {
const { getByText } = render(
wrapWithTheme(<EnableObjectStorageModal {...props} />)
);
const link = getByText('Learn more');
expect(link.closest('a')).toHaveAttribute(
'href',
Expand Down Expand Up @@ -169,10 +159,10 @@ describe('EnableObjectStorageModal', () => {
});

it('calls the handleSubmit prop/handler when the Enable Object Storage button is clicked', () => {
const { getByText } = render(
const { getByTestId } = render(
wrapWithTheme(<EnableObjectStorageModal {...props} />)
);
fireEvent.click(getByText('Enable Object Storage'));
fireEvent.click(getByTestId('enable-obj'));
expect(props.handleSubmit).toHaveBeenCalled();
});
});
Loading

0 comments on commit 029a75e

Please sign in to comment.