feat: handle onboarding visibility

This commit is contained in:
Yunus M 2024-10-29 19:57:00 +05:30
parent a1090bfdc5
commit 845dc00568
10 changed files with 372 additions and 134 deletions

View File

@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import getLocalStorageApi from 'api/browser/localstorage/get';
import getOrgUser from 'api/user/getOrgUser';
import loginApi from 'api/user/login';
import { Logout } from 'api/utils';
import Spinner from 'components/Spinner';
@ -8,8 +9,10 @@ import ROUTES from 'constants/routes';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { ReactChild, useEffect, useMemo } from 'react';
import { isEmpty } from 'lodash-es';
import { ReactChild, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { matchPath, Redirect, useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
@ -17,6 +20,7 @@ import { AppState } from 'store/reducers';
import { getInitialUserTokenRefreshToken } from 'store/utils';
import AppActions from 'types/actions';
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
import { Organization } from 'types/api/user/getOrganization';
import AppReducer from 'types/reducer/app';
import { routePermission } from 'utils/permission';
@ -31,6 +35,14 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const location = useLocation();
const { pathname } = location;
const { org, orgPreferences } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const [isOnboardingComplete, setIsOnboardingComplete] = useState<
boolean | null
>(null);
const mapRoutes = useMemo(
() =>
new Map(
@ -44,6 +56,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
[pathname],
);
useEffect(() => {
if (orgPreferences && !isEmpty(orgPreferences)) {
const onboardingPreference = orgPreferences?.find(
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
);
if (onboardingPreference) {
setIsOnboardingComplete(onboardingPreference.value);
}
}
}, [orgPreferences]);
const {
data: licensesData,
isFetching: isFetchingLicensesData,
@ -53,9 +77,11 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
isUserFetching,
isUserFetchingError,
isLoggedIn: isLoggedInState,
isFetchingOrgPreferences,
} = useSelector<AppState, AppReducer>((state) => state.app);
const { t } = useTranslation(['common']);
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
const dispatch = useDispatch<Dispatch<AppActions>>();
@ -66,6 +92,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
const isLocalStorageLoggedIn =
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
@ -81,6 +109,42 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
};
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
queryFn: () => {
if (orgData && orgData.id !== undefined) {
return getOrgUser({
orgId: orgData.id,
});
}
return undefined;
},
queryKey: ['getOrgUser'],
enabled: !isEmpty(orgData),
});
const checkFirstTimeUser = (): boolean => {
const users = orgUsers?.payload || [];
const remainingUsers = users.filter(
(user) => user.email !== 'admin@signoz.cloud',
);
return remainingUsers.length === 1;
};
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
const shouldShowOnboarding = (): boolean => {
// Only run this effect if the org users and preferences are loaded
if (!isLoadingOrgUsers) {
const isFirstUser = checkFirstTimeUser();
// Redirect to get started if it's not the first user or if the onboarding is complete
return isFirstUser && !isOnboardingComplete;
}
return false;
};
const handleUserLoginIfTokenPresent = async (
key: keyof typeof ROUTES,
): Promise<void> => {
@ -102,6 +166,16 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
response.payload.refreshJwt,
);
const showOnboarding = shouldShowOnboarding();
if (
userResponse &&
showOnboarding &&
userResponse.payload.role === 'ADMIN'
) {
history.push(ROUTES.ONBOARDING);
}
if (
userResponse &&
route &&
@ -160,6 +234,35 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
}, [isFetchingLicensesData]);
useEffect(() => {
if (org && org.length > 0 && org[0].id !== undefined) {
setOrgData(org[0]);
}
}, [org]);
const handleRouting = (): void => {
const showOnboarding = shouldShowOnboarding();
if (showOnboarding) {
history.push(ROUTES.ONBOARDING);
} else {
history.push(ROUTES.APPLICATION);
}
};
useEffect(() => {
// Only run this effect if the org users and preferences are loaded
if (!isLoadingOrgUsers && isOnboardingComplete !== null) {
const isFirstUser = checkFirstTimeUser();
// Redirect to get started if it's not the first user or if the onboarding is complete
if (isFirstUser && !isOnboardingComplete) {
history.push(ROUTES.ONBOARDING);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoadingOrgUsers, isOnboardingComplete, orgUsers]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
(async (): Promise<void> => {
@ -183,7 +286,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
// no need to fetch the user and make user fetching false
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
history.push(ROUTES.APPLICATION);
handleRouting();
}
dispatch({
type: UPDATE_USER_IS_FETCH,
@ -195,7 +298,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
} else if (pathname === ROUTES.HOME_PAGE) {
// routing to application page over root page
if (isLoggedInState) {
history.push(ROUTES.APPLICATION);
handleRouting();
} else {
navigateToLoginIfNotLoggedIn();
}
@ -214,7 +317,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
}
if (isUserFetching) {
if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) {
return <Spinner tip="Loading..." />;
}

View File

@ -2,6 +2,7 @@ import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
@ -24,12 +25,17 @@ import AlertRuleProvider from 'providers/Alert';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { Route, Router, Switch } from 'react-router-dom';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
import {
UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_IS_FETCHING_ORG_PREFERENCES,
UPDATE_ORG_PREFERENCES,
} from 'types/actions/app';
import AppReducer, { User } from 'types/reducer/app';
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
@ -65,6 +71,30 @@ function App(): JSX.Element {
const isPremiumSupportEnabled =
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
queryFn: () => getAllOrgPreferences(),
queryKey: ['getOrgPreferences'],
enabled: isLoggedInState,
});
useEffect(() => {
if (orgPreferences && !isLoadingOrgPreferences) {
dispatch({
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
payload: {
isFetchingOrgPreferences: false,
},
});
dispatch({
type: UPDATE_ORG_PREFERENCES,
payload: {
orgPreferences: orgPreferences.payload?.data || null,
},
});
}
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
const featureResponse = useGetFeatureFlag((allFlags) => {
dispatch({
type: UPDATE_FEATURE_FLAG_RESPONSE,
@ -182,6 +212,16 @@ function App(): JSX.Element {
}, [isLoggedInState, isOnBasicPlan, user]);
useEffect(() => {
if (pathname === ROUTES.ONBOARDING) {
window.Intercom('update', {
hide_default_launcher: true,
});
} else {
window.Intercom('update', {
hide_default_launcher: false,
});
}
trackPageView(pathname);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);
@ -204,6 +244,7 @@ function App(): JSX.Element {
user,
licenseData,
isPremiumSupportEnabled,
pathname,
]);
useEffect(() => {

View File

@ -4,10 +4,15 @@ import '../OnboardingQuestionaire.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Input, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import editOrg from 'api/user/editOrg';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowRight, CheckCircle, Loader2 } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Dispatch, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_ORG_NAME } from 'types/actions/app';
import AppReducer from 'types/reducer/app';
export interface OrgData {
@ -25,10 +30,9 @@ export interface OrgDetails {
}
interface OrgQuestionsProps {
isLoading: boolean;
currentOrgData: OrgData | null;
orgDetails: OrgDetails;
setOrgDetails: (details: OrgDetails) => void;
onNext: () => void;
onNext: (details: OrgDetails) => void;
}
const observabilityTools = {
@ -49,12 +53,15 @@ const o11yFamiliarityOptions: Record<string, string> = {
};
function OrgQuestions({
isLoading,
currentOrgData,
orgDetails,
setOrgDetails,
onNext,
}: OrgQuestionsProps): JSX.Element {
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
const { notifications } = useNotifications();
const dispatch = useDispatch<Dispatch<AppActions>>();
const { t } = useTranslation(['organizationsettings', 'common']);
const [organisationName, setOrganisationName] = useState<string>(
orgDetails?.organisationName || '',
@ -77,6 +84,78 @@ function OrgQuestions({
setOrganisationName(orgDetails.organisationName);
}, [orgDetails.organisationName]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleOrgNameUpdate = async (): Promise<void> => {
/* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */
if (
!currentOrgData ||
!organisationName ||
organisationName === '' ||
orgDetails.organisationName === organisationName
) {
onNext({
organisationName,
usesObservability,
observabilityTool,
otherTool,
familiarity,
});
return;
}
try {
setIsLoading(true);
const { statusCode, error } = await editOrg({
isAnonymous: currentOrgData.isAnonymous,
name: organisationName,
orgId: currentOrgData.id,
});
if (statusCode === 200) {
dispatch({
type: UPDATE_ORG_NAME,
payload: {
orgId: currentOrgData?.id,
name: orgDetails.organisationName,
},
});
logEvent('User Onboarding: Org Name Updated', {
organisationName: orgDetails.organisationName,
});
onNext({
organisationName,
usesObservability,
observabilityTool,
otherTool,
familiarity,
});
} else {
logEvent('User Onboarding: Org Name Update Failed', {
organisationName: orgDetails.organisationName,
});
notifications.error({
message:
error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
};
const isValidUsesObservability = (): boolean => {
if (usesObservability === null) {
return false;
@ -112,23 +191,7 @@ function OrgQuestions({
]);
const handleOnNext = (): void => {
setOrgDetails({
organisationName,
usesObservability,
observabilityTool,
otherTool,
familiarity,
});
logEvent('User Onboarding: Org Questions Answered', {
organisationName,
usesObservability,
observabilityTool,
otherTool,
familiarity,
});
onNext();
handleOrgNameUpdate();
};
return (

View File

@ -4,9 +4,9 @@ import { Skeleton } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import logEvent from 'api/common/logEvent';
import updateProfileAPI from 'api/onboarding/updateProfile';
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
import getOrgPreference from 'api/preferences/getOrgPreference';
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
import editOrg from 'api/user/editOrg';
import getOrgUser from 'api/user/getOrgUser';
import { AxiosError } from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@ -16,12 +16,14 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import { Dispatch, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_ORG_NAME } from 'types/actions/app';
import {
UPDATE_IS_FETCHING_ORG_PREFERENCES,
UPDATE_ORG_PREFERENCES,
} from 'types/actions/app';
import AppReducer from 'types/reducer/app';
import {
@ -67,8 +69,8 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
function OnboardingQuestionaire(): JSX.Element {
const { notifications } = useNotifications();
const [currentStep, setCurrentStep] = useState<number>(4);
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
const [currentStep, setCurrentStep] = useState<number>(1);
const [orgDetails, setOrgDetails] = useState<OrgDetails>(INITIAL_ORG_DETAILS);
const [signozDetails, setSignozDetails] = useState<SignozDetails>(
INITIAL_SIGNOZ_DETAILS,
@ -81,9 +83,6 @@ function OnboardingQuestionaire(): JSX.Element {
InviteTeamMembersProps[] | null
>(null);
const { t } = useTranslation(['organizationsettings', 'common']);
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
queryFn: () =>
getOrgUser({
@ -93,26 +92,57 @@ function OnboardingQuestionaire(): JSX.Element {
});
const dispatch = useDispatch<Dispatch<AppActions>>();
const [orgData, setOrgData] = useState<OrgData | null>(null);
const [currentOrgData, setCurrentOrgData] = useState<OrgData | null>(null);
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
false,
);
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
const {
data: onboardingPreferenceData,
isLoading: isLoadingOnboardingPreference,
} = useQuery({
queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }),
queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'],
});
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
queryFn: () => getAllOrgPreferences(),
queryKey: ['getOrgPreferences'],
enabled: isOnboardingComplete,
});
useEffect(() => {
if (!isLoadingOrgPreferences && !isEmpty(orgPreferences?.payload?.data)) {
const preferenceId = orgPreferences?.payload?.data?.preference_id;
const preferenceValue = orgPreferences?.payload?.data?.preference_value;
if (orgPreferences && !isLoadingOrgPreferences) {
dispatch({
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
payload: {
isFetchingOrgPreferences: false,
},
});
dispatch({
type: UPDATE_ORG_PREFERENCES,
payload: {
orgPreferences: orgPreferences.payload?.data || null,
},
});
}
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
useEffect(() => {
if (
!isLoadingOnboardingPreference &&
!isEmpty(onboardingPreferenceData?.payload?.data)
) {
const preferenceId = onboardingPreferenceData?.payload?.data?.preference_id;
const preferenceValue =
onboardingPreferenceData?.payload?.data?.preference_value;
if (preferenceId === 'ORG_ONBOARDING') {
setIsOnboardingComplete(preferenceValue as boolean);
}
}
}, [orgPreferences, isLoadingOrgPreferences]);
}, [onboardingPreferenceData, isLoadingOnboardingPreference]);
const checkFirstTimeUser = (): boolean => {
const users = orgUsers?.payload || [];
@ -126,7 +156,7 @@ function OnboardingQuestionaire(): JSX.Element {
useEffect(() => {
// Only run this effect if the org users and preferences are loaded
if (!isLoadingOrgUsers && !isLoadingOrgPreferences) {
if (!isLoadingOrgUsers && !isLoadingOnboardingPreference) {
const isFirstUser = checkFirstTimeUser();
// Redirect to get started if it's not the first user or if the onboarding is complete
@ -144,14 +174,14 @@ function OnboardingQuestionaire(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isLoadingOrgUsers,
isLoadingOrgPreferences,
isLoadingOnboardingPreference,
isOnboardingComplete,
orgUsers,
]);
useEffect(() => {
if (org) {
setOrgData(org[0]);
setCurrentOrgData(org[0]);
setOrgDetails({
...orgDetails,
@ -166,70 +196,6 @@ function OnboardingQuestionaire(): JSX.Element {
optimiseSignozDetails.hostsPerDay === 0 &&
optimiseSignozDetails.services === 0;
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleOrgNameUpdate = async (): Promise<void> => {
/* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */
if (
!orgData ||
!orgDetails.organisationName ||
orgDetails.organisationName === '' ||
orgData.name === orgDetails.organisationName
) {
setCurrentStep(2);
return;
}
try {
setIsLoading(true);
const { statusCode, error } = await editOrg({
isAnonymous: orgData?.isAnonymous,
name: orgDetails.organisationName,
orgId: orgData?.id,
});
if (statusCode === 200) {
dispatch({
type: UPDATE_ORG_NAME,
payload: {
orgId: orgData?.id,
name: orgDetails.organisationName,
},
});
logEvent('User Onboarding: Org Name Updated', {
organisationName: orgDetails.organisationName,
});
setCurrentStep(2);
} else {
logEvent('User Onboarding: Org Name Update Failed', {
organisationName: orgDetails.organisationName,
});
notifications.error({
message:
error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
};
const handleOrgDetailsUpdate = (): void => {
handleOrgNameUpdate();
};
const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation(
updateProfileAPI,
{
@ -289,20 +255,22 @@ function OnboardingQuestionaire(): JSX.Element {
</div>
<div className="onboarding-questionaire-content">
{(isLoadingOrgPreferences || isLoadingOrgUsers) && (
{(isLoadingOnboardingPreference || isLoadingOrgUsers) && (
<div className="onboarding-questionaire-loading-container">
<Skeleton />
</div>
)}
{!isLoadingOrgPreferences && !isLoadingOrgUsers && (
{!isLoadingOnboardingPreference && !isLoadingOrgUsers && (
<>
{currentStep === 1 && (
<OrgQuestions
isLoading={isLoading}
currentOrgData={currentOrgData}
orgDetails={orgDetails}
setOrgDetails={setOrgDetails}
onNext={handleOrgDetailsUpdate}
onNext={(orgDetails: OrgDetails): void => {
setOrgDetails(orgDetails);
setCurrentStep(2);
}}
/>
)}

View File

@ -8,10 +8,12 @@ import {
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_IS_FETCHING_ORG_PREFERENCES,
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
UPDATE_ORG,
UPDATE_ORG_NAME,
UPDATE_ORG_PREFERENCES,
UPDATE_USER,
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
UPDATE_USER_FLAG,
@ -59,6 +61,8 @@ const InitialValue: InitialValueTypes = {
userFlags: {},
ee: 'Y',
setupCompleted: true,
orgPreferences: null,
isFetchingOrgPreferences: true,
};
const appReducer = (
@ -73,6 +77,17 @@ const appReducer = (
};
}
case UPDATE_ORG_PREFERENCES: {
return { ...state, orgPreferences: action.payload.orgPreferences };
}
case UPDATE_IS_FETCHING_ORG_PREFERENCES: {
return {
...state,
isFetchingOrgPreferences: action.payload.isFetchingOrgPreferences,
};
}
case UPDATE_FEATURE_FLAG_RESPONSE: {
return {
...state,

View File

@ -276,26 +276,39 @@ notifications - 2050
}
@font-face {
font-family: 'Inter';
src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
font-weight: 300 700;
font-style: normal;
font-family: 'Inter';
src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
font-weight: 300 700;
font-style: normal;
}
@font-face {
font-family: 'Work Sans';
src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-family: 'Work Sans';
src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Space Mono';
src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-family: 'Space Mono';
src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Fira Code';
src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
font-weight: 300 700;
font-style: normal;
font-family: 'Fira Code';
src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
font-weight: 300 700;
font-style: normal;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}

View File

@ -25,7 +25,10 @@ export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
export const UPDATE_ORG = 'UPDATE_ORG';
export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
export const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG';
export const UPDATE_ORG_PREFERENCES = 'UPDATE_ORG_PREFERENCES';
export const UPDATE_FEATURE_FLAG_RESPONSE = 'UPDATE_FEATURE_FLAG_RESPONSE';
export const UPDATE_IS_FETCHING_ORG_PREFERENCES =
'UPDATE_IS_FETCHING_ORG_PREFERENCES';
export interface LoggedInUser {
type: typeof LOGGED_IN;
@ -130,6 +133,20 @@ export interface UpdateFeatureFlag {
};
}
export interface UpdateOrgPreferences {
type: typeof UPDATE_ORG_PREFERENCES;
payload: {
orgPreferences: AppReducer['orgPreferences'];
};
}
export interface UpdateIsFetchingOrgPreferences {
type: typeof UPDATE_IS_FETCHING_ORG_PREFERENCES;
payload: {
isFetchingOrgPreferences: AppReducer['isFetchingOrgPreferences'];
};
}
export type AppAction =
| LoggedInUser
| UpdateAppVersion
@ -143,4 +160,6 @@ export type AppAction =
| UpdateOrg
| UpdateConfigs
| UpdateUserFlag
| UpdateFeatureFlag;
| UpdateFeatureFlag
| UpdateOrgPreferences
| UpdateIsFetchingOrgPreferences;

View File

@ -1,3 +1,5 @@
import { OrgPreference } from 'types/reducer/app';
export interface GetOrgPreferenceResponseProps {
status: string;
data: Record<string, unknown>;
@ -10,7 +12,7 @@ export interface GetUserPreferenceResponseProps {
export interface GetAllOrgPreferencesResponseProps {
status: string;
data: Record<string, unknown>;
data: OrgPreference[];
}
export interface GetAllUserPreferencesResponseProps {

View File

@ -15,6 +15,18 @@ export interface User {
profilePictureURL: UserPayload['profilePictureURL'];
}
export interface OrgPreference {
key: string;
name: string;
description: string;
valueType: string;
defaultValue: boolean;
allowedValues: any[];
isDiscreteValues: boolean;
allowedScopes: string[];
value: boolean;
}
export default interface AppReducer {
isLoggedIn: boolean;
currentVersion: string;
@ -30,6 +42,8 @@ export default interface AppReducer {
userFlags: null | UserFlags;
ee: 'Y' | 'N';
setupCompleted: boolean;
orgPreferences: OrgPreference[] | null;
isFetchingOrgPreferences: boolean;
featureResponse: {
data: FeatureFlagPayload[] | null;
refetch: QueryObserverBaseResult['refetch'];

View File

@ -86,7 +86,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'],
ONBOARDING: ['ADMIN', 'EDITOR', 'VIEWER'],
ONBOARDING: ['ADMIN'],
GET_STARTED_APPLICATION_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'],
GET_STARTED_INFRASTRUCTURE_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'],
GET_STARTED_LOGS_MANAGEMENT: ['ADMIN', 'EDITOR', 'VIEWER'],