Skip to content

Commit ed6ca20

Browse files
authored
ref(onboarding): Improve project polling mechanism (#85918)
De-dupe logic. Stop polling once the project is determined active. - part of getsentry/projects#736
1 parent 265795f commit ed6ca20

File tree

8 files changed

+86
-114
lines changed

8 files changed

+86
-114
lines changed
+30-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import moment from 'moment-timezone';
22

3-
import type {Group} from 'sentry/types/group';
43
import type {OnboardingRecentCreatedProject} from 'sentry/types/onboarding';
54
import type {Organization} from 'sentry/types/organization';
65
import type {Project} from 'sentry/types/project';
@@ -11,15 +10,29 @@ const DEFAULT_POLL_INTERVAL_MS = 1000;
1110

1211
type Props = {
1312
orgSlug: Organization['slug'];
13+
pollUntilFirstEvent?: boolean;
1414
projectSlug?: Project['slug'];
1515
};
1616

17-
// This hook will fetch the project details endpoint until a firstEvent(issue) is received
18-
// When the firstEvent is received it fetches the issues endpoint to find the first issue
17+
function isProjectActive(project: Project) {
18+
const olderThanOneHour = project
19+
? moment.duration(moment().diff(project.dateCreated)).asHours() > 1
20+
: false;
21+
return !!(
22+
project?.firstTransactionEvent ||
23+
project?.hasReplays ||
24+
project?.hasSessions ||
25+
project?.firstEvent ||
26+
olderThanOneHour
27+
);
28+
}
29+
30+
// This hook will fetch the project details endpoint until a firstEvent (issue) is received
1931
export function useRecentCreatedProject({
2032
orgSlug,
2133
projectSlug,
22-
}: Props): undefined | OnboardingRecentCreatedProject {
34+
pollUntilFirstEvent,
35+
}: Props): OnboardingRecentCreatedProject {
2336
const {isPending: isProjectLoading, data: project} = useApiQuery<Project>(
2437
[`/projects/${orgSlug}/${projectSlug}/`],
2538
{
@@ -30,41 +43,26 @@ export function useRecentCreatedProject({
3043
return false;
3144
}
3245
const [projectData] = query.state.data;
33-
return projectData?.firstEvent ? false : DEFAULT_POLL_INTERVAL_MS;
34-
},
35-
}
36-
);
37-
38-
const firstEvent = project?.firstEvent;
3946

40-
const {data: issues} = useApiQuery<Group[]>(
41-
[`/projects/${orgSlug}/${projectSlug}/issues/`],
42-
{
43-
staleTime: Infinity,
44-
enabled: !!firstEvent,
47+
if (pollUntilFirstEvent) {
48+
return projectData.firstEvent ? false : DEFAULT_POLL_INTERVAL_MS;
49+
}
50+
return isProjectActive(projectData) ? false : DEFAULT_POLL_INTERVAL_MS;
51+
},
4552
}
4653
);
4754

48-
const firstIssue =
49-
!!firstEvent && issues
50-
? issues.find((issue: Group) => issue.firstSeen === firstEvent)
51-
: undefined;
52-
53-
const olderThanOneHour = project
54-
? moment.duration(moment().diff(project.dateCreated)).asHours() > 1
55-
: false;
56-
5755
if (isProjectLoading || !project) {
58-
return undefined;
56+
return {
57+
isProjectActive: false,
58+
project: undefined,
59+
};
5960
}
6061

62+
const isActive = isProjectActive(project);
63+
6164
return {
62-
...project,
63-
firstTransaction: !!project?.firstTransactionEvent,
64-
hasReplays: !!project?.hasReplays,
65-
hasSessions: !!project?.hasSessions,
66-
firstError: !!firstEvent,
67-
firstIssue,
68-
olderThanOneHour,
65+
project,
66+
isProjectActive: isActive,
6967
};
7068
}

Diff for: static/app/types/onboarding.tsx

+3-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {OnboardingContextProps} from 'sentry/components/onboarding/onboardi
44
import type {Category} from 'sentry/components/platformPicker';
55
import type {InjectedRouter} from 'sentry/types/legacyReactRouter';
66

7-
import type {Group} from './group';
87
import type {Organization} from './organization';
98
import type {PlatformIntegration, PlatformKey, Project} from './project';
109
import type {AvatarUser} from './user';
@@ -141,13 +140,9 @@ export interface OnboardingSelectedSDK
141140
key: PlatformKey;
142141
}
143142

144-
export type OnboardingRecentCreatedProject = Project & {
145-
firstError: boolean;
146-
firstTransaction: boolean;
147-
hasReplays: boolean;
148-
hasSessions: boolean;
149-
olderThanOneHour: boolean;
150-
firstIssue?: Group;
143+
export type OnboardingRecentCreatedProject = {
144+
isProjectActive: boolean | undefined;
145+
project: Project | undefined;
151146
};
152147

153148
export type OnboardingPlatformDoc = {html: string; link: string};

Diff for: static/app/views/onboarding/components/firstEventFooter.tsx

+25-12
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {IconCheckmark} from 'sentry/icons';
1010
import {t} from 'sentry/locale';
1111
import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
1212
import {space} from 'sentry/styles/space';
13-
import type {OnboardingRecentCreatedProject} from 'sentry/types/onboarding';
13+
import type {Group} from 'sentry/types/group';
1414
import type {Organization} from 'sentry/types/organization';
15+
import type {Project} from 'sentry/types/project';
1516
import {trackAnalytics} from 'sentry/utils/analytics';
17+
import {useApiQuery} from 'sentry/utils/queryClient';
1618
import testableTransition from 'sentry/utils/testableTransition';
1719
import CreateSampleEventButton from 'sentry/views/onboarding/createSampleEventButton';
1820
import {useOnboardingSidebar} from 'sentry/views/onboarding/useOnboardingSidebar';
@@ -23,7 +25,7 @@ interface FirstEventFooterProps {
2325
isLast: boolean;
2426
onClickSetupLater: () => void;
2527
organization: Organization;
26-
project: OnboardingRecentCreatedProject;
28+
project: Project;
2729
}
2830

2931
export default function FirstEventFooter({
@@ -34,20 +36,33 @@ export default function FirstEventFooter({
3436
}: FirstEventFooterProps) {
3537
const {activateSidebar} = useOnboardingSidebar();
3638

39+
const {data: issues} = useApiQuery<Group[]>(
40+
[`/projects/${organization.slug}/${project.slug}/issues/`],
41+
{
42+
staleTime: Infinity,
43+
enabled: !!project.firstEvent,
44+
}
45+
);
46+
47+
const firstIssue =
48+
!!project.firstEvent && issues
49+
? issues.find((issue: Group) => issue.firstSeen === project.firstEvent)
50+
: undefined;
51+
3752
const source = 'targeted_onboarding_first_event_footer';
3853

3954
const getSecondaryCta = useCallback(() => {
4055
// if hasn't sent first event, allow skiping.
4156
// if last, no secondary cta
42-
if (!project?.firstError && !isLast) {
57+
if (!project?.firstEvent && !isLast) {
4358
return <Button onClick={onClickSetupLater}>{t('Next Platform')}</Button>;
4459
}
4560
return null;
46-
}, [project?.firstError, isLast, onClickSetupLater]);
61+
}, [project?.firstEvent, isLast, onClickSetupLater]);
4762

4863
const getPrimaryCta = useCallback(() => {
4964
// if hasn't sent first event, allow creation of sample error
50-
if (!project?.firstError) {
65+
if (!project?.firstEvent) {
5166
return (
5267
<CreateSampleEventButton
5368
project={project}
@@ -67,16 +82,14 @@ export default function FirstEventFooter({
6782
})
6883
}
6984
to={`/organizations/${organization.slug}/issues/${
70-
project?.firstIssue && 'id' in project.firstIssue
71-
? `${project.firstIssue.id}/`
72-
: ''
85+
firstIssue && 'id' in firstIssue ? `${firstIssue.id}/` : ''
7386
}?referrer=onboarding-first-event-footer`}
7487
priority="primary"
7588
>
7689
{t('Take me to my error')}
7790
</LinkButton>
7891
);
79-
}, [project, organization.slug]);
92+
}, [project, organization.slug, firstIssue]);
8093

8194
return (
8295
<GridFooter>
@@ -112,7 +125,7 @@ export default function FirstEventFooter({
112125
exit: {opacity: 0, y: 10},
113126
}}
114127
>
115-
{project?.firstError ? (
128+
{project?.firstEvent ? (
116129
<IconCheckmark isCircled color="green400" />
117130
) : (
118131
<WaitingIndicator
@@ -121,11 +134,11 @@ export default function FirstEventFooter({
121134
/>
122135
)}
123136
<AnimatedText
124-
errorReceived={project?.firstError}
137+
errorReceived={!!project?.firstEvent}
125138
variants={indicatorAnimation}
126139
transition={testableTransition()}
127140
>
128-
{project?.firstError ? t('Error Received') : t('Waiting for error')}
141+
{project?.firstEvent ? t('Error Received') : t('Waiting for error')}
129142
</AnimatedText>
130143
</StatusWrapper>
131144
<OnboardingButtonBar gap={2}>

Diff for: static/app/views/onboarding/onboarding.spec.tsx

+6-21
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,8 @@ describe('Onboarding', function () {
117117
.spyOn(useRecentCreatedProjectHook, 'useRecentCreatedProject')
118118
.mockImplementation(() => {
119119
return {
120-
...nextJsProject,
121-
firstError: false,
122-
firstTransaction: false,
123-
hasReplays: false,
124-
hasSessions: false,
125-
olderThanOneHour: false,
126-
firstIssue: undefined,
120+
project: nextJsProject,
121+
isProjectActive: false,
127122
};
128123
});
129124

@@ -204,13 +199,8 @@ describe('Onboarding', function () {
204199
.spyOn(useRecentCreatedProjectHook, 'useRecentCreatedProject')
205200
.mockImplementation(() => {
206201
return {
207-
...reactProject,
208-
firstError: false,
209-
firstTransaction: false,
210-
hasReplays: false,
211-
hasSessions: false,
212-
olderThanOneHour: false,
213-
firstIssue: undefined,
202+
project: reactProject,
203+
isProjectActive: false,
214204
};
215205
});
216206

@@ -301,13 +291,8 @@ describe('Onboarding', function () {
301291
.spyOn(useRecentCreatedProjectHook, 'useRecentCreatedProject')
302292
.mockImplementation(() => {
303293
return {
304-
...reactProject,
305-
firstError: false,
306-
firstTransaction: false,
307-
hasReplays: false,
308-
hasSessions: true,
309-
olderThanOneHour: false,
310-
firstIssue: undefined,
294+
project: reactProject,
295+
isProjectActive: true,
311296
};
312297
});
313298

Diff for: static/app/views/onboarding/onboarding.tsx

+9-14
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ function Onboarding(props: Props) {
8686
const projectSlug =
8787
stepObj && stepObj.id === 'setup-docs' ? selectedProjectSlug : undefined;
8888

89-
const recentCreatedProject = useRecentCreatedProject({
89+
const {project: recentCreatedProject, isProjectActive} = useRecentCreatedProject({
9090
orgSlug: organization.slug,
9191
projectSlug,
92+
// Wait until the first event is received as we have an UI element that depends on it
93+
pollUntilFirstEvent: true,
9294
});
9395

9496
const cornerVariantTimeoutRed = useRef<number | undefined>(undefined);
@@ -150,18 +152,7 @@ function Onboarding(props: Props) {
150152
]);
151153

152154
const shallProjectBeDeleted =
153-
stepObj?.id === 'setup-docs' &&
154-
recentCreatedProject &&
155-
// if the project has received a first error, we don't delete it
156-
recentCreatedProject.firstError === false &&
157-
// if the project has received a first transaction, we don't delete it
158-
recentCreatedProject.firstTransaction === false &&
159-
// if the project has replays, we don't delete it
160-
recentCreatedProject.hasReplays === false &&
161-
// if the project has sessions, we don't delete it
162-
recentCreatedProject.hasSessions === false &&
163-
// if the project is older than one hour, we don't delete it
164-
recentCreatedProject.olderThanOneHour === false;
155+
stepObj?.id === 'setup-docs' && defined(isProjectActive) && !isProjectActive;
165156

166157
const cornerVariantControl = useAnimation();
167158
const updateCornerVariant = () => {
@@ -292,7 +283,11 @@ function Onboarding(props: Props) {
292283
}
293284

294285
// from setup docs to selected platform
295-
if (onboardingSteps[stepIndex]!.id === 'setup-docs' && shallProjectBeDeleted) {
286+
if (
287+
onboardingSteps[stepIndex]!.id === 'setup-docs' &&
288+
shallProjectBeDeleted &&
289+
recentCreatedProject
290+
) {
296291
trackAnalytics('onboarding.data_removal_modal_confirm_button_clicked', {
297292
organization,
298293
platform: recentCreatedProject.slug,

Diff for: static/app/views/onboarding/setupDocs.spec.tsx

+8-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types';
1313
import {OnboardingContextProvider} from 'sentry/components/onboarding/onboardingContext';
1414
import ProjectsStore from 'sentry/stores/projectsStore';
15-
import type {OnboardingRecentCreatedProject} from 'sentry/types/onboarding';
1615
import type {Organization} from 'sentry/types/organization';
1716
import type {Project} from 'sentry/types/project';
1817
import SetupDocs from 'sentry/views/onboarding/setupDocs';
@@ -85,7 +84,7 @@ describe('Onboarding Setup Docs', function () {
8584
genSkipOnboardingLink={() => ''}
8685
orgId={organization.slug}
8786
search=""
88-
recentCreatedProject={project as OnboardingRecentCreatedProject}
87+
recentCreatedProject={project}
8988
/>
9089
</OnboardingContextProvider>,
9190
{
@@ -133,7 +132,7 @@ describe('Onboarding Setup Docs', function () {
133132
genSkipOnboardingLink={() => ''}
134133
orgId={organization.slug}
135134
search=""
136-
recentCreatedProject={project as OnboardingRecentCreatedProject}
135+
recentCreatedProject={project}
137136
/>
138137
</OnboardingContextProvider>,
139138
{
@@ -189,7 +188,7 @@ describe('Onboarding Setup Docs', function () {
189188
genSkipOnboardingLink={() => ''}
190189
orgId={organization.slug}
191190
search=""
192-
recentCreatedProject={project as OnboardingRecentCreatedProject}
191+
recentCreatedProject={project}
193192
/>
194193
</OnboardingContextProvider>,
195194
{
@@ -243,7 +242,7 @@ describe('Onboarding Setup Docs', function () {
243242
genSkipOnboardingLink={() => ''}
244243
orgId={organization.slug}
245244
search=""
246-
recentCreatedProject={project as OnboardingRecentCreatedProject}
245+
recentCreatedProject={project}
247246
/>
248247
</OnboardingContextProvider>,
249248
{
@@ -293,7 +292,7 @@ describe('Onboarding Setup Docs', function () {
293292
genSkipOnboardingLink={() => ''}
294293
orgId={organization.slug}
295294
search=""
296-
recentCreatedProject={project as OnboardingRecentCreatedProject}
295+
recentCreatedProject={project}
297296
/>
298297
</OnboardingContextProvider>,
299298
{
@@ -343,7 +342,7 @@ describe('Onboarding Setup Docs', function () {
343342
genSkipOnboardingLink={() => ''}
344343
orgId={organization.slug}
345344
search=""
346-
recentCreatedProject={project as OnboardingRecentCreatedProject}
345+
recentCreatedProject={project}
347346
/>
348347
</OnboardingContextProvider>,
349348
{
@@ -412,7 +411,7 @@ describe('Onboarding Setup Docs', function () {
412411
genSkipOnboardingLink={() => ''}
413412
orgId={organization.slug}
414413
search=""
415-
recentCreatedProject={project as OnboardingRecentCreatedProject}
414+
recentCreatedProject={project}
416415
/>
417416
</OnboardingContextProvider>,
418417
{
@@ -496,7 +495,7 @@ describe('Onboarding Setup Docs', function () {
496495
genSkipOnboardingLink={() => ''}
497496
orgId={organization.slug}
498497
search=""
499-
recentCreatedProject={project as OnboardingRecentCreatedProject}
498+
recentCreatedProject={project}
500499
/>
501500
</OnboardingContextProvider>,
502501
{

0 commit comments

Comments
 (0)