1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-21 13:47:39 +02:00

chore(1-3316): update request info boxes to new design (#9180)

Updates the existing number of requests and overage info boxes to the
new design.

The existing versions of the boxes had some issues on narrower screens,
so it was easier to just leave them as is and start from scratch.

The previous boxes on narrow screens:

![image](https://github.com/user-attachments/assets/f3efa00d-ac0d-41ed-82d8-11766e043cb5)


The current ones (from wide to narrower):
Wide

![image](https://github.com/user-attachments/assets/0a48c013-afcd-4652-9229-0fca19a83733)

Mid (the text should probably ideally wrap at the same time here, but
I'm not sure how at the moment)

![image](https://github.com/user-attachments/assets/2ea3a672-80a6-4445-ae90-736c91c6e88e)

Narrow

![image](https://github.com/user-attachments/assets/03e3de0e-23c1-436a-8f6c-4c78cd4fdae7)

Extra narrow:

![image](https://github.com/user-attachments/assets/652c0c3b-71b1-4b2e-9e86-217f0c827aa6)



There's still some work we **could** do, but we should have UX have a
look first. In particular, it's about how the text wraps in certain
places etc, but I think it's good enough for now.

I'll come back with tests for the calculations and some refactoring and
cleanup in a followup.
This commit is contained in:
Thomas Heartman 2025-01-31 14:05:36 +01:00 committed by GitHub
parent eba183a12c
commit e72a7c1197
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 279 additions and 30 deletions

View File

@ -42,6 +42,8 @@ import { PeriodSelector } from './PeriodSelector';
import { useUiFlag } from 'hooks/useUiFlag';
import { format } from 'date-fns';
import { monthlyTrafficDataToCurrentUsage } from './monthly-traffic-data-to-current-usage';
import { OverageInfo, RequestSummary } from './RequestSummary';
import { averageTrafficPreviousMonths } from './average-traffic-previous-months';
const StyledBox = styled(Box)(({ theme }) => ({
display: 'grid',
@ -148,16 +150,30 @@ const createBarChartOptions = (
});
// this is primarily for dev purposes. The existing grid is very inflexible, so we might want to change it, but for demoing the design, this is enough.
const NewHeader = styled('div')(() => ({
const NewHeader = styled('div')(({ theme }) => ({
display: 'flex',
flexFlow: 'row wrap',
justifyContent: 'space-between',
alignItems: 'flex-start',
gap: theme.spacing(2, 4),
}));
const TrafficInfoBoxes = styled('div')(({ theme }) => ({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, max-content))',
flex: 1,
gap: theme.spacing(2, 4),
}));
const BoldText = styled('span')(({ theme }) => ({
fontWeight: 'bold',
}));
const NewNetworkTrafficUsage: FC = () => {
usePageTitle('Network - Data Usage');
const theme = useTheme();
const estimateTrafficDataCost = useUiFlag('estimateTrafficDataCost');
const { isOss } = useUiConfig();
const { locationSettings } = useLocationSettings();
@ -168,6 +184,7 @@ const NewNetworkTrafficUsage: FC = () => {
toTrafficUsageSum,
calculateOverageCost,
calculateEstimatedMonthlyCost,
endpointsInfo,
} = useTrafficDataEstimation();
const includedTraffic = useTrafficLimit();
@ -192,7 +209,11 @@ const NewNetworkTrafficUsage: FC = () => {
},
);
} else {
return new Date(tooltipItems[0].label).toLocaleDateString(
const timestamp = Date.parse(tooltipItems[0].label);
if (Number.isNaN(timestamp)) {
return 'Current month to date';
}
return new Date(timestamp).toLocaleDateString(
locationSettings?.locale ?? 'en-US',
{
month: 'long',
@ -248,6 +269,21 @@ const NewNetworkTrafficUsage: FC = () => {
}
}, [data]);
// todo: extract this (and also endpoints info)
const [lastLabel, ...otherLabels] = Object.values(endpointsInfo)
.map((info) => info.label.toLowerCase())
.toReversed();
const requestTypes = `${otherLabels.toReversed().join(', ')}, and ${lastLabel}`;
const chartLabel =
newPeriod.grouping === 'daily'
? `A bar chart showing daily traffic usage for ${new Date(
newPeriod.month,
).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
})}. Each date shows ${requestTypes} requests.`
: `A bar chart showing monthly total traffic usage for the current month and the preceding ${newPeriod.monthsBack} months. Each month shows ${requestTypes} requests.`;
return (
<ConditionallyRender
condition={isOss()}
@ -255,42 +291,61 @@ const NewNetworkTrafficUsage: FC = () => {
elseShow={
<>
<ConditionallyRender
condition={includedTraffic > 0 && overageCost > 0}
condition={
(newPeriod.grouping === 'monthly' ||
newPeriod.month ===
format(new Date(), 'yyyy-MM')) &&
includedTraffic > 0 &&
overageCost > 0
}
show={
<Alert severity='warning' sx={{ mb: 4 }}>
<b>Heads up!</b> You are currently consuming
more requests than your plan includes and will
be billed according to our terms. Please see{' '}
<Link
component={RouterLink}
to='https://www.getunleash.io/pricing'
>
<BoldText>Heads up!</BoldText> You are currently
consuming more requests than your plan includes
and will be billed according to our terms.
Please see{' '}
<RouterLink to='https://www.getunleash.io/pricing'>
this page
</Link>{' '}
</RouterLink>{' '}
for more information. In order to reduce your
traffic consumption, you may configure an{' '}
<Link
component={RouterLink}
to='https://docs.getunleash.io/reference/unleash-edge'
>
<RouterLink to='https://docs.getunleash.io/reference/unleash-edge'>
Unleash Edge instance
</Link>{' '}
</RouterLink>{' '}
in your own datacenter.
</Alert>
}
/>
<StyledBox>
<NewHeader>
{
// todo: add new usage plan summary that works for monthly data as well as daily
}
<NetworkTrafficUsagePlanSummary
usageTotal={usageTotal}
includedTraffic={includedTraffic}
overageCost={overageCost}
estimatedMonthlyCost={estimatedMonthlyCost}
/>
<TrafficInfoBoxes>
<RequestSummary
period={newPeriod}
usageTotal={
newPeriod.grouping === 'daily'
? usageTotal
: averageTrafficPreviousMonths(
Object.keys(endpointsInfo),
traffic.usage,
)
}
includedTraffic={includedTraffic}
/>
{newPeriod.grouping === 'daily' &&
includedTraffic > 0 &&
usageTotal - includedTraffic > 0 &&
estimateTrafficDataCost && (
<OverageInfo
overageCost={overageCost}
overages={
usageTotal - includedTraffic
}
estimatedMonthlyCost={
estimatedMonthlyCost
}
/>
)}
</TrafficInfoBoxes>
<PeriodSelector
selectedPeriod={newPeriod}
setPeriod={setNewPeriod}
@ -300,7 +355,7 @@ const NewNetworkTrafficUsage: FC = () => {
data={data}
plugins={[customHighlightPlugin()]}
options={options}
aria-label='An instance metrics line chart with two lines: requests per second for admin API and requests per second for client API' // todo: this isn't correct at all!
aria-label={chartLabel}
/>
</StyledBox>
</>

View File

@ -0,0 +1,156 @@
import { Link, styled } from '@mui/material';
import { Badge } from 'component/common/Badge/Badge';
import { subMonths } from 'date-fns';
import type { ChartDataSelection } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import { useLocationSettings } from 'hooks/useLocationSettings';
import type { FC } from 'react';
type Props = {
period: ChartDataSelection;
usageTotal: number;
includedTraffic: number;
};
const Container = styled('article')(({ theme }) => ({
minWidth: '200px',
border: `2px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusLarge,
padding: theme.spacing(3),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
}));
const Header = styled('h3')(({ theme }) => ({
margin: 0,
fontSize: theme.typography.body1.fontSize,
}));
const List = styled('dl')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2.5),
padding: 0,
margin: 0,
}));
const Row = styled('div')(({ theme }) => ({
display: 'flex',
flexFlow: 'row wrap',
justifyContent: 'space-between',
gap: theme.spacing(1, 3),
fontSize: theme.typography.body2.fontSize,
color: theme.palette.text.secondary,
'& dd': {
margin: 0,
color: theme.palette.text.primary,
},
}));
const incomingRequestsText = (period: ChartDataSelection): string => {
const formatMonth = (date: Date) =>
date.toLocaleString('en-US', {
month: 'short',
year: 'numeric',
});
if (period.grouping === 'monthly') {
const currentMonth = new Date();
const fromMonth = subMonths(currentMonth, period.monthsBack);
const toMonth = subMonths(currentMonth, 1);
return `Average requests from ${formatMonth(fromMonth)} to ${formatMonth(toMonth)}`;
}
return `Incoming requests in ${formatMonth(new Date(period.month))}`;
};
export const RequestSummary: FC<Props> = ({
period,
usageTotal,
includedTraffic,
}) => {
const { locationSettings } = useLocationSettings();
return (
<Container>
<Header>Number of requests to Unleash</Header>
<List>
<Row>
<dt>{incomingRequestsText(period)}</dt>
<dd>
<Badge
color={
includedTraffic > 0
? usageTotal <= includedTraffic
? 'success'
: 'error'
: 'neutral'
}
tabIndex={-1}
>
{usageTotal.toLocaleString(
locationSettings.locale ?? 'en-US',
)}{' '}
requests
</Badge>
</dd>
</Row>
{includedTraffic > 0 && (
<Row>
<dt>Included in your plan monthly</dt>
<dd>
{includedTraffic.toLocaleString('en-US')} requests
</dd>
</Row>
)}
</List>
</Container>
);
};
type OverageProps = {
overages: number;
overageCost: number;
estimatedMonthlyCost: number;
};
export const OverageInfo: FC<OverageProps> = ({
overages,
overageCost,
estimatedMonthlyCost,
}) => {
return (
<Container>
<Header>Accrued traffic charges</Header>
<List>
<Row>
<dt>
Request overages this month (
<Link href='https://www.getunleash.io/pricing'>
pricing
</Link>
)
</dt>
<dd>{overages.toLocaleString()} requests</dd>
</Row>
<Row>
<dt>Accrued traffic charges</dt>
<dd>
<Badge color='secondary'>{overageCost} USD</Badge>
</dd>
</Row>
{estimatedMonthlyCost > 0 && (
<Row>
<dt>Estimated charges based on current usage</dt>
<dd>
<Badge color='secondary'>
{estimatedMonthlyCost} USD
</Badge>
</dd>
</Row>
)}
</List>
</Container>
);
};

View File

@ -0,0 +1,34 @@
import { differenceInCalendarMonths, format } from 'date-fns';
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi';
export const averageTrafficPreviousMonths = (
endpointData: string[],
traffic: TrafficUsageDataSegmentedCombinedSchema,
) => {
if (!traffic || traffic.grouping === 'daily') {
return 0;
}
const monthsToCount = Math.abs(
differenceInCalendarMonths(
new Date(traffic.dateRange.to),
new Date(traffic.dateRange.from),
),
);
const currentMonth = format(new Date(), 'yyyy-MM');
const totalTraffic = traffic.apiData
.filter((endpoint) => endpointData.includes(endpoint.apiPath))
.map((endpoint) =>
endpoint.dataPoints
.filter(({ period }) => period !== currentMonth)
.reduce(
(acc, current) => acc + current.trafficTypes[0].count,
0,
),
)
.reduce((total, next) => total + next, 0);
return Math.round(totalTraffic / monthsToCount);
};

View File

@ -29,6 +29,7 @@ interface IBadgeProps {
children?: ReactNode;
title?: string;
onClick?: (event: React.SyntheticEvent) => void;
tabIndex?: number;
}
interface IBadgeIconProps {
@ -96,13 +97,14 @@ export const Badge: FC<IBadgeProps> = forwardRef(
className,
sx,
children,
tabIndex = 0,
...props
}: IBadgeProps,
ref: ForwardedRef<HTMLDivElement>,
) => (
<StyledBadge
as={as}
tabIndex={0}
tabIndex={tabIndex}
color={color}
icon={icon}
className={className}

View File

@ -172,7 +172,9 @@ const toMonthlyChartData = (
);
const labels = Array.from({ length: numMonths }).map((_, index) =>
formatMonth(addMonths(from, index)),
index === numMonths - 1
? 'Current month'
: formatMonth(addMonths(from, index)),
);
return { datasets, labels };