From 583d6361448f8caa78308e5e3ef37d87037d9de1 Mon Sep 17 00:00:00 2001 From: olav Date: Tue, 21 Jun 2022 11:22:27 +0200 Subject: [PATCH] refactor: fix handling of expired/churned trial states (#1107) --- .../BillingPlan/BillingPlan.tsx | 12 +- .../common/InstanceStatus/InstanceStatus.tsx | 35 +++--- .../InstanceStatus/InstanceStatusBar.test.tsx | 56 +++++++-- .../InstanceStatus/InstanceStatusBar.tsx | 110 ++++++++++------- .../InstanceStatusBar.test.tsx.snap | 116 +++++++++++++++++- frontend/src/utils/instanceTrial.test.ts | 115 +++++++++-------- frontend/src/utils/instanceTrial.ts | 80 +++++++----- 7 files changed, 358 insertions(+), 166 deletions(-) diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx index 569b37a4b7..8cd01433ff 100644 --- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx +++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx @@ -9,7 +9,7 @@ import { InstanceState, InstancePlan, } from 'interfaces/instance'; -import { hasTrialExpired } from 'utils/instanceTrial'; +import { trialHasExpired, isTrialInstance } 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 = ({ instanceStatus }) => { const { users } = useUsers(); - const trialHasExpired = hasTrialExpired(instanceStatus); + const expired = trialHasExpired(instanceStatus); const price = { [InstancePlan.PRO]: 80, @@ -124,18 +124,16 @@ export const BillingPlan: FC = ({ instanceStatus }) => { {instanceStatus.plan} ({ - color: trialHasExpired + color: expired ? theme.palette.error.dark : theme.palette.warning.dark, })} > - {trialHasExpired + {expired ? 'Trial expired' : instanceStatus.trialExtended ? 'Extended Trial' diff --git a/frontend/src/component/common/InstanceStatus/InstanceStatus.tsx b/frontend/src/component/common/InstanceStatus/InstanceStatus.tsx index 70e63bea00..242d5473f1 100644 --- a/frontend/src/component/common/InstanceStatus/InstanceStatus.tsx +++ b/frontend/src/component/common/InstanceStatus/InstanceStatus.tsx @@ -5,11 +5,11 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Typography } from '@mui/material'; import { useNavigate } from 'react-router-dom'; -import { IInstanceStatus, InstanceState } from 'interfaces/instance'; +import { IInstanceStatus } from 'interfaces/instance'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import AccessContext from 'contexts/AccessContext'; import useInstanceStatusApi from 'hooks/api/actions/useInstanceStatusApi/useInstanceStatusApi'; -import { hasTrialExpired } from 'utils/instanceTrial'; +import { trialHasExpired, canExtendTrial } from 'utils/instanceTrial'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; @@ -24,16 +24,16 @@ const TrialDialog: VFC = ({ }) => { const { hasAccess } = useContext(AccessContext); const navigate = useNavigate(); - const trialHasExpired = hasTrialExpired(instanceStatus); - const [dialogOpen, setDialogOpen] = useState(trialHasExpired); + const expired = trialHasExpired(instanceStatus); + const [dialogOpen, setDialogOpen] = useState(expired); useEffect(() => { - setDialogOpen(trialHasExpired); + setDialogOpen(expired); const interval = setInterval(() => { - setDialogOpen(trialHasExpired); + setDialogOpen(expired); }, 60000); return () => clearInterval(interval); - }, [trialHasExpired]); + }, [expired]); if (hasAccess(ADMIN)) { return ( @@ -41,9 +41,9 @@ const TrialDialog: VFC = ({ open={dialogOpen} primaryButtonText="Upgrade trial" secondaryButtonText={ - instanceStatus?.trialExtended - ? 'Remind me later' - : 'Extend trial (5 days)' + canExtendTrial(instanceStatus) + ? 'Extend trial (5 days)' + : 'Remind me later' } onClick={() => { navigate('/admin/billing'); @@ -92,16 +92,11 @@ export const InstanceStatus: FC = ({ children }) => { const { setToastApiError } = useToast(); const onExtendTrial = async () => { - if ( - instanceStatus?.state === InstanceState.TRIAL && - !instanceStatus?.trialExtended - ) { - try { - await extendTrial(); - await refetchInstanceStatus(); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } + try { + await extendTrial(); + await refetchInstanceStatus(); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/common/InstanceStatus/InstanceStatusBar.test.tsx b/frontend/src/component/common/InstanceStatus/InstanceStatusBar.test.tsx index 726e788b85..8791740e68 100644 --- a/frontend/src/component/common/InstanceStatus/InstanceStatusBar.test.tsx +++ b/frontend/src/component/common/InstanceStatus/InstanceStatusBar.test.tsx @@ -2,7 +2,7 @@ import { InstanceStatusBar } from 'component/common/InstanceStatus/InstanceStatu import { InstancePlan, InstanceState } from 'interfaces/instance'; import { render } from 'utils/testRenderer'; import { screen } from '@testing-library/react'; -import { addDays } from 'date-fns'; +import { addDays, subDays } from 'date-fns'; import { INSTANCE_STATUS_BAR_ID } from 'utils/testIds'; import { UNKNOWN_INSTANCE_STATUS } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; @@ -14,7 +14,22 @@ test('InstanceStatusBar should be hidden by default', async () => { ).not.toBeInTheDocument(); }); -test('InstanceStatusBar should be hidden when the trial is far from expired', async () => { +test('InstanceStatusBar should be hidden when state is active', async () => { + render( + + ); + + expect( + screen.queryByTestId(INSTANCE_STATUS_BAR_ID) + ).not.toBeInTheDocument(); +}); + +test('InstanceStatusBar should warn when the trial is far from expired', async () => { render(