Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Update Monthly Network Transfer Pool dialog copy and typography ([#9692](https://github.com/linode/manager/pull/9692))
1 change: 1 addition & 0 deletions packages/manager/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const Dialog = (props: DialogProps) => {
{titleBottomBorder && <StyledHr />}
<DialogContent
sx={{
overflowX: 'hidden',
paddingBottom: theme.spacing(3),
}}
className={className}
Expand Down
3 changes: 2 additions & 1 deletion packages/manager/src/components/TooltipIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface Props
* Enables a leaveDelay of 3000ms
* @default false
*/
leaveDelay?: boolean;
leaveDelay?: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fixes an existing type issue on the component (unrelated to main PR purpose)

/**
* Sets the icon and color
*/
Expand Down Expand Up @@ -142,6 +142,7 @@ export const TooltipIcon = (props: Props) => {
return (
<Tooltip
classes={classes}
componentsProps={props.componentsProps}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those were not picked up otherwise. Decided against passing {...props} to avoid regressions related to spreading everything (unknown HTML props etc)

data-qa-help-tooltip
disableInteractive={!interactive}
enterTouchDelay={0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('TransferDisplayDialog', () => {
const transferButton = await findByText(TRANSFER_DISPLAY_BUTTON);
fireEvent.click(transferButton);

expect(getByTestId('general-transfer-pool-display')).toBeInTheDocument();
expect(getByTestId('region-transfer-pool-display')).toBeInTheDocument();
expect(getByTestId('global-transfer-pool-header')).toBeInTheDocument();
expect(getByTestId('other-transfer-pools-header')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { styled } from '@mui/material/styles';
import { useTheme } from '@mui/material/styles';
import * as React from 'react';

Expand All @@ -7,9 +8,10 @@ import { Divider } from 'src/components/Divider';
import { Typography } from 'src/components/Typography';

import { DocsLink } from '../DocsLink/DocsLink';
import { TransferDisplayDialogHeader } from './TransferDisplayDialogHeader';
import { TransferDisplayUsage } from './TransferDisplayUsage';
import { NETWORK_TRANSFER_QUOTA_DOCS_LINKS } from './constants';
import { getDaysRemaining } from './utils';
import { formatRegionList, getDaysRemaining } from './utils';

import type { RegionTransferPool } from './utils';

Expand Down Expand Up @@ -37,11 +39,16 @@ export const TransferDisplayDialog = React.memo(
} = props;
const theme = useTheme();
const daysRemainingInMonth = getDaysRemaining();
const listOfOtherRegionTransferPools: string[] =
regionTransferPools.length > 0
? regionTransferPools.map((pool) => pool.regionName)
: [];
const otherRegionPools = formatRegionList(listOfOtherRegionTransferPools);

const transferQuotaDocsText =
used === 0
? 'Compute instances, NodeBalancers, and Object Storage include network transfer.'
: 'View products and services that include network transfer, and learn how to optimize network usage to avoid billing surprises.';
: 'In some regions, the monthly network transfer is calculated and tracked independently. Transfer overages will be billed separately.';

return (
<Dialog
Expand All @@ -54,40 +61,35 @@ export const TransferDisplayDialog = React.memo(
{/**
* Global Transfer Pool Display
*/}
<Typography
data-testid="general-transfer-pool-display"
fontFamily={theme.font.bold}
marginBottom={theme.spacing()}
>
Global Network Transfer Pool
</Typography>
<TransferDisplayDialogHeader
tooltipText={`The Global Pool includes transfer associated with active services in your devices' regions${
listOfOtherRegionTransferPools.length > 0
? ` except for ${otherRegionPools}.`
: '.'
}
`}
dataTestId="global-transfer-pool-header"
headerText="Global Network Transfer Pool"
/>
<TransferDisplayUsage
pullUsagePct={generalPoolUsagePct}
quota={quota}
used={used}
/>
<Divider
sx={{ marginBottom: theme.spacing(2), marginTop: theme.spacing(3) }}
/>
<StyledDivider />
{/**
* DC-specific Transfer Pool Display
*/}
{regionTransferPools.length > 0 && (
<>
<Typography
data-testid="region-transfer-pool-display"
fontFamily={theme.font.bold}
marginBottom={theme.spacing()}
>
Data Center-Specific Network Transfer Pools
</Typography>
<Typography
marginBottom={theme.spacing()}
marginTop={theme.spacing()}
>
In some regions, the monthly network transfer is calculated and
tracked independently. These regions are listed below. Transfer
overages will be billed separately.
<TransferDisplayDialogHeader
dataTestId="other-transfer-pools-header"
headerText="Other Transfer Pools"
tooltipText="In some regions, the monthly network transfer is calculated and tracked independently. Transfer overages will be billed separately."
/>
<Typography marginBottom={theme.spacing(2)} marginTop={-1}>
These data center-specific transfer pools are not included in the
Global Transfer Pool.
</Typography>

{regionTransferPools.map((pool, key) => (
Expand All @@ -97,7 +99,6 @@ export const TransferDisplayDialog = React.memo(
>
<Typography
fontFamily={theme.font.bold}
fontSize={theme.typography.body2.fontSize}
marginBottom={theme.spacing()}
>
{pool.regionName}{' '}
Expand Down Expand Up @@ -137,3 +138,13 @@ export const TransferDisplayDialog = React.memo(
);
}
);

const StyledDivider = styled(Divider, {
label: 'TransferDisplayDialogDivider',
})(({ theme }) => ({
borderColor: theme.color.border3,
marginBottom: theme.spacing(2),
marginLeft: theme.spacing(-3),
marginTop: theme.spacing(3),
width: `calc(100% + ${theme.spacing(6)})`,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useTheme } from '@mui/material/styles';
import * as React from 'react';

import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';

interface Props {
dataTestId: string;
headerText: string;
tooltipText: string;
}

export const TransferDisplayDialogHeader = React.memo((props: Props) => {
const { dataTestId, headerText, tooltipText } = props;
const theme = useTheme();

return (
<Typography
data-testid={dataTestId}
fontFamily={theme.font.bold}
fontSize={theme.typography.h3.fontSize}
>
{headerText}
<TooltipIcon
componentsProps={{
tooltip: {
style: {
marginTop: -8,
minWidth: 250,
},
},
}}
status="help"
sxTooltipIcon={{ left: -2, top: -2 }}
text={tooltipText}
/>
</Typography>
);
});
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 enough coverage of the feature to leave this new micro component without a unit test

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('TransferDisplayDialogUsage', () => {
const progressBars = getAllByRole('progressbar');

expect(progressBars.length).toBe(3);
expect(getByTestId('general-transfer-pool-display')).toBeInTheDocument();
expect(getByTestId('global-transfer-pool-header')).toBeInTheDocument();

expect(await findByText('9000 GB Used (36%)')).toBeInTheDocument();
expect(await findByText('8500 GB Used (85%)')).toBeInTheDocument();
Expand All @@ -62,7 +62,7 @@ describe('TransferDisplayDialogUsage', () => {

const progressBars = getAllByRole('progressbar');

expect(getByTestId('general-transfer-pool-display')).toBeInTheDocument();
expect(getByTestId('global-transfer-pool-header')).toBeInTheDocument();
expect(progressBars.length).toBe(1);
expect(progressBars[0]).toHaveAttribute('aria-valuenow', '36');
});
Expand Down
17 changes: 17 additions & 0 deletions packages/manager/src/components/TransferDisplay/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
calculatePoolUsagePct,
formatPoolUsagePct,
formatRegionList,
getDaysRemaining,
getRegionTransferPools,
} from './utils';
Expand Down Expand Up @@ -72,3 +73,19 @@ describe('formatPoolUsagePct', () => {
expect(formattedPct).toBe('85%');
});
});

describe('formatRegionList', () => {
it('should format the list of regions correctly', () => {
const listOfNoRegions = [''];
const formattedListNoRegions = formatRegionList(listOfNoRegions);
expect(formattedListNoRegions).toBe('');

const listOfOneRegion = ['Newark, NJ'];
const formattedListOneRegion = formatRegionList(listOfOneRegion);
expect(formattedListOneRegion).toBe('Newark, NJ');

const listOfRegions = ['Newark, NJ', 'Dallas, TX', 'Fremont, CA'];
const formattedList = formatRegionList(listOfRegions);
expect(formattedList).toBe('Newark, NJ, Dallas, TX and Fremont, CA');
});
});
24 changes: 24 additions & 0 deletions packages/manager/src/components/TransferDisplay/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,27 @@ export const getRegionTransferPools = (
export const formatPoolUsagePct = (pct: number): string => {
return `${pct.toFixed(pct < 1 ? 2 : 0)}%`;
};

/**
* Format a list of regions into a readable string.
* @param regions
* @returns string
*
* @example formatRegionList(['Region 1', 'Region 2', 'Region 3']) // 'Region 1, Region 2 and Region 3'
* @example formatRegionList(['Region 1, Region 2']) // 'Region 1 and Region 2'
* @example formatRegionList(['Region 1']) // 'Region 1'
* @example formatRegionList([]) // ''
Copy link
Contributor

@coliu-akamai coliu-akamai Sep 19, 2023

Choose a reason for hiding this comment

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

super small, but worth writing a test case for this as well (the empty [] case) just to hit all the cases 😆

Copy link
Contributor

Choose a reason for hiding this comment

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

Seconding this. 👍🏼

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure thing, done

*/
export const formatRegionList = (regions: string[]) => {
const length = regions.length;

if (length === 0) {
return '';
} else if (length === 1) {
return regions[0];
} else {
const lastRegion = regions.pop();

return `${regions.join(', ')} and ${lastRegion}`;
}
};