mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-15 19:21:31 +08:00

* feat: setup the app context to fetch users,licenses and feature flags * feat: added global event listeners for after_login event * feat: remove redux from app state and private route * feat: syncronize the approutes file * feat: cleanup the private routes * feat: handle login and logout * feat: cleanup the app layout file * feat: cleanup and syncronize side nav item * fix: minor small re-render issue * feat: parallel processing for sync calls for faster bootup of application * feat: some refactoring for private routes * fix: entire application too much re-rendering * fix: remove redux * feat: some more corrections * feat: fix all the files except signup * feat: add app provider to the test-utils * feat: should fix a lot of tests * chore: fix more tests * chore: fix more tests * feat: fix some tests and corrected the redux mock * feat: delete snapshot * fix: test cases * fix: pipeline actions test cases * fix: billing test cases * feat: update the signup API to accept isAnonymous and hasOptedUpdates * chore: cleanup the console logs * fix: indefinite loading on manage licenses screen * fix: better handling and route to something_went_wrong in case of qs down * fix: signup for subsequent users * chore: update test-utils * fix: jerky behaviour on entering the home page * feat: handle the retention for login context flow * fix: do not let users workaround workspace blocked screen
225 lines
6.0 KiB
TypeScript
225 lines
6.0 KiB
TypeScript
import getLocalStorageApi from 'api/browser/localstorage/get';
|
|
import setLocalStorageApi from 'api/browser/localstorage/set';
|
|
import getOrgUser from 'api/user/getOrgUser';
|
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
import ROUTES from 'constants/routes';
|
|
import history from 'lib/history';
|
|
import { isEmpty } from 'lodash-es';
|
|
import { useAppContext } from 'providers/App/App';
|
|
import { ReactChild, useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { useQuery } from 'react-query';
|
|
import { matchPath, useLocation } from 'react-router-dom';
|
|
import { LicenseState, LicenseStatus } from 'types/api/licensesV3/getActive';
|
|
import { Organization } from 'types/api/user/getOrganization';
|
|
import { isCloudUser } from 'utils/app';
|
|
import { routePermission } from 'utils/permission';
|
|
|
|
import routes, {
|
|
LIST_LICENSES,
|
|
oldNewRoutesMapping,
|
|
oldRoutes,
|
|
} from './routes';
|
|
|
|
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|
const location = useLocation();
|
|
const { pathname } = location;
|
|
const {
|
|
org,
|
|
orgPreferences,
|
|
user,
|
|
isLoggedIn: isLoggedInState,
|
|
isFetchingOrgPreferences,
|
|
licenses,
|
|
isFetchingLicenses,
|
|
activeLicenseV3,
|
|
isFetchingActiveLicenseV3,
|
|
} = useAppContext();
|
|
const mapRoutes = useMemo(
|
|
() =>
|
|
new Map(
|
|
[...routes, LIST_LICENSES].map((e) => {
|
|
const currentPath = matchPath(pathname, {
|
|
path: e.path,
|
|
});
|
|
return [currentPath === null ? null : 'current', e];
|
|
}),
|
|
),
|
|
[pathname],
|
|
);
|
|
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
|
const currentRoute = mapRoutes.get('current');
|
|
const isCloudUserVal = isCloudUser();
|
|
|
|
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
|
|
|
const { data: orgUsers, isFetching: isFetchingOrgUsers } = useQuery({
|
|
queryFn: () => {
|
|
if (orgData && orgData.id !== undefined) {
|
|
return getOrgUser({
|
|
orgId: orgData.id,
|
|
});
|
|
}
|
|
return undefined;
|
|
},
|
|
queryKey: ['getOrgUser'],
|
|
enabled: !isEmpty(orgData) && user.role === 'ADMIN',
|
|
});
|
|
|
|
const checkFirstTimeUser = useCallback((): boolean => {
|
|
const users = orgUsers?.payload || [];
|
|
|
|
const remainingUsers = users.filter(
|
|
(user) => user.email !== 'admin@signoz.cloud',
|
|
);
|
|
|
|
return remainingUsers.length === 1;
|
|
}, [orgUsers?.payload]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
isCloudUserVal &&
|
|
!isFetchingOrgPreferences &&
|
|
orgPreferences &&
|
|
!isFetchingOrgUsers &&
|
|
orgUsers &&
|
|
orgUsers.payload
|
|
) {
|
|
const isOnboardingComplete = orgPreferences?.find(
|
|
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
|
)?.value;
|
|
|
|
const isFirstUser = checkFirstTimeUser();
|
|
if (isFirstUser && !isOnboardingComplete) {
|
|
history.push(ROUTES.ONBOARDING);
|
|
}
|
|
}
|
|
}, [
|
|
checkFirstTimeUser,
|
|
isCloudUserVal,
|
|
isFetchingOrgPreferences,
|
|
isFetchingOrgUsers,
|
|
orgPreferences,
|
|
orgUsers,
|
|
pathname,
|
|
]);
|
|
|
|
const navigateToWorkSpaceBlocked = (route: any): void => {
|
|
const { path } = route;
|
|
|
|
if (path && path !== ROUTES.WORKSPACE_LOCKED) {
|
|
history.push(ROUTES.WORKSPACE_LOCKED);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!isFetchingLicenses) {
|
|
const currentRoute = mapRoutes.get('current');
|
|
const shouldBlockWorkspace = licenses?.workSpaceBlock;
|
|
|
|
if (shouldBlockWorkspace && currentRoute) {
|
|
navigateToWorkSpaceBlocked(currentRoute);
|
|
}
|
|
}
|
|
}, [isFetchingLicenses, licenses?.workSpaceBlock, mapRoutes, pathname]);
|
|
|
|
const navigateToWorkSpaceSuspended = (route: any): void => {
|
|
const { path } = route;
|
|
|
|
if (path && path !== ROUTES.WORKSPACE_SUSPENDED) {
|
|
history.push(ROUTES.WORKSPACE_SUSPENDED);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
|
const currentRoute = mapRoutes.get('current');
|
|
const shouldSuspendWorkspace =
|
|
activeLicenseV3.status === LicenseStatus.SUSPENDED &&
|
|
activeLicenseV3.state === LicenseState.PAYMENT_FAILED;
|
|
|
|
if (shouldSuspendWorkspace && currentRoute) {
|
|
navigateToWorkSpaceSuspended(currentRoute);
|
|
}
|
|
}
|
|
}, [isFetchingActiveLicenseV3, activeLicenseV3, mapRoutes, pathname]);
|
|
|
|
useEffect(() => {
|
|
if (org && org.length > 0 && org[0].id !== undefined) {
|
|
setOrgData(org[0]);
|
|
}
|
|
}, [org]);
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
useEffect(() => {
|
|
// if it is an old route navigate to the new route
|
|
if (isOldRoute) {
|
|
const redirectUrl = oldNewRoutesMapping[pathname];
|
|
|
|
const newLocation = {
|
|
...location,
|
|
pathname: redirectUrl,
|
|
};
|
|
history.replace(newLocation);
|
|
}
|
|
// if the current route
|
|
if (currentRoute) {
|
|
const { isPrivate, key } = currentRoute;
|
|
if (isPrivate) {
|
|
if (isLoggedInState) {
|
|
const route = routePermission[key];
|
|
if (route && route.find((e) => e === user.role) === undefined) {
|
|
history.push(ROUTES.UN_AUTHORIZED);
|
|
}
|
|
} else {
|
|
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, pathname);
|
|
history.push(ROUTES.LOGIN);
|
|
}
|
|
} else if (isLoggedInState) {
|
|
const fromPathname = getLocalStorageApi(
|
|
LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT,
|
|
);
|
|
if (fromPathname) {
|
|
history.push(fromPathname);
|
|
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
|
|
} else {
|
|
history.push(ROUTES.APPLICATION);
|
|
}
|
|
} else {
|
|
// do nothing as the unauthenticated routes are LOGIN and SIGNUP and the LOGIN container takes care of routing to signup if
|
|
// setup is not completed
|
|
}
|
|
} else if (isLoggedInState) {
|
|
const fromPathname = getLocalStorageApi(
|
|
LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT,
|
|
);
|
|
if (fromPathname) {
|
|
history.push(fromPathname);
|
|
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
|
|
} else {
|
|
history.push(ROUTES.APPLICATION);
|
|
}
|
|
} else {
|
|
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, pathname);
|
|
history.push(ROUTES.LOGIN);
|
|
}
|
|
}, [
|
|
licenses,
|
|
isLoggedInState,
|
|
pathname,
|
|
user,
|
|
isOldRoute,
|
|
currentRoute,
|
|
location,
|
|
]);
|
|
|
|
// NOTE: disabling this rule as there is no need to have div
|
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
return <>{children}</>;
|
|
}
|
|
|
|
interface PrivateRouteProps {
|
|
children: ReactChild;
|
|
}
|
|
|
|
export default PrivateRoute;
|