Skip to content

Commit d68feb8

Browse files
authored
feat(seer): Add new onboarding for Seer (#104215)
This adds a new onboarding page for Seer, currently feature flagged for sentry-test and sentry-sdks orgs. access via hard-coded url: `/settings/seer/onboarding-v2/`
1 parent e419988 commit d68feb8

19 files changed

+1399
-0
lines changed

static/app/router/routes.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,11 @@ function buildRoutes(): RouteObject[] {
12301230
name: t('Configure Seer for All Projects'),
12311231
component: make(() => import('getsentry/views/seerAutomation/onboarding')),
12321232
},
1233+
{
1234+
path: 'onboarding-v2/',
1235+
name: t('Setup Wizard'),
1236+
component: make(() => import('getsentry/views/seerAutomation/onboardingV2')),
1237+
},
12331238
],
12341239
},
12351240
{

static/app/utils/analytics/integrations/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export type IntegrationView = {
1919
| 'project_creation'
2020
| 'developer_settings'
2121
| 'new_integration_modal'
22+
| 'seer_onboarding_github'
23+
| 'seer_onboarding_code_review'
2224
| 'test_analytics_onboarding'
2325
| 'test_analytics_org_selector';
2426
};

static/app/views/settings/organizationIntegrations/addIntegration.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Props = {
2323
| 'integrations_directory'
2424
| 'onboarding'
2525
| 'project_creation'
26+
| 'seer_onboarding_github'
27+
| 'seer_onboarding_code_review'
2628
| 'test_analytics_onboarding'
2729
| 'test_analytics_org_selector';
2830
};

static/app/views/settings/organizationIntegrations/integrationContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ type IntegrationContextProps = {
1212
| 'integrations_directory'
1313
| 'onboarding'
1414
| 'project_creation'
15+
| 'seer_onboarding_github'
16+
| 'seer_onboarding_code_review'
1517
| 'test_analytics_onboarding'
1618
| 'test_analytics_org_selector';
1719
referrer?: string;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import styled from '@emotion/styled';
2+
3+
import Panel from 'sentry/components/panels/panel';
4+
5+
export const MaxWidthPanel = styled(Panel)`
6+
max-width: 600px;
7+
`;
8+
9+
export const PanelDescription = styled('div')`
10+
padding: ${p => p.theme.space.xl};
11+
border-bottom: 1px solid ${p => p.theme.innerBorder};
12+
`;
13+
14+
export const StepContent = styled('div')`
15+
margin-top: ${p => p.theme.space.lg};
16+
margin-bottom: ${p => p.theme.space.xl};
17+
18+
p {
19+
margin-bottom: ${p => p.theme.space.lg};
20+
line-height: 1.6;
21+
}
22+
23+
p:last-of-type {
24+
margin-bottom: 0;
25+
}
26+
`;
27+
28+
export const ActionSection = styled('div')`
29+
margin-top: ${p => p.theme.space.xl};
30+
display: flex;
31+
gap: ${p => p.theme.space.md};
32+
`;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {Fragment, useCallback} from 'react';
2+
3+
import {Button} from '@sentry/scraps/button';
4+
5+
import {
6+
GuidedSteps,
7+
useGuidedStepsContext,
8+
} from 'sentry/components/guidedSteps/guidedSteps';
9+
import PanelBody from 'sentry/components/panels/panelBody';
10+
import {t} from 'sentry/locale';
11+
12+
import {useSeerOnboardingContext} from './hooks/seerOnboardingContext';
13+
import {MaxWidthPanel, PanelDescription, StepContent} from './common';
14+
import {RepositorySelector} from './repositorySelector';
15+
16+
export function ConfigureCodeReviewStep() {
17+
const {selectedCodeReviewRepositories} = useSeerOnboardingContext();
18+
const {currentStep, setCurrentStep} = useGuidedStepsContext();
19+
20+
const hasSelectedRepositories = selectedCodeReviewRepositories.length > 0;
21+
22+
const handleNextStep = useCallback(() => {
23+
if (hasSelectedRepositories) {
24+
// TODO: Save to backend
25+
setCurrentStep(currentStep + 1);
26+
}
27+
}, [hasSelectedRepositories, setCurrentStep, currentStep]);
28+
29+
return (
30+
<Fragment>
31+
<StepContent>
32+
<MaxWidthPanel>
33+
<PanelBody>
34+
<PanelDescription>
35+
<p>{t(`You successfully connected to GitHub!`)}</p>
36+
37+
<p>
38+
{t(`
39+
Now, select which of your repositories you would like to run Seer’s AI Code Review on.
40+
`)}
41+
</p>
42+
</PanelDescription>
43+
44+
<RepositorySelector />
45+
</PanelBody>
46+
</MaxWidthPanel>
47+
48+
<GuidedSteps.ButtonWrapper>
49+
<Button
50+
size="md"
51+
onClick={handleNextStep}
52+
priority={hasSelectedRepositories ? 'primary' : 'default'}
53+
disabled={!hasSelectedRepositories}
54+
aria-label={t('Next Step')}
55+
title={
56+
hasSelectedRepositories
57+
? undefined
58+
: t('Select repositories before continuing to the next step')
59+
}
60+
>
61+
{t('Next Step')}
62+
</Button>
63+
</GuidedSteps.ButtonWrapper>
64+
</StepContent>
65+
</Fragment>
66+
);
67+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import {Fragment, useCallback, useEffect, useMemo, useState} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import {Button} from '@sentry/scraps/button';
5+
6+
import {addErrorMessage} from 'sentry/actionCreators/indicator';
7+
import {Flex} from 'sentry/components/core/layout/flex';
8+
import {Switch} from 'sentry/components/core/switch';
9+
import {
10+
GuidedSteps,
11+
useGuidedStepsContext,
12+
} from 'sentry/components/guidedSteps/guidedSteps';
13+
import PanelBody from 'sentry/components/panels/panelBody';
14+
import PanelItem from 'sentry/components/panels/panelItem';
15+
import Placeholder from 'sentry/components/placeholder';
16+
import {t} from 'sentry/locale';
17+
import useProjects from 'sentry/utils/useProjects';
18+
19+
import {useSeerOnboardingContext} from './hooks/seerOnboardingContext';
20+
import {useCodeMappings} from './hooks/useCodeMappings';
21+
import {MaxWidthPanel, PanelDescription, StepContent} from './common';
22+
import {RepositoryToProjectConfiguration} from './repositoryToProjectConfiguration';
23+
24+
export function ConfigureRootCauseAnalysisStep() {
25+
const [proposeFixesEnabled, setProposeFixesEnabled] = useState(true);
26+
const [autoCreatePREnabled, setAutoCreatePREnabled] = useState(true);
27+
28+
const {currentStep, setCurrentStep} = useGuidedStepsContext();
29+
const {
30+
selectedRootCauseAnalysisRepositories,
31+
repositoryProjectMapping,
32+
changeRepositoryProjectMapping,
33+
addRepositoryProjectMappings,
34+
} = useSeerOnboardingContext();
35+
36+
const {
37+
projects,
38+
initiallyLoaded: isProjectsLoaded,
39+
fetching: isProjectsFetching,
40+
} = useProjects();
41+
42+
const {codeMappingsMap, isLoading: isCodeMappingsLoading} = useCodeMappings({
43+
enabled: selectedRootCauseAnalysisRepositories.length > 0,
44+
});
45+
46+
useEffect(() => {
47+
if (!isCodeMappingsLoading && codeMappingsMap.size > 0) {
48+
const additionalMappings: Record<string, string[]> = {};
49+
selectedRootCauseAnalysisRepositories.forEach(repo => {
50+
const mappedProjects = Array.from(codeMappingsMap.get(repo.id) || []);
51+
additionalMappings[repo.id] = mappedProjects;
52+
});
53+
addRepositoryProjectMappings(additionalMappings);
54+
}
55+
}, [
56+
isCodeMappingsLoading,
57+
selectedRootCauseAnalysisRepositories,
58+
codeMappingsMap,
59+
addRepositoryProjectMappings,
60+
]);
61+
62+
const handlePreviousStep = useCallback(() => {
63+
setCurrentStep(currentStep - 1);
64+
}, [setCurrentStep, currentStep]);
65+
66+
const handleNextStep = useCallback(() => {
67+
// TODO: Save to backend
68+
setCurrentStep(currentStep + 1);
69+
}, [setCurrentStep, currentStep]);
70+
71+
const handleRepositoryProjectMappingsChange = useCallback(
72+
(repoId: string, index: number, newValue: string | undefined) => {
73+
const currentProjects = repositoryProjectMapping[repoId] || [];
74+
75+
if (newValue && currentProjects.includes(newValue)) {
76+
// Project is already mapped to this repo, show an error message and don't update anything
77+
// We could make our dropdowns smarter by filtering out selected projects,
78+
// but this is much simpler.
79+
addErrorMessage(t('Project is already mapped to this repo'));
80+
return;
81+
}
82+
83+
changeRepositoryProjectMapping(repoId, index, newValue);
84+
},
85+
[changeRepositoryProjectMapping, repositoryProjectMapping]
86+
);
87+
88+
const isFinishDisabled = useMemo(() => {
89+
const mappings = Object.values(repositoryProjectMapping);
90+
return (
91+
!mappings.length ||
92+
mappings.length !== selectedRootCauseAnalysisRepositories.length ||
93+
Boolean(mappings.some(mappedProjects => mappedProjects.length === 0)) ||
94+
selectedRootCauseAnalysisRepositories.length === 0
95+
);
96+
}, [repositoryProjectMapping, selectedRootCauseAnalysisRepositories.length]);
97+
98+
return (
99+
<Fragment>
100+
<StepContent>
101+
<MaxWidthPanel>
102+
<PanelBody>
103+
<PanelDescription>
104+
<p>
105+
{t(
106+
'Pair your projects with your repositories to enable Seer to analyze your codebase.'
107+
)}
108+
</p>
109+
</PanelDescription>
110+
111+
<Field>
112+
<Flex direction="column" flex="1" gap="xs">
113+
<FieldLabel>{t('Propose Fixes For Root Cause Analysis')}</FieldLabel>
114+
<FieldDescription>
115+
{t(
116+
'For all projects below, Seer will automatically analyze highly actionable issues, and create a root cause analysis and proposed solution without a user needing to prompt it.'
117+
)}
118+
</FieldDescription>
119+
</Flex>
120+
<Switch
121+
size="lg"
122+
checked={proposeFixesEnabled}
123+
onChange={() => setProposeFixesEnabled(!proposeFixesEnabled)}
124+
/>
125+
</Field>
126+
<Field>
127+
<Flex direction="column" flex="1" gap="xs">
128+
<FieldLabel>{t('Automatic PR Creation')}</FieldLabel>
129+
<FieldDescription>
130+
{t('For all projects below, Seer will be able to make a pull request.')}
131+
</FieldDescription>
132+
</Flex>
133+
<Switch
134+
size="lg"
135+
checked={autoCreatePREnabled}
136+
onChange={() => setAutoCreatePREnabled(!autoCreatePREnabled)}
137+
/>
138+
</Field>
139+
140+
{isProjectsLoaded && !isProjectsFetching ? (
141+
<RepositoryToProjectConfiguration
142+
isPending={isCodeMappingsLoading}
143+
projects={projects}
144+
onChange={handleRepositoryProjectMappingsChange}
145+
/>
146+
) : (
147+
<Flex direction="column" gap="md" padding="md">
148+
{selectedRootCauseAnalysisRepositories.map(repository => (
149+
<Placeholder key={repository.id} />
150+
))}
151+
</Flex>
152+
)}
153+
</PanelBody>
154+
</MaxWidthPanel>
155+
</StepContent>
156+
157+
<GuidedSteps.ButtonWrapper>
158+
<Button size="md" onClick={handlePreviousStep} aria-label={t('Previous Step')}>
159+
{t('Previous Step')}
160+
</Button>
161+
<Button
162+
size="md"
163+
onClick={handleNextStep}
164+
priority={isFinishDisabled ? 'default' : 'primary'}
165+
disabled={isFinishDisabled}
166+
aria-label={t('Last Step')}
167+
>
168+
{t('Last Step')}
169+
</Button>
170+
</GuidedSteps.ButtonWrapper>
171+
</Fragment>
172+
);
173+
}
174+
175+
const Field = styled(PanelItem)`
176+
align-items: start;
177+
justify-content: space-between;
178+
gap: ${p => p.theme.space.xl};
179+
`;
180+
181+
const FieldLabel = styled('div')`
182+
font-weight: ${p => p.theme.fontWeight.bold};
183+
`;
184+
185+
const FieldDescription = styled('div')`
186+
font-size: ${p => p.theme.fontSize.sm};
187+
color: ${p => p.theme.subText};
188+
line-height: 1.4;
189+
`;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {Fragment, useCallback} from 'react';
2+
3+
import {GuidedSteps} from 'sentry/components/guidedSteps/guidedSteps';
4+
import PanelBody from 'sentry/components/panels/panelBody';
5+
import {t} from 'sentry/locale';
6+
7+
import {ActionSection, MaxWidthPanel, StepContent} from './common';
8+
import {GithubButton} from './githubButton';
9+
10+
export function ConnectGithubStep() {
11+
const handleAddIntegration = useCallback(() => {
12+
window.location.reload();
13+
}, []);
14+
15+
return (
16+
<Fragment>
17+
<StepContent>
18+
<MaxWidthPanel>
19+
<PanelBody withPadding>
20+
<p>
21+
{t(
22+
'In order to get the most out of Sentry and use Seer we will need to access your code repositories in GitHub. (We do not currently support Gitlab, Bitbucket, or others)'
23+
)}
24+
</p>
25+
<ActionSection>
26+
<GithubButton
27+
onAddIntegration={handleAddIntegration}
28+
analyticsView="seer_onboarding_github"
29+
/>
30+
</ActionSection>
31+
<GuidedSteps.ButtonWrapper>
32+
<GuidedSteps.NextButton size="md" />
33+
</GuidedSteps.ButtonWrapper>
34+
</PanelBody>
35+
</MaxWidthPanel>
36+
</StepContent>
37+
</Fragment>
38+
);
39+
}

0 commit comments

Comments
 (0)