feat: handle errors for profiles and invite users api

This commit is contained in:
Yunus M 2024-10-23 20:08:23 +05:30
parent 6c350f30aa
commit 4dc5615d2f
9 changed files with 93 additions and 40 deletions

View File

@ -4,6 +4,7 @@
"SERVICE_METRICS": "SigNoz | Service Metrics", "SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map", "SERVICE_MAP": "SigNoz | Service Map",
"GET_STARTED": "SigNoz | Get Started", "GET_STARTED": "SigNoz | Get Started",
"GET_STARTED_V2": "SigNoz | Get Started",
"GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM", "GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM",
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs", "GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure", "GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",

View File

@ -1,26 +1,20 @@
import { GatewayApiV2Instance } from 'api'; import { GatewayApiV2Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api'; import { ErrorResponse, SuccessResponse } from 'types/api';
import { UpdateProfileProps } from 'types/api/onboarding/types'; import { UpdateProfileProps } from 'types/api/onboarding/types';
const updateProfile = async ( const updateProfile = async (
props: UpdateProfileProps, props: UpdateProfileProps,
): Promise<SuccessResponse<UpdateProfileProps> | ErrorResponse> => { ): Promise<SuccessResponse<UpdateProfileProps> | ErrorResponse> => {
try { const response = await GatewayApiV2Instance.put('/profiles/me', {
const response = await GatewayApiV2Instance.put('/profiles/me', { ...props,
...props, });
});
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: response.data.status, message: response.data.status,
payload: response.data.data, payload: response.data.data,
}; };
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
}; };
export default updateProfile; export default updateProfile;

View File

@ -1,25 +1,18 @@
import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import axios from 'api';
import axios, { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api'; import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers'; import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers';
const inviteUsers = async ( const inviteUsers = async (
users: UsersProps, users: UsersProps,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => { ): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try { const response = await axios.post(`/invite/bulk`, users);
const response = await axios.post(`/invite/bulk`, {
...users,
});
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: response.data.status, message: response.data.status,
payload: response.data, payload: response.data,
}; };
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
}; };
export default inviteUsers; export default inviteUsers;

View File

@ -41,6 +41,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
text-transform: capitalize;
} }
} }
} }

View File

@ -2,6 +2,8 @@ import './InviteTeamMembers.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Input, Select, Typography } from 'antd'; import { Button, Input, Select, Typography } from 'antd';
import inviteUsers from 'api/user/inviteUsers';
import { AxiosError } from 'axios';
import { cloneDeep, debounce, isEmpty } from 'lodash-es'; import { cloneDeep, debounce, isEmpty } from 'lodash-es';
import { import {
ArrowLeft, ArrowLeft,
@ -11,6 +13,8 @@ import {
TriangleAlert, TriangleAlert,
} from 'lucide-react'; } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { ErrorResponse } from 'types/api';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
interface TeamMember { interface TeamMember {
@ -37,18 +41,17 @@ function InviteTeamMembers({
const [teamMembersToInvite, setTeamMembersToInvite] = useState< const [teamMembersToInvite, setTeamMembersToInvite] = useState<
TeamMember[] | null TeamMember[] | null
>(teamMembers); >(teamMembers);
const [emailValidity, setEmailValidity] = useState<Record<string, boolean>>( const [emailValidity, setEmailValidity] = useState<Record<string, boolean>>(
{}, {},
); );
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false); const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const defaultTeamMember: TeamMember = { const defaultTeamMember: TeamMember = {
email: '', email: '',
role: 'EDITOR', role: 'EDITOR',
name: '', name: '',
frontendBaseUrl: '', frontendBaseUrl: window.location.origin,
id: '', id: '',
}; };
@ -65,7 +68,10 @@ function InviteTeamMembers({
}, [teamMembers]); }, [teamMembers]);
const handleAddTeamMember = (): void => { const handleAddTeamMember = (): void => {
const newTeamMember = { ...defaultTeamMember, id: uuid() }; const newTeamMember = {
...defaultTeamMember,
id: uuid(),
};
setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]); setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]);
}; };
@ -89,10 +95,34 @@ function InviteTeamMembers({
return isValid; return isValid;
}; };
const handleError = (error: AxiosError): void => {
const errorMessage = error.response?.data as ErrorResponse;
setError(errorMessage.error);
};
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
inviteUsers,
{
onSuccess: (): void => {
onNext();
},
onError: (error): void => {
handleError(error as AxiosError);
},
},
);
const handleNext = (): void => { const handleNext = (): void => {
if (validateAllUsers()) { if (validateAllUsers()) {
setTeamMembers(teamMembersToInvite || []); setTeamMembers(teamMembersToInvite || []);
onNext();
setError(null);
setHasInvalidEmails(false);
sendInvites({
users: teamMembersToInvite || [],
});
} }
}; };
@ -129,6 +159,10 @@ function InviteTeamMembers({
} }
}; };
const handleDoLater = (): void => {
onNext();
};
return ( return (
<div className="questions-container"> <div className="questions-container">
<Typography.Title level={3} className="title"> <Typography.Title level={3} className="title">
@ -209,20 +243,33 @@ function InviteTeamMembers({
</div> </div>
)} )}
{error && (
<div className="error-message-container">
<Typography.Text className="error-message" type="danger">
<TriangleAlert size={14} /> {error}
</Typography.Text>
</div>
)}
<div className="next-prev-container"> <div className="next-prev-container">
<Button type="default" className="next-button" onClick={onBack}> <Button type="default" className="next-button" onClick={onBack}>
<ArrowLeft size={14} /> <ArrowLeft size={14} />
Back Back
</Button> </Button>
<Button type="primary" className="next-button" onClick={handleNext}> <Button
type="primary"
className="next-button"
onClick={handleNext}
loading={isSendingInvites}
>
Send Invites Send Invites
<ArrowRight size={14} /> <ArrowRight size={14} />
</Button> </Button>
</div> </div>
<div className="do-later-container"> <div className="do-later-container">
<Button type="link" onClick={onNext}> <Button type="link" onClick={handleDoLater}>
I&apos;ll do this later I&apos;ll do this later
</Button> </Button>
</div> </div>

View File

@ -14,6 +14,7 @@ interface OptimiseSignozNeedsProps {
setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void; setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void;
onNext: () => void; onNext: () => void;
onBack: () => void; onBack: () => void;
onWillDoLater: () => void;
isUpdatingProfile: boolean; isUpdatingProfile: boolean;
} }
@ -48,6 +49,7 @@ function OptimiseSignozNeeds({
setOptimiseSignozDetails, setOptimiseSignozDetails,
onNext, onNext,
onBack, onBack,
onWillDoLater,
}: OptimiseSignozNeedsProps): JSX.Element { }: OptimiseSignozNeedsProps): JSX.Element {
const [logsPerDay, setLogsPerDay] = useState<number>( const [logsPerDay, setLogsPerDay] = useState<number>(
optimiseSignozDetails?.logsPerDay || 25, optimiseSignozDetails?.logsPerDay || 25,
@ -95,6 +97,8 @@ function OptimiseSignozNeeds({
services: 0, services: 0,
}); });
onWillDoLater();
logEvent('Onboarding: Optimise SigNoz Needs: Will do later', { logEvent('Onboarding: Optimise SigNoz Needs: Will do later', {
logsPerDay: 0, logsPerDay: 0,
hostsPerDay: 0, hostsPerDay: 0,

View File

@ -5,6 +5,7 @@ import updateProfileAPI from 'api/onboarding/updateProfile';
import editOrg from 'api/user/editOrg'; import editOrg from 'api/user/editOrg';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import ROUTES from 'constants/routes';
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history'; import history from 'lib/history';
@ -187,7 +188,7 @@ function OnboardingQuestionaire(): JSX.Element {
}; };
const handleOnboardingComplete = (): void => { const handleOnboardingComplete = (): void => {
history.push('/'); history.push(ROUTES.APPLICATION);
}; };
return ( return (
@ -222,6 +223,7 @@ function OnboardingQuestionaire(): JSX.Element {
setOptimiseSignozDetails={setOptimiseSignozDetails} setOptimiseSignozDetails={setOptimiseSignozDetails}
onBack={(): void => setCurrentStep(2)} onBack={(): void => setCurrentStep(2)}
onNext={handleUpdateProfile} onNext={handleUpdateProfile}
onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet
/> />
)} )}

View File

@ -1,10 +1,9 @@
import { User } from 'types/reducer/app'; import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
export interface UserProps { export interface UserProps {
name: User['name']; name: User['name'];
email: User['email']; email: User['email'];
role: ROLES; role: string;
frontendBaseUrl: string; frontendBaseUrl: string;
} }

View File

@ -0,0 +1,12 @@
import { NotificationInstance } from 'antd/es/notification/interface';
import axios from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api';
export const showErrorNotification = (
notifications: NotificationInstance,
err: Error,
): void => {
notifications.error({
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
});
};