-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [M3-7036] DC specific transfer pool displays (#9620)
* feat: [M3-6957] save work * feat: [M3-7036] component cleanup * feat: [M3-7036] save workk * feat: [M3-7036] add region transfer pools PCT * feat: [M3-7036] cleanup * feat: [M3-7036] add usage to modal * feat: [M3-7036] formatting * feat: [M3-7036] flag splitting * feat: [M3-7036] linear progress styling * feat: [M3-7036] util test * feat: [M3-7036] component tests * feat: [M3-7036] cleanup * feat: [M3-7036] better coverage * feat: [M3-7036] test refactor * feat: [M3-7036] test refactor 2 * feat: [M3-7036] NetworkTRansfer panel * feat: [M3-7036] Naming conventions, component splitting and BarPercent * feat: [M3-7036] Wrap up linode netowrk display transfer * feat: [M3-7036] Test wrap up * feat: [M3-7036] cleanup * feat: [M3-7036] cleanup 2 * Added changeset: DC specific transfer pools and linode usage displays * feat: [M3-7036] feedback 1 * feat: [M3-7036] feedback 2 * feat: [M3-7036] refactor doc links * feat: [M3-7036] fix test * feat: [M3-7036] fix Network Transfer History graph color * feat: [M3-7036] small feedback 3
- Loading branch information
1 parent
02d0a7b
commit 304ba45
Showing
29 changed files
with
1,601 additions
and
422 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
packages/manager/.changeset/pr-9620-upcoming-features-1694457457440.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Upcoming Features | ||
--- | ||
|
||
DC specific transfer pools and linode usage displays ([#9620](https://github.com/linode/manager/pull/9620)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
packages/manager/src/components/TransferDisplay/LegacyTransferDisplay.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// ================================================================================= | ||
// This is a copy of the original TransferDisplay test, which is getting deprecated. | ||
// It can be safely deleted once the new TransferDisplay component | ||
// is fully rolled out and the dcSpecificPricing flag is cleaned up. | ||
// ================================================================================== | ||
|
||
import { fireEvent } from '@testing-library/react'; | ||
import React from 'react'; | ||
|
||
import { rest, server } from 'src/mocks/testServer'; | ||
import { renderWithTheme } from 'src/utilities/testHelpers'; | ||
|
||
import { TransferDisplay } from './TransferDisplay'; | ||
|
||
const MockData = { | ||
billable: 0, | ||
quota: 0, | ||
used: 0, | ||
}; | ||
|
||
const transferDisplayPercentageSubstring = /You have used \d+\.\d\d%/; | ||
const transferDisplayButtonSubstring = /Monthly Network Transfer Pool/; | ||
const docsLink = 'https://www.linode.com/docs/guides/network-transfer-quota/'; | ||
|
||
describe('TransferDisplay', () => { | ||
it('renders transfer display text and opens the transfer dialog, with GB data stats, on click', async () => { | ||
server.use( | ||
rest.get('*/account/transfer', (req, res, ctx) => { | ||
return res( | ||
ctx.json({ | ||
billable: 0, | ||
quota: 11347, | ||
used: 50, | ||
}) | ||
); | ||
}) | ||
); | ||
|
||
const { findByText, getByTestId } = renderWithTheme(<TransferDisplay />); | ||
const transferButton = await findByText(transferDisplayButtonSubstring, { | ||
exact: false, | ||
}); | ||
|
||
expect(transferButton).toBeInTheDocument(); | ||
expect( | ||
await findByText(transferDisplayPercentageSubstring, { exact: false }) | ||
).toBeInTheDocument(); | ||
fireEvent.click(transferButton); | ||
|
||
const transferDialog = getByTestId('drawer'); | ||
expect(transferDialog.innerHTML).toMatch(/GB/); | ||
}); | ||
|
||
it('renders transfer display text with a percentage of 0.00% if no usage', async () => { | ||
server.use( | ||
rest.get('*/account/transfer', (req, res, ctx) => { | ||
return res(ctx.json(MockData)); | ||
}) | ||
); | ||
|
||
const { findByText } = renderWithTheme(<TransferDisplay />); | ||
const usage = await findByText(transferDisplayPercentageSubstring, { | ||
exact: false, | ||
}); | ||
|
||
expect(usage.innerHTML).toMatch(/0.00%/); | ||
}); | ||
|
||
it('renders transfer display dialog without usage or quota data if no quota/resources', async () => { | ||
server.use( | ||
rest.get('*/account/transfer', (req, res, ctx) => { | ||
return res(ctx.json(MockData)); | ||
}) | ||
); | ||
|
||
const { findByText, getByTestId } = renderWithTheme(<TransferDisplay />); | ||
const transferButton = await findByText(transferDisplayButtonSubstring); | ||
fireEvent.click(transferButton); | ||
|
||
const transferDialog = getByTestId('drawer'); | ||
expect(transferDialog.innerHTML).toMatch( | ||
/Your monthly network transfer will be shown when you create a resource./ | ||
); | ||
expect(transferDialog.innerHTML).not.toMatch(/GB/); | ||
}); | ||
|
||
it('renders the transfer display dialog with an accessible docs link', async () => { | ||
server.use( | ||
rest.get('*/account/transfer', (req, res, ctx) => { | ||
return res(ctx.json(MockData)); | ||
}) | ||
); | ||
|
||
const { findByText, getByRole } = renderWithTheme(<TransferDisplay />); | ||
const transferButton = await findByText(transferDisplayButtonSubstring); | ||
fireEvent.click(transferButton); | ||
|
||
expect(getByRole('link')).toHaveAttribute('href', docsLink); | ||
expect(getByRole('link').getAttribute('aria-label')); | ||
}); | ||
}); |
218 changes: 218 additions & 0 deletions
218
packages/manager/src/components/TransferDisplay/LegacyTransferDisplay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// ====================================================================================== | ||
// This is a copy of the original TransferDisplay component, which is getting deprecated. | ||
// It can be safely deleted once the new TransferDisplay component | ||
// is fully rolled out and the dcSpecificPricing flag is cleaned up. | ||
// ====================================================================================== | ||
|
||
import Grid from '@mui/material/Unstable_Grid2'; | ||
import { Theme } from '@mui/material/styles'; | ||
import { DateTime } from 'luxon'; | ||
import * as React from 'react'; | ||
import { makeStyles } from 'tss-react/mui'; | ||
|
||
import BarPercent from 'src/components/BarPercent'; | ||
import { Dialog } from 'src/components/Dialog/Dialog'; | ||
import { Link } from 'src/components/Link'; | ||
import { Typography } from 'src/components/Typography'; | ||
import { useAccountTransfer } from 'src/queries/accountTransfer'; | ||
|
||
const useStyles = makeStyles()((theme: Theme) => ({ | ||
link: { | ||
marginTop: theme.spacing(1), | ||
}, | ||
openModalButton: { | ||
...theme.applyLinkStyles, | ||
}, | ||
paddedDocsText: { | ||
[theme.breakpoints.up('md')]: { | ||
paddingRight: theme.spacing(3), // Prevents link text from being split onto two lines. | ||
}, | ||
}, | ||
paper: { | ||
padding: theme.spacing(3), | ||
}, | ||
poolUsageProgress: { | ||
'& .MuiLinearProgress-root': { | ||
borderRadius: 1, | ||
}, | ||
marginBottom: theme.spacing(0.5), | ||
}, | ||
proratedNotice: { | ||
marginBottom: theme.spacing(1), | ||
marginTop: theme.spacing(1), | ||
}, | ||
root: { | ||
margin: 'auto', | ||
textAlign: 'center', | ||
[theme.breakpoints.down('md')]: { | ||
width: '85%', | ||
}, | ||
width: '100%', | ||
}, | ||
})); | ||
|
||
export interface Props { | ||
spacingTop?: number; | ||
} | ||
|
||
export const LegacyTransferDisplay = React.memo(({ spacingTop }: Props) => { | ||
const { classes } = useStyles(); | ||
|
||
const [modalOpen, setModalOpen] = React.useState(false); | ||
const { data, isError, isLoading } = useAccountTransfer(); | ||
const quota = data?.quota ?? 0; | ||
const used = data?.used ?? 0; | ||
|
||
// Usage percentage should not be 100% if there has been no usage or usage has not exceeded quota. | ||
const poolUsagePct = | ||
used < quota ? (used / quota) * 100 : used === 0 ? 0 : 100; | ||
|
||
if (isError) { | ||
// We may want to add an error state for this but I think that would clutter | ||
// up the display. | ||
return null; | ||
} | ||
|
||
return ( | ||
<> | ||
<Typography | ||
className={classes.root} | ||
style={{ marginTop: spacingTop ?? 8 }} | ||
> | ||
{isLoading ? ( | ||
'Loading transfer data...' | ||
) : ( | ||
<> | ||
You have used {poolUsagePct.toFixed(poolUsagePct < 1 ? 2 : 0)}% of | ||
your | ||
{` `} | ||
<button | ||
className={classes.openModalButton} | ||
onClick={() => setModalOpen(true)} | ||
> | ||
Monthly Network Transfer Pool | ||
</button> | ||
. | ||
</> | ||
)} | ||
</Typography> | ||
<TransferDialog | ||
isOpen={modalOpen} | ||
onClose={() => setModalOpen(false)} | ||
poolUsagePct={poolUsagePct} | ||
quota={quota} | ||
used={used} | ||
/> | ||
</> | ||
); | ||
}); | ||
|
||
export const getDaysRemaining = () => | ||
Math.floor( | ||
DateTime.local() | ||
.setZone('America/New_York') | ||
.endOf('month') | ||
.diffNow('days') | ||
.toObject().days ?? 0 | ||
); | ||
|
||
// ============================================================================= | ||
// Dialog | ||
// ============================================================================= | ||
interface DialogProps { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
poolUsagePct: number; | ||
quota: number; | ||
used: number; | ||
} | ||
|
||
export const TransferDialog = React.memo((props: DialogProps) => { | ||
const { isOpen, onClose, poolUsagePct, quota, used } = props; | ||
const { classes } = useStyles(); | ||
const daysRemainingInMonth = getDaysRemaining(); | ||
// Don't display usage, quota, or bar percent if the network transfer pool is empty (e.g. account has no resources). | ||
const isEmptyPool = quota === 0; | ||
const transferQuotaDocsText = | ||
used === 0 ? ( | ||
<span className={classes.paddedDocsText}> | ||
Compute instances, NodeBalancers, and Object Storage include network | ||
transfer. | ||
</span> | ||
) : ( | ||
'View products and services that include network transfer, and learn how to optimize network usage to avoid billing surprises.' | ||
); | ||
|
||
return ( | ||
<Dialog | ||
classes={{ paper: classes.paper }} | ||
fullWidth | ||
maxWidth="sm" | ||
onClose={onClose} | ||
open={isOpen} | ||
title="Monthly Network Transfer Pool" | ||
> | ||
<Grid | ||
container | ||
justifyContent="space-between" | ||
spacing={2} | ||
style={{ marginBottom: 0 }} | ||
> | ||
<Grid style={{ marginRight: 10 }}> | ||
{!isEmptyPool ? ( | ||
<Typography>{used} GB Used</Typography> | ||
) : ( | ||
<Typography> | ||
Your monthly network transfer will be shown when you create a | ||
resource. | ||
</Typography> | ||
)} | ||
</Grid> | ||
<Grid> | ||
{!isEmptyPool ? ( | ||
<Typography> | ||
{quota >= used ? ( | ||
<span>{quota - used} GB Available</span> | ||
) : ( | ||
<span> | ||
{(quota - used).toString().replace(/\-/, '')} GB Over Quota | ||
</span> | ||
)} | ||
</Typography> | ||
) : null} | ||
</Grid> | ||
</Grid> | ||
{!isEmptyPool ? ( | ||
<BarPercent | ||
className={classes.poolUsageProgress} | ||
max={100} | ||
rounded | ||
value={Math.ceil(poolUsagePct)} | ||
/> | ||
) : null} | ||
|
||
<Typography className={classes.proratedNotice}> | ||
<strong> | ||
Your account’s monthly network transfer allotment will reset in{' '} | ||
{daysRemainingInMonth} days. | ||
</strong> | ||
</Typography> | ||
<Typography className={classes.proratedNotice}> | ||
Your account’s network transfer pool adds up all the included | ||
transfer associated with active Linode services on your account and is | ||
prorated based on service creation. | ||
</Typography> | ||
<div className={classes.link}> | ||
<Typography> | ||
{transferQuotaDocsText}{' '} | ||
<Link | ||
aria-label="Learn more – link opens in a new tab" | ||
to="https://www.linode.com/docs/guides/network-transfer-quota/" | ||
> | ||
Learn more. | ||
</Link> | ||
</Typography> | ||
</div> | ||
</Dialog> | ||
); | ||
}); |
14 changes: 14 additions & 0 deletions
14
packages/manager/src/components/TransferDisplay/TransferDisplay.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { styled } from '@mui/material/styles'; | ||
|
||
import { Box } from 'src/components/Box'; | ||
|
||
export const StyledTransferDisplayContainer = styled(Box, { | ||
label: 'StyledTransferDisplayTypography', | ||
})(({ theme }) => ({ | ||
margin: 'auto', | ||
textAlign: 'center', | ||
[theme.breakpoints.down('md')]: { | ||
width: '85%', | ||
}, | ||
width: '100%', | ||
})); |
Oops, something went wrong.