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:  The current ones (from wide to narrower): Wide  Mid (the text should probably ideally wrap at the same time here, but I'm not sure how at the moment)  Narrow  Extra narrow:  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:
parent
eba183a12c
commit
e72a7c1197
@ -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>
|
||||
</>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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);
|
||||
};
|
@ -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}
|
||||
|
@ -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 };
|
||||
|
Loading…
Reference in New Issue
Block a user