Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update logic to conditionally show Get Started and Billing routes #3807

Merged
merged 4 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions frontend/src/AppRoutes/Private.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
[pathname],
);

const { data: licensesData } = useLicense();
const {
data: licensesData,
isFetching: isFetchingLicensesData,
} = useLicense();

const {
user,
isUserFetching,
isUserFetchingError,
isLoggedIn: isLoggedInState,
Expand Down Expand Up @@ -116,7 +118,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
if (
localStorageUserAuthToken &&
localStorageUserAuthToken.refreshJwt &&
user?.userId === ''
isUserFetching
) {
handleUserLoginIfTokenPresent(key);
} else {
Expand All @@ -131,28 +133,34 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {

if (path && path !== ROUTES.WORKSPACE_LOCKED) {
history.push(ROUTES.WORKSPACE_LOCKED);
}

dispatch({
type: UPDATE_USER_IS_FETCH,
payload: {
isUserFetching: false,
},
});
dispatch({
type: UPDATE_USER_IS_FETCH,
payload: {
isUserFetching: false,
},
});
}
};

useEffect(() => {
if (!isFetchingLicensesData) {
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;

if (shouldBlockWorkspace) {
navigateToWorkSpaceBlocked(currentRoute);
}
}
}, [isFetchingLicensesData]);

// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
(async (): Promise<void> => {
try {
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;

if (currentRoute) {
const { isPrivate, key } = currentRoute;

if (shouldBlockWorkspace) {
navigateToWorkSpaceBlocked(currentRoute);
} else if (isPrivate) {
if (isPrivate && key !== ROUTES.WORKSPACE_LOCKED) {
handlePrivateRoutes(key);
} else {
// no need to fetch the user and make user fetching false
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/AppRoutes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ const routes: AppRoutes[] = [
path: ROUTES.WORKSPACE_LOCKED,
exact: true,
component: WorkspaceBlocked,
isPrivate: false,
isPrivate: true,
key: 'WORKSPACE_LOCKED',
},
];
Expand Down
73 changes: 45 additions & 28 deletions frontend/src/container/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import ROUTES from 'constants/routes';
import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import history from 'lib/history';
import { LifeBuoy } from 'lucide-react';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { sideBarCollapse } from 'store/actions/app';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { checkVersionState, isCloudUser, isEECloudUser } from 'utils/app';

import { routeConfig, styles } from './config';
Expand All @@ -33,6 +40,7 @@ import {

function SideNav(): JSX.Element {
const dispatch = useDispatch();
const [menuItems, setMenuItems] = useState(defaultMenuItems);
const [collapsed, setCollapsed] = useState<boolean>(
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
);
Expand All @@ -44,36 +52,45 @@ function SideNav(): JSX.Element {
featureResponse,
} = useSelector<AppState, AppReducer>((state) => state.app);

const { data } = useLicense();
const { data, isFetching } = useLicense();

let secondaryMenuItems: MenuItem[] = [];

const isOnBasicPlan =
data?.payload?.licenses?.some(
(license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || data?.payload?.licenses === null;

const menuItems = useMemo(
() =>
defaultMenuItems.filter((item) => {
const isOnboardingEnabled =
featureResponse.data?.find(
(feature) => feature.name === FeatureKeys.ONBOARDING,
)?.active || false;

if (role !== 'ADMIN' || isOnBasicPlan) {
return item.key !== ROUTES.BILLING;
}

if (!isOnboardingEnabled || !isCloudUser()) {
return item.key !== ROUTES.GET_STARTED;
}

return true;
}),
[featureResponse.data, isOnBasicPlan, role],
);
useEffect((): void => {
palashgdev marked this conversation as resolved.
Show resolved Hide resolved
const isOnboardingEnabled =
featureResponse.data?.find(
(feature) => feature.name === FeatureKeys.ONBOARDING,
)?.active || false;

if (!isOnboardingEnabled || !isCloudUser()) {
let items = [...menuItems];

items = items.filter((item) => item.key !== ROUTES.GET_STARTED);

setMenuItems(items);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [featureResponse.data]);

// using a separate useEffect as the license fetching call takes few milliseconds
useEffect(() => {
if (!isFetching) {
let items = [...menuItems];

const isOnBasicPlan =
data?.payload?.licenses?.some(
(license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || data?.payload?.licenses === null;

if (role !== USER_ROLES.ADMIN || isOnBasicPlan) {
items = items.filter((item) => item.key !== ROUTES.BILLING);
}

setMenuItems(items);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.payload?.licenses, isFetching, role]);

const { pathname, search } = useLocation();

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useLicense/constant.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const LICENSE_PLAN_KEY = {
ENTERPRISE_PLAN: 'ENTERPRISE_PLAN',
BASIC_PLAN: 'BASIC_PLAN ',
BASIC_PLAN: 'BASIC_PLAN',
palashgdev marked this conversation as resolved.
Show resolved Hide resolved
};

export const LICENSE_PLAN_STATUS = {
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/mocks-server/__mockdata__/licenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ export const licensesSuccessResponse = {
workSpaceBlock: false,
trialConvertedToSubscription: false,
gracePeriodEnd: -1,
licenses: [
{
key: 'testKeyId1',
activationId: 'testActivationId1',
ValidationMessage: '',
isCurrent: false,
planKey: 'ENTERPRISE_PLAN',
ValidFrom: '2022-10-13T13:58:51Z',
ValidUntil: '2023-10-13T19:57:37Z',
status: 'VALID',
},
{
key: 'testKeyId2',
activationId: 'testActivationId2',
ValidationMessage: '',
isCurrent: true,
planKey: 'ENTERPRISE_PLAN',
ValidFrom: '2023-09-12T11:55:43Z',
ValidUntil: '2024-09-11T17:34:29Z',
status: 'VALID',
},
],
},
};

export const licensesSuccessWorkspaceLockedResponse = {
status: 'success',
data: {
trialStart: 1695992049,
trialEnd: 1697806449,
onTrial: false,
workSpaceBlock: true,
trialConvertedToSubscription: false,
gracePeriodEnd: -1,
licenses: [
{
key: 'testKeyId1',
Expand Down
38 changes: 31 additions & 7 deletions frontend/src/pages/WorkspaceLocked/WorkspaceLocked.test.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,70 @@
import { licensesSuccessWorkspaceLockedResponse } from 'mocks-server/__mockdata__/licenses';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { act, render, screen } from 'tests/test-utils';

import WorkspaceLocked from '.';

describe('WorkspaceLocked', () => {
const apiURL = 'http://localhost/api/v2/licenses';

test('Should render the component', async () => {
server.use(
rest.get(apiURL, (req, res, ctx) =>
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
),
);

act(() => {
render(<WorkspaceLocked />);
});
const workspaceLocked = screen.getByRole('heading', {

const workspaceLocked = await screen.findByRole('heading', {
name: /workspace locked/i,
});
expect(workspaceLocked).toBeInTheDocument();

const gotQuestionText = screen.getByText(/got question?/i);
const gotQuestionText = await screen.findByText(/got question?/i);
expect(gotQuestionText).toBeInTheDocument();

const contactUsLink = screen.getByRole('link', {
const contactUsLink = await screen.findByRole('link', {
name: /contact us/i,
});
expect(contactUsLink).toBeInTheDocument();
});

test('Render for Admin', async () => {
server.use(
rest.get(apiURL, (req, res, ctx) =>
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
),
);

render(<WorkspaceLocked />);
const contactAdminMessage = screen.queryByText(
const contactAdminMessage = await screen.queryByText(
/please contact your administrator for further help/i,
);
expect(contactAdminMessage).not.toBeInTheDocument();
const updateCreditCardBtn = screen.getByRole('button', {
const updateCreditCardBtn = await screen.findByRole('button', {
name: /update credit card/i,
});
expect(updateCreditCardBtn).toBeInTheDocument();
});

test('Render for non Admin', async () => {
server.use(
rest.get(apiURL, (req, res, ctx) =>
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
),
);

render(<WorkspaceLocked />, {}, 'VIEWER');
const updateCreditCardBtn = screen.queryByRole('button', {
const updateCreditCardBtn = await screen.queryByRole('button', {
name: /update credit card/i,
});
expect(updateCreditCardBtn).not.toBeInTheDocument();

const contactAdminMessage = screen.getByText(
const contactAdminMessage = await screen.findByText(
/please contact your administrator for further help/i,
);
expect(contactAdminMessage).toBeInTheDocument();
Expand Down
Loading
Loading