1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

refactor: fix trial expiration calculations (#1090)

* refactor: fix trial expiration calculations

* refactor: count full trial days for warning banner

* refactor: fix flaky test
This commit is contained in:
olav 2022-06-14 11:51:11 +02:00 committed by GitHub
parent 67a4f2e67f
commit f46047f10a
7 changed files with 135 additions and 48 deletions

View File

@ -9,7 +9,7 @@ import {
InstanceState,
InstancePlan,
} from 'interfaces/instance';
import { calculateTrialDaysRemaining } from 'utils/billing';
import { hasTrialExpired } from 'utils/instanceTrial';
import { GridRow } from 'component/common/GridRow/GridRow';
import { GridCol } from 'component/common/GridCol/GridCol';
import { GridColLink } from './GridColLink/GridColLink';
@ -81,7 +81,7 @@ interface IBillingPlanProps {
export const BillingPlan: FC<IBillingPlanProps> = ({ instanceStatus }) => {
const { users } = useUsers();
const trialDaysRemaining = calculateTrialDaysRemaining(instanceStatus);
const trialHasExpired = hasTrialExpired(instanceStatus);
const price = {
[InstancePlan.PRO]: 80,
@ -91,11 +91,6 @@ export const BillingPlan: FC<IBillingPlanProps> = ({ instanceStatus }) => {
user: 15,
};
const statusExpired =
instanceStatus.state === InstanceState.TRIAL &&
typeof trialDaysRemaining === 'number' &&
trialDaysRemaining <= 0;
const planPrice = price[instanceStatus.plan];
const seats = instanceStatus.seats ?? 5;
const freeAssigned = Math.min(users.length, seats);
@ -135,12 +130,12 @@ export const BillingPlan: FC<IBillingPlanProps> = ({ instanceStatus }) => {
show={
<StyledTrialSpan
sx={theme => ({
color: statusExpired
color: trialHasExpired
? theme.palette.error.dark
: theme.palette.warning.dark,
})}
>
{statusExpired
{trialHasExpired
? 'Trial expired'
: instanceStatus.trialExtended
? 'Extended Trial'

View File

@ -9,7 +9,7 @@ import { IInstanceStatus, InstanceState } from 'interfaces/instance';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AccessContext from 'contexts/AccessContext';
import useInstanceStatusApi from 'hooks/api/actions/useInstanceStatusApi/useInstanceStatusApi';
import { calculateTrialDaysRemaining } from 'utils/billing';
import { hasTrialExpired } from 'utils/instanceTrial';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
@ -24,22 +24,16 @@ const TrialDialog: VFC<ITrialDialogProps> = ({
}) => {
const { hasAccess } = useContext(AccessContext);
const navigate = useNavigate();
const trialDaysRemaining = calculateTrialDaysRemaining(instanceStatus);
const statusExpired =
instanceStatus.state === InstanceState.TRIAL &&
typeof trialDaysRemaining === 'number' &&
trialDaysRemaining <= 0;
const [dialogOpen, setDialogOpen] = useState(statusExpired);
const trialHasExpired = hasTrialExpired(instanceStatus);
const [dialogOpen, setDialogOpen] = useState(trialHasExpired);
useEffect(() => {
setDialogOpen(statusExpired);
setDialogOpen(trialHasExpired);
const interval = setInterval(() => {
setDialogOpen(statusExpired);
setDialogOpen(trialHasExpired);
}, 60000);
return () => clearInterval(interval);
}, [statusExpired]);
}, [trialHasExpired]);
if (hasAccess(ADMIN)) {
return (

View File

@ -1,5 +1,5 @@
import { styled, Button, Typography } from '@mui/material';
import { IInstanceStatus, InstanceState } from 'interfaces/instance';
import { IInstanceStatus } from 'interfaces/instance';
import { INSTANCE_STATUS_BAR_ID } from 'utils/testIds';
import { InfoOutlined, WarningAmber } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
@ -7,7 +7,10 @@ import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { calculateTrialDaysRemaining } from 'utils/billing';
import {
hasTrialExpired,
formatTrialExpirationWarning,
} from 'utils/instanceTrial';
const StyledWarningBar = styled('aside')(({ theme }) => ({
position: 'relative',
@ -59,14 +62,10 @@ export const InstanceStatusBar = ({
instanceStatus,
}: IInstanceStatusBarProps) => {
const { hasAccess } = useContext(AccessContext);
const trialHasExpired = hasTrialExpired(instanceStatus);
const trialExpirationWarning = formatTrialExpirationWarning(instanceStatus);
const trialDaysRemaining = calculateTrialDaysRemaining(instanceStatus);
if (
instanceStatus.state === InstanceState.TRIAL &&
typeof trialDaysRemaining === 'number' &&
trialDaysRemaining <= 0
) {
if (trialHasExpired) {
return (
<StyledWarningBar data-testid={INSTANCE_STATUS_BAR_ID}>
<StyledWarningIcon />
@ -87,11 +86,7 @@ export const InstanceStatusBar = ({
);
}
if (
instanceStatus.state === InstanceState.TRIAL &&
typeof trialDaysRemaining === 'number' &&
trialDaysRemaining <= 10
) {
if (trialExpirationWarning) {
return (
<StyledInfoBar data-testid={INSTANCE_STATUS_BAR_ID}>
<StyledInfoIcon />
@ -101,7 +96,7 @@ export const InstanceStatusBar = ({
})}
>
<strong>Heads up!</strong> You have{' '}
<strong>{trialDaysRemaining} days</strong> left of your free{' '}
<strong>{trialExpirationWarning}</strong> left of your free{' '}
{instanceStatus.plan} trial.
</Typography>
<ConditionallyRender

View File

@ -65,8 +65,7 @@ exports[`InstanceStatusBar should warn when the trial is about to expire 1`] = `
You have
<strong>
4
days
4 days
</strong>
left of your free

View File

@ -1,10 +0,0 @@
import { differenceInDays, parseISO } from 'date-fns';
import { IInstanceStatus } from 'interfaces/instance';
export const calculateTrialDaysRemaining = (
instanceStatus?: IInstanceStatus
): number | undefined => {
return instanceStatus?.trialExpiry
? differenceInDays(parseISO(instanceStatus.trialExpiry), new Date())
: undefined;
};

View File

@ -0,0 +1,66 @@
import {
hasTrialExpired,
formatTrialExpirationWarning,
} from 'utils/instanceTrial';
import { InstancePlan, InstanceState } from 'interfaces/instance';
import { subHours, addHours, addMinutes, subMinutes } from 'date-fns';
test.each([
undefined,
{ plan: InstancePlan.UNKNOWN },
{ plan: InstancePlan.UNKNOWN, state: InstanceState.ACTIVE },
{ plan: InstancePlan.UNKNOWN, state: InstanceState.TRIAL },
{ plan: InstancePlan.COMPANY, state: InstanceState.TRIAL },
{ plan: InstancePlan.PRO, state: InstanceState.TRIAL },
])('unknown trial states should not count as expired', input => {
expect(hasTrialExpired(input)).toEqual(false);
expect(formatTrialExpirationWarning(input)).toEqual(undefined);
});
test('hasTrialExpired', () => {
expect(
hasTrialExpired({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: subHours(new Date(), 2).toISOString(),
})
).toEqual(true);
expect(
hasTrialExpired({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: addHours(new Date(), 2).toISOString(),
})
).toEqual(false);
});
test('formatTrialExpirationWarning', () => {
expect(
formatTrialExpirationWarning({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: subMinutes(new Date(), 1).toISOString(),
})
).toEqual(undefined);
expect(
formatTrialExpirationWarning({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: addMinutes(new Date(), 23 * 60 + 1).toISOString(),
})
).toEqual('23 hours');
expect(
formatTrialExpirationWarning({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: addHours(new Date(), 25).toISOString(),
})
).toEqual('1 day');
expect(
formatTrialExpirationWarning({
plan: InstancePlan.UNKNOWN,
state: InstanceState.TRIAL,
trialExpiry: addHours(new Date(), 24 * 11 - 1).toISOString(),
})
).toEqual('10 days');
});

View File

@ -0,0 +1,48 @@
import { parseISO, formatDistanceToNowStrict, isPast } from 'date-fns';
import { IInstanceStatus, InstanceState } from 'interfaces/instance';
import differenceInDays from 'date-fns/differenceInDays';
const TRIAL_EXPIRATION_WARNING_DAYS_THRESHOLD = 10;
export const hasTrialExpired = (
instanceStatus: IInstanceStatus | undefined
): boolean => {
const trialExpiry = parseTrialExpiryDate(instanceStatus);
if (!trialExpiry) {
return false;
}
return isPast(trialExpiry);
};
export const formatTrialExpirationWarning = (
instanceStatus: IInstanceStatus | undefined
): string | undefined => {
const trialExpiry = parseTrialExpiryDate(instanceStatus);
if (!trialExpiry || isPast(trialExpiry)) {
return;
}
if (
differenceInDays(trialExpiry, new Date()) <=
TRIAL_EXPIRATION_WARNING_DAYS_THRESHOLD
) {
return formatDistanceToNowStrict(trialExpiry, {
roundingMethod: 'floor',
});
}
};
const parseTrialExpiryDate = (
instanceStatus: IInstanceStatus | undefined
): Date | undefined => {
if (
instanceStatus &&
instanceStatus.state === InstanceState.TRIAL &&
instanceStatus.trialExpiry
) {
return parseISO(instanceStatus.trialExpiry);
}
};