feat: revamp onboarding (#9068)

* feat: revamp onboarding, send list to mixpanel, join logic to convert to single string

* chore: props changes

* fix: allow user to proceed even if api fails

* chore: remove console.log

* chore: remove commented code

* chore: minor colour tweaks

* chore: resolve comments
This commit is contained in:
manika-signoz 2025-09-23 20:47:39 +05:30 committed by GitHub
parent b2dc2790d8
commit 73ff89a80a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 177 additions and 161 deletions

View File

@ -2,14 +2,14 @@
import '../OnboardingQuestionaire.styles.scss'; import '../OnboardingQuestionaire.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Input, Typography } from 'antd'; import { Button, Checkbox, Input, Typography } from 'antd';
import TextArea from 'antd/lib/input/TextArea'; import TextArea from 'antd/lib/input/TextArea';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react'; import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export interface SignozDetails { export interface SignozDetails {
interestInSignoz: string | null; interestInSignoz: string[] | null;
otherInterestInSignoz: string | null; otherInterestInSignoz: string | null;
discoverSignoz: string | null; discoverSignoz: string | null;
} }
@ -22,9 +22,12 @@ interface AboutSigNozQuestionsProps {
} }
const interestedInOptions: Record<string, string> = { const interestedInOptions: Record<string, string> = {
savingCosts: 'Saving costs', loweringCosts: 'Lowering observability costs',
otelNativeStack: 'Interested in Otel-native stack', otelNativeStack: 'Interested in OTel-native stack',
allInOne: 'All in one (Logs, Metrics & Traces)', deploymentFlexibility: 'Deployment flexibility (Cloud/Self-Host) in future',
singleTool:
'Single Tool (logs, metrics & traces) to reduce operational overhead',
correlateSignals: 'Correlate signals for faster troubleshooting',
}; };
export function AboutSigNozQuestions({ export function AboutSigNozQuestions({
@ -33,8 +36,8 @@ export function AboutSigNozQuestions({
onNext, onNext,
onBack, onBack,
}: AboutSigNozQuestionsProps): JSX.Element { }: AboutSigNozQuestionsProps): JSX.Element {
const [interestInSignoz, setInterestInSignoz] = useState<string | null>( const [interestInSignoz, setInterestInSignoz] = useState<string[]>(
signozDetails?.interestInSignoz || null, signozDetails?.interestInSignoz || [],
); );
const [otherInterestInSignoz, setOtherInterestInSignoz] = useState<string>( const [otherInterestInSignoz, setOtherInterestInSignoz] = useState<string>(
signozDetails?.otherInterestInSignoz || '', signozDetails?.otherInterestInSignoz || '',
@ -47,8 +50,8 @@ export function AboutSigNozQuestions({
useEffect((): void => { useEffect((): void => {
if ( if (
discoverSignoz !== '' && discoverSignoz !== '' &&
interestInSignoz !== null && interestInSignoz.length > 0 &&
(interestInSignoz !== 'Others' || otherInterestInSignoz !== '') (!interestInSignoz.includes('Others') || otherInterestInSignoz !== '')
) { ) {
setIsNextDisabled(false); setIsNextDisabled(false);
} else { } else {
@ -56,6 +59,14 @@ export function AboutSigNozQuestions({
} }
}, [interestInSignoz, otherInterestInSignoz, discoverSignoz]); }, [interestInSignoz, otherInterestInSignoz, discoverSignoz]);
const handleInterestChange = (option: string, checked: boolean): void => {
if (checked) {
setInterestInSignoz((prev) => [...prev, option]);
} else {
setInterestInSignoz((prev) => prev.filter((item) => item !== option));
}
};
const handleOnNext = (): void => { const handleOnNext = (): void => {
setSignozDetails({ setSignozDetails({
discoverSignoz, discoverSignoz,
@ -108,50 +119,45 @@ export function AboutSigNozQuestions({
<div className="form-group"> <div className="form-group">
<div className="question">What got you interested in SigNoz?</div> <div className="question">What got you interested in SigNoz?</div>
<div className="two-column-grid"> <div className="checkbox-grid">
{Object.keys(interestedInOptions).map((option: string) => ( {Object.keys(interestedInOptions).map((option: string) => (
<Button <div key={option} className="checkbox-item">
key={option} <Checkbox
type="primary" checked={interestInSignoz.includes(option)}
className={`onboarding-questionaire-button ${ onChange={(e): void => handleInterestChange(option, e.target.checked)}
interestInSignoz === option ? 'active' : '' >
}`} {interestedInOptions[option]}
onClick={(): void => setInterestInSignoz(option)} </Checkbox>
> </div>
{interestedInOptions[option]}
{interestInSignoz === option && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
))} ))}
{interestInSignoz === 'Others' ? ( <div className="checkbox-item">
<Input <Checkbox
type="text" checked={interestInSignoz.includes('Others')}
className="onboarding-questionaire-other-input" onChange={(e): void =>
placeholder="Please specify your interest" handleInterestChange('Others', e.target.checked)
value={otherInterestInSignoz}
autoFocus
addonAfter={
otherInterestInSignoz !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
} }
onChange={(e): void => setOtherInterestInSignoz(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
interestInSignoz === 'Others' ? 'active' : ''
}`}
onClick={(): void => setInterestInSignoz('Others')}
> >
Others Others
</Button> </Checkbox>
)} {interestInSignoz.includes('Others') && (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify your interest"
value={otherInterestInSignoz}
autoFocus
addonAfter={
otherInterestInSignoz !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
}
onChange={(e): void => setOtherInterestInSignoz(e.target.value)}
/>
)}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -94,6 +94,7 @@
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
padding: 12px; padding: 12px;
font-weight: 400;
&::placeholder { &::placeholder {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
@ -290,6 +291,37 @@
gap: 10px; gap: 10px;
} }
.checkbox-grid {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}
.checkbox-item {
display: flex;
flex-direction: column;
gap: 8px;
.ant-checkbox-wrapper {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
.ant-checkbox {
.ant-checkbox-inner {
border-color: var(--bg-slate-100);
background-color: var(--bg-ink-200);
}
&.ant-checkbox-checked .ant-checkbox-inner {
background-color: var(--bg-robin-500);
border-color: var(--bg-robin-500);
}
}
}
}
.onboarding-questionaire-button, .onboarding-questionaire-button,
.add-another-member-button, .add-another-member-button,
.remove-team-member-button { .remove-team-member-button {
@ -466,6 +498,7 @@
border: 1px solid var(--bg-vanilla-300); border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100); background: var(--bg-vanilla-100);
color: var(--text-ink-300); color: var(--text-ink-300);
font-weight: 400;
&::placeholder { &::placeholder {
color: var(--bg-slate-400); color: var(--bg-slate-400);
@ -527,6 +560,24 @@
color: var(--bg-slate-300); color: var(--bg-slate-300);
} }
.checkbox-item {
.ant-checkbox-wrapper {
color: var(--bg-ink-300);
.ant-checkbox {
.ant-checkbox-inner {
border-color: var(--bg-vanilla-300);
background-color: var(--bg-vanilla-100);
}
&.ant-checkbox-checked .ant-checkbox-inner {
background-color: var(--bg-robin-500);
border-color: var(--bg-robin-500);
}
}
}
}
input[type='text'] { input[type='text'] {
border: 1px solid var(--bg-vanilla-300); border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100); background: var(--bg-vanilla-100);

View File

@ -38,6 +38,7 @@ const observabilityTools = {
AzureAppMonitor: 'Azure App Monitor', AzureAppMonitor: 'Azure App Monitor',
GCPNativeO11yTools: 'GCP-native o11y tools', GCPNativeO11yTools: 'GCP-native o11y tools',
Honeycomb: 'Honeycomb', Honeycomb: 'Honeycomb',
None: 'None/Starting fresh',
}; };
function OrgQuestions({ function OrgQuestions({
@ -53,9 +54,6 @@ function OrgQuestions({
const [organisationName, setOrganisationName] = useState<string>( const [organisationName, setOrganisationName] = useState<string>(
orgDetails?.organisationName || '', orgDetails?.organisationName || '',
); );
const [usesObservability, setUsesObservability] = useState<boolean | null>(
orgDetails?.usesObservability || null,
);
const [observabilityTool, setObservabilityTool] = useState<string | null>( const [observabilityTool, setObservabilityTool] = useState<string | null>(
orgDetails?.observabilityTool || null, orgDetails?.observabilityTool || null,
); );
@ -83,7 +81,7 @@ function OrgQuestions({
orgDetails.organisationName === organisationName orgDetails.organisationName === organisationName
) { ) {
logEvent('Org Onboarding: Answered', { logEvent('Org Onboarding: Answered', {
usesObservability, usesObservability: !observabilityTool?.includes('None'),
observabilityTool, observabilityTool,
otherTool, otherTool,
usesOtel, usesOtel,
@ -91,7 +89,7 @@ function OrgQuestions({
onNext({ onNext({
organisationName, organisationName,
usesObservability, usesObservability: !observabilityTool?.includes('None'),
observabilityTool, observabilityTool,
otherTool, otherTool,
usesOtel, usesOtel,
@ -114,7 +112,7 @@ function OrgQuestions({
}); });
logEvent('Org Onboarding: Answered', { logEvent('Org Onboarding: Answered', {
usesObservability, usesObservability: !observabilityTool?.includes('None'),
observabilityTool, observabilityTool,
otherTool, otherTool,
usesOtel, usesOtel,
@ -122,7 +120,7 @@ function OrgQuestions({
onNext({ onNext({
organisationName, organisationName,
usesObservability, usesObservability: !observabilityTool?.includes('None'),
observabilityTool, observabilityTool,
otherTool, otherTool,
usesOtel, usesOtel,
@ -152,16 +150,16 @@ function OrgQuestions({
}; };
const isValidUsesObservability = (): boolean => { const isValidUsesObservability = (): boolean => {
if (usesObservability === null) { if (!observabilityTool || observabilityTool === '') {
return false;
}
if (usesObservability && (!observabilityTool || observabilityTool === '')) {
return false; return false;
} }
// eslint-disable-next-line sonarjs/prefer-single-boolean-return // eslint-disable-next-line sonarjs/prefer-single-boolean-return
if (usesObservability && observabilityTool === 'Others' && otherTool === '') { if (
!observabilityTool?.includes('None') &&
observabilityTool === 'Others' &&
otherTool === ''
) {
return false; return false;
} }
@ -177,13 +175,7 @@ function OrgQuestions({
setIsNextDisabled(true); setIsNextDisabled(true);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [organisationName, usesOtel, observabilityTool, otherTool]);
organisationName,
usesObservability,
usesOtel,
observabilityTool,
otherTool,
]);
const handleOnNext = (): void => { const handleOnNext = (): void => {
handleOrgNameUpdate(); handleOrgNameUpdate();
@ -217,99 +209,57 @@ function OrgQuestions({
</div> </div>
<div className="form-group"> <div className="form-group">
<label className="question" htmlFor="usesObservability"> <label className="question" htmlFor="observabilityTool">
Do you currently use any observability/monitoring tool? Which observability tool do you currently use?
</label> </label>
<div className="two-column-grid"> <div className="two-column-grid">
<Button {Object.keys(observabilityTools).map((tool) => (
type="primary" <Button
name="usesObservability" key={tool}
className={`onboarding-questionaire-button ${ type="primary"
usesObservability === true ? 'active' : '' className={`onboarding-questionaire-button ${
}`} observabilityTool === tool ? 'active' : ''
onClick={(): void => { }`}
setUsesObservability(true); onClick={(): void => setObservabilityTool(tool)}
}} >
> {observabilityTools[tool as keyof typeof observabilityTools]}
Yes{' '}
{usesObservability === true && ( {observabilityTool === tool && (
<CheckCircle size={12} color={Color.BG_FOREST_500} /> <CheckCircle size={12} color={Color.BG_FOREST_500} />
)} )}
</Button> </Button>
<Button ))}
type="primary"
className={`onboarding-questionaire-button ${ {observabilityTool === 'Others' ? (
usesObservability === false ? 'active' : '' <Input
}`} type="text"
onClick={(): void => { className="onboarding-questionaire-other-input"
setUsesObservability(false); placeholder="Please specify the tool"
setObservabilityTool(null); value={otherTool || ''}
setOtherTool(''); autoFocus
}} addonAfter={
> otherTool && otherTool !== '' ? (
No{' '} <CheckCircle size={12} color={Color.BG_FOREST_500} />
{usesObservability === false && ( ) : (
<CheckCircle size={12} color={Color.BG_FOREST_500} /> ''
)} )
</Button> }
onChange={(e): void => setOtherTool(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === 'Others' ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool('Others')}
>
Others
</Button>
)}
</div> </div>
</div> </div>
{usesObservability && (
<div className="form-group">
<label className="question" htmlFor="observabilityTool">
Which observability tool do you currently use?
</label>
<div className="two-column-grid">
{Object.keys(observabilityTools).map((tool) => (
<Button
key={tool}
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === tool ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool(tool)}
>
{observabilityTools[tool as keyof typeof observabilityTools]}
{observabilityTool === tool && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
))}
{observabilityTool === 'Others' ? (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify the tool"
value={otherTool || ''}
autoFocus
addonAfter={
otherTool && otherTool !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
}
onChange={(e): void => setOtherTool(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === 'Others' ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool('Others')}
>
Others
</Button>
)}
</div>
</div>
)}
<div className="form-group"> <div className="form-group">
<div className="question">Do you already use OpenTelemetry?</div> <div className="question">Do you already use OpenTelemetry?</div>
<div className="two-column-grid"> <div className="two-column-grid">

View File

@ -46,7 +46,7 @@ const INITIAL_ORG_DETAILS: OrgDetails = {
}; };
const INITIAL_SIGNOZ_DETAILS: SignozDetails = { const INITIAL_SIGNOZ_DETAILS: SignozDetails = {
interestInSignoz: '', interestInSignoz: [],
otherInterestInSignoz: '', otherInterestInSignoz: '',
discoverSignoz: '', discoverSignoz: '',
}; };
@ -145,6 +145,9 @@ function OnboardingQuestionaire(): JSX.Element {
}, },
onError: (error) => { onError: (error) => {
showErrorNotification(notifications, error as AxiosError); showErrorNotification(notifications, error as AxiosError);
// Allow user to proceed even if API fails
setCurrentStep(4);
}, },
}, },
); );
@ -174,10 +177,16 @@ function OnboardingQuestionaire(): JSX.Element {
? (orgDetails?.otherTool as string) ? (orgDetails?.otherTool as string)
: (orgDetails?.observabilityTool as string), : (orgDetails?.observabilityTool as string),
where_did_you_discover_signoz: signozDetails?.discoverSignoz as string, where_did_you_discover_signoz: signozDetails?.discoverSignoz as string,
reasons_for_interest_in_signoz: reasons_for_interest_in_signoz: signozDetails?.interestInSignoz?.includes(
signozDetails?.interestInSignoz === 'Others' 'Others',
? (signozDetails?.otherInterestInSignoz as string) )
: (signozDetails?.interestInSignoz as string), ? ([
...(signozDetails?.interestInSignoz?.filter(
(item) => item !== 'Others',
) || []),
signozDetails?.otherInterestInSignoz,
] as string[])
: (signozDetails?.interestInSignoz as string[]),
logs_scale_per_day_in_gb: optimiseSignozDetails?.logsPerDay as number, logs_scale_per_day_in_gb: optimiseSignozDetails?.logsPerDay as number,
number_of_hosts: optimiseSignozDetails?.hostsPerDay as number, number_of_hosts: optimiseSignozDetails?.hostsPerDay as number,
number_of_services: optimiseSignozDetails?.services as number, number_of_services: optimiseSignozDetails?.services as number,

View File

@ -1,5 +1,5 @@
export interface UpdateProfileProps { export interface UpdateProfileProps {
reasons_for_interest_in_signoz: string; reasons_for_interest_in_signoz: string[];
uses_otel: boolean; uses_otel: boolean;
has_existing_observability_tool: boolean; has_existing_observability_tool: boolean;
existing_observability_tool: string; existing_observability_tool: string;