Skip to content
Draft
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
69 changes: 67 additions & 2 deletions static/app/components/onboarding/onboardingContext.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {RepositoryFixture} from 'sentry-fixture/repository';

import {render, screen} from 'sentry-test/reactTestingLibrary';
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';

import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types';
import {
Expand All @@ -18,14 +18,22 @@ const platform = {
};

function StateConsumer() {
const {selectedRepository, selectedPlatform, selectedFeatures} = useOnboardingContext();
const {
selectedRepository,
selectedPlatform,
selectedFeatures,
setSelectedPlatform,
resetOnboarding,
} = useOnboardingContext();
return (
<div>
<div>{selectedRepository ? `repo:${selectedRepository.id}` : 'no-repo'}</div>
<div>{selectedPlatform ? `platform:${selectedPlatform.key}` : 'no-platform'}</div>
<div>
{selectedFeatures ? `features:${selectedFeatures.length}` : 'no-features'}
</div>
<button onClick={() => setSelectedPlatform(undefined)}>Clear platform</button>
<button onClick={() => resetOnboarding()}>Reset onboarding</button>
</div>
);
}
Expand Down Expand Up @@ -74,3 +82,60 @@ describe('OnboardingContextProvider', () => {
expect(screen.getByText('features:1')).toBeInTheDocument();
});
});

describe('OnboardingContextProvider session semantics', () => {
afterEach(() => {
window.sessionStorage.clear();
});

it('keeps the rest of the session when clearing the selected platform', async () => {
render(
<OnboardingContextProvider
initialValue={{
selectedRepository: RepositoryFixture({id: '42'}),
selectedPlatform: platform,
}}
>
<StateConsumer />
</OnboardingContextProvider>
);

await userEvent.click(screen.getByRole('button', {name: 'Clear platform'}));

// Clearing one field must stay local to that field. This previously routed
// through removeOnboarding and wiped the whole session, taking the
// connected repository with it.
expect(screen.getByText('no-platform')).toBeInTheDocument();
expect(screen.getByText('repo:42')).toBeInTheDocument();
expect(JSON.parse(sessionStorage.getItem('onboarding') ?? '{}')).toMatchObject({
selectedRepository: {id: '42'},
});
});

it('clears persisted session state on resetOnboarding', async () => {
// Seeded through sessionStorage rather than the initialValue prop:
// useSessionStorage's removeItem resets in-memory state back to
// initialValue, so a seeded prop would be restored rather than cleared.
// The real provider passes no initialValue, so removal is total there.
sessionStorage.setItem(
'onboarding',
JSON.stringify({
selectedRepository: RepositoryFixture({id: '42'}),
selectedPlatform: platform,
})
);

render(
<OnboardingContextProvider>
<StateConsumer />
</OnboardingContextProvider>
);
expect(screen.getByText('platform:javascript-nextjs')).toBeInTheDocument();

await userEvent.click(screen.getByRole('button', {name: 'Reset onboarding'}));

expect(screen.getByText('no-platform')).toBeInTheDocument();
expect(screen.getByText('no-repo')).toBeInTheDocument();
expect(sessionStorage.getItem('onboarding')).toBeNull();
});
});
13 changes: 8 additions & 5 deletions static/app/components/onboarding/onboardingContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {useSessionStorage} from 'sentry/utils/useSessionStorage';

type OnboardingContextProps = {
clearDerivedState: () => void;
resetOnboarding: () => void;
setCreatedProjectSlug: (slug?: string) => void;
setSelectedFeatures: (features?: ProductSolution[]) => void;
setSelectedIntegration: (integration?: Integration) => void;
Expand Down Expand Up @@ -42,6 +43,7 @@ const OnboardingContext = createContext<OnboardingContextProps>({
createdProjectSlug: undefined,
setCreatedProjectSlug: () => {},
clearDerivedState: () => {},
resetOnboarding: () => {},
});

type ProviderProps = {
Expand Down Expand Up @@ -85,11 +87,7 @@ export function OnboardingContextProvider({children, initialValue}: ProviderProp
() => ({
selectedPlatform: onboarding?.selectedPlatform,
setSelectedPlatform: (selectedPlatform?: OnboardingSelectedSDK) => {
if (selectedPlatform === undefined) {
removeOnboarding();
} else {
setOnboarding(prev => ({...prev, selectedPlatform}));
}
setOnboarding(prev => ({...prev, selectedPlatform}));
},
selectedIntegration: onboarding?.selectedIntegration,
setSelectedIntegration: (selectedIntegration?: Integration) => {
Expand Down Expand Up @@ -118,6 +116,11 @@ export function OnboardingContextProvider({children, initialValue}: ProviderProp
createdProjectSlug: undefined,
}));
},
// Full-flow exits should clear every staged choice explicitly. Do not
// reach for a selected-platform reset to do this: clearing one field must
// stay local to that field so organization-scoped state added later
// survives local repository and platform changes.
resetOnboarding: removeOnboarding,
}),
[onboarding, setOnboarding, removeOnboarding]
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {LinkButton} from '@sentry/scraps/button';

import {useOnboardingContext} from 'sentry/components/onboarding/onboardingContext';
import {useOnboardingSidebar} from 'sentry/components/onboarding/useOnboardingSidebar';
import {t} from 'sentry/locale';
import {trackAnalytics} from 'sentry/utils/analytics';
Expand Down Expand Up @@ -39,6 +40,7 @@ interface OnboardingSkipButtonProps {

export function OnboardingSkipButton({stepId}: OnboardingSkipButtonProps) {
const organization = useOrganization();
const {resetOnboarding} = useOnboardingContext();
const {activateSidebar} = useOnboardingSidebar();

const config = SKIP_CONFIG_BY_STEP[stepId];
Expand All @@ -47,6 +49,7 @@ export function OnboardingSkipButton({stepId}: OnboardingSkipButtonProps) {
}

const handleClick = () => {
resetOnboarding();
trackAnalytics('onboarding.scm_header_skip_clicked', {
organization,
step: stepId,
Expand Down
2 changes: 1 addition & 1 deletion static/app/views/onboarding/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export function OnboardingWithoutContext() {
organization,
source,
});
onboardingContext.setSelectedPlatform(undefined);
onboardingContext.resetOnboarding();
activateSidebar({
userClicked: false,
source: 'targeted_onboarding_select_platform_skip',
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/onboarding/useBackActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function useBackActions({
if (preserveOnboardingState) {
onboardingContext.setCreatedProjectSlug(undefined);
} else {
onboardingContext.setSelectedPlatform(undefined);
onboardingContext.resetOnboarding();
}

try {
Expand Down Expand Up @@ -106,7 +106,7 @@ export function useBackActions({

// from selected platform to welcome
if (currentStep.id === 'select-platform') {
onboardingContext.setSelectedPlatform(undefined);
onboardingContext.resetOnboarding();

if (!browserBackButton) {
goToStep(prevStep);
Expand Down
2 changes: 1 addition & 1 deletion static/app/views/onboarding/useConfigureSdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export function useConfigureSdk({
platform: selectedPlatform.key,
organization,
});
onboardingContext.setSelectedPlatform(undefined);
onboardingContext.resetOnboarding();
},
}
);
Expand Down
6 changes: 5 additions & 1 deletion static/app/views/onboarding/useWelcomeAnalyticsEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export function useWelcomeAnalyticsEffect() {

if (onboardingContext.selectedPlatform) {
// At this point the selectedSDK shall be undefined but just in case, cleaning this up here too
onboardingContext.setSelectedPlatform(undefined);
if (hasScmOnboarding) {
onboardingContext.clearDerivedState();
} else {
onboardingContext.resetOnboarding();
}
}
}, [organization, onboardingContext, hasScmOnboarding]);
}
Loading