Skip to content

Commit 6a3f076

Browse files
feat(source-maps): Introduce new empty state copies and react-native callout (#92286)
1 parent 975f8d7 commit 6a3f076

File tree

3 files changed

+132
-11
lines changed

3 files changed

+132
-11
lines changed

static/app/components/events/interfaces/sourceMapsDebuggerModal.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export interface SourceMapsDebuggerModalProps extends ModalRenderProps {
157157
orgSlug?: string;
158158
}
159159

160-
const projectPlatformToDocsMap: Record<string, string> = {
160+
export const projectPlatformToDocsMap: Record<string, string> = {
161161
'node-azurefunctions': 'azure-functions',
162162
'node-cloudflare-pages': 'cloudflare',
163163
'node-cloudflare-workers': 'cloudflare',
@@ -170,6 +170,20 @@ const projectPlatformToDocsMap: Record<string, string> = {
170170
'node-nestjs': 'nestjs',
171171
'node-restify': 'restify',
172172
'node-awslambda': 'aws-lambda',
173+
'javascript-react': 'react',
174+
'javascript-angular': 'angular',
175+
'javascript-ember': 'ember',
176+
'javascript-gatsby': 'gatsby',
177+
'javascript-vue': 'vue',
178+
'javascript-nextjs': 'nextjs',
179+
'javascript-nuxt': 'nuxt',
180+
'javascript-remix': 'remix',
181+
'javascript-solid': 'solid',
182+
'javascript-solidstart': 'solidstart',
183+
'javascript-svelte': 'svelte',
184+
'javascript-sveltekit': 'sveltekit',
185+
'javascript-astro': 'astro',
186+
'javascript-tanstackstart-react': 'tanstackstart-react',
173187
};
174188

175189
function isReactNativeSDK({sdkName}: Pick<FrameSourceMapDebuggerData, 'sdkName'>) {
@@ -190,7 +204,7 @@ function getPlatform({
190204
);
191205
}
192206

193-
function getSourceMapsDocLinks(platform: string) {
207+
export function getSourceMapsDocLinks(platform: string) {
194208
if (platform === 'react-native') {
195209
return {
196210
sourcemaps: `https://docs.sentry.io/platforms/react-native/sourcemaps/`,

static/app/views/settings/projectSourceMaps/sourceMapsList.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ describe('ProjectSourceMaps', function () {
169169
organization,
170170
});
171171

172-
expect(await screen.findByText('No source map uploads found')).toBeInTheDocument();
172+
expect(await screen.findByText('No source maps uploaded')).toBeInTheDocument();
173173
});
174174
});
175175
});

static/app/views/settings/projectSourceMaps/sourceMapsList.tsx

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import {Fragment, useCallback, useMemo, useState} from 'react';
2+
import {css} from '@emotion/react';
23
import styled from '@emotion/styled';
34

45
import Access from 'sentry/components/acl/access';
6+
import {CodeSnippet} from 'sentry/components/codeSnippet';
57
import Confirm from 'sentry/components/confirm';
68
import {Button, type ButtonProps} from 'sentry/components/core/button';
79
import {Tooltip} from 'sentry/components/core/tooltip';
810
import {DateTime} from 'sentry/components/dateTime';
911
import EmptyMessage from 'sentry/components/emptyMessage';
1012
import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
13+
import {
14+
getSourceMapsDocLinks,
15+
projectPlatformToDocsMap,
16+
} from 'sentry/components/events/interfaces/sourceMapsDebuggerModal';
1117
import ExternalLink from 'sentry/components/links/externalLink';
1218
import Link from 'sentry/components/links/link';
1319
import LoadingIndicator from 'sentry/components/loadingIndicator';
@@ -24,6 +30,7 @@ import type {Organization} from 'sentry/types/organization';
2430
import type {Project} from 'sentry/types/project';
2531
import type {SourceMapsArchive} from 'sentry/types/release';
2632
import type {DebugIdBundle, DebugIdBundleAssociation} from 'sentry/types/sourceMaps';
33+
import {defined} from 'sentry/utils';
2734
import {keepPreviousData, useApiQuery} from 'sentry/utils/queryClient';
2835
import {decodeScalar} from 'sentry/utils/queryString';
2936
import useOrganization from 'sentry/utils/useOrganization';
@@ -135,7 +142,7 @@ function useSourceMapUploads({
135142

136143
export function SourceMapsList({location, router, project}: Props) {
137144
const organization = useOrganization();
138-
const query = decodeScalar(location.query.query);
145+
const query = decodeScalar(location.query.query) ?? '';
139146

140147
const cursor = location.query.cursor ?? '';
141148

@@ -165,16 +172,20 @@ export function SourceMapsList({location, router, project}: Props) {
165172
[router, location]
166173
);
167174

175+
const platformByProject = defined(project.platform)
176+
? projectPlatformToDocsMap[project.platform]
177+
: undefined;
178+
const platform = platformByProject ?? project.platform ?? 'javascript';
179+
const sourceMapsLinks = getSourceMapsDocLinks(platform);
180+
168181
return (
169182
<Fragment>
170183
<SettingsPageHeader title={t('Source Map Uploads')} />
171184
<TextBlock>
172185
{tct(
173186
`These source map archives help Sentry identify where to look when code is minified. By providing this information, you can get better context for your stack traces when debugging. To learn more about source maps, [link: read the docs].`,
174187
{
175-
link: (
176-
<ExternalLink href="https://docs.sentry.io/platforms/javascript/sourcemaps/" />
177-
),
188+
link: <ExternalLink href={sourceMapsLinks.sourcemaps} />,
178189
}
179190
)}
180191
</TextBlock>
@@ -187,30 +198,119 @@ export function SourceMapsList({location, router, project}: Props) {
187198
project={project}
188199
sourceMapUploads={sourceMapUploads}
189200
isLoading={isPending}
190-
emptyMessage={t('No source map uploads found')}
201+
query={query}
202+
onClearSearch={() => handleSearch('')}
191203
onDelete={id => {
192204
deleteSourceMaps({bundleId: id, projectSlug: project.slug});
193205
}}
206+
docsLink={sourceMapsLinks.sourcemaps}
194207
/>
195208
<Pagination pageLinks={headers?.('Link') ?? ''} />
196209
</Fragment>
197210
);
198211
}
199212

213+
function ReactNativeCallOut() {
214+
const [selectedTab, setSelectedTab] = useState('expo');
215+
216+
return (
217+
<div
218+
css={css`
219+
text-align: left;
220+
display: grid;
221+
gap: ${space(1)};
222+
`}
223+
>
224+
<div>
225+
{tct(
226+
'For React Native projects, ensure you [strong:run the app in release mode] and execute the scripts below to upload source maps for both iOS and Android:',
227+
{strong: <strong />}
228+
)}
229+
</div>
230+
<CodeSnippet
231+
dark
232+
language="bash"
233+
tabs={[
234+
{label: 'Expo', value: 'expo'},
235+
{label: 'React Native', value: 'react-native'},
236+
]}
237+
selectedTab={selectedTab}
238+
onTabClick={value => setSelectedTab(value)}
239+
>
240+
{selectedTab === 'expo'
241+
? '# First run this to create a build and upload source maps\n./gradlew assembleRelease\n# Then run this to test your build locally\nnpx expo run:android --variant release\n\n# iOS version (pending confirmation)\nnpx expo run:ios --configuration Release'
242+
: 'npx react-native run-android --mode release\nnpx react-native run-ios --mode Release'}
243+
</CodeSnippet>
244+
</div>
245+
);
246+
}
247+
248+
interface SourceMapsEmptyStateProps {
249+
docsLink: string;
250+
onClearSearch: () => void;
251+
project: Project;
252+
query?: string;
253+
}
254+
255+
function SourceMapsEmptyState({
256+
query,
257+
onClearSearch,
258+
project,
259+
docsLink,
260+
}: SourceMapsEmptyStateProps) {
261+
return (
262+
<Panel dashedBorder>
263+
<EmptyMessage
264+
title={
265+
query
266+
? t('No source maps uploads matching your search')
267+
: t('No source maps uploaded')
268+
}
269+
description={
270+
query
271+
? tct(
272+
'Try to modify or [clear:clear] your search to see all source maps uploads.',
273+
{
274+
clear: (
275+
<Button
276+
priority="link"
277+
aria-label={t('Clear Search')}
278+
onClick={onClearSearch}
279+
/>
280+
),
281+
}
282+
)
283+
: tct(
284+
'Source maps allow Sentry to map your production code to your source code. See our [docs:docs] to learn more about configuring your application to upload source maps to Sentry.',
285+
{
286+
docs: <ExternalLink href={docsLink} />,
287+
}
288+
)
289+
}
290+
action={project.platform === 'react-native' ? <ReactNativeCallOut /> : undefined}
291+
/>
292+
</Panel>
293+
);
294+
}
295+
200296
interface SourceMapUploadsListProps {
201-
emptyMessage: React.ReactNode;
297+
docsLink: string;
202298
isLoading: boolean;
299+
onClearSearch: () => void;
203300
onDelete: (id: string) => void;
204301
project: Project;
302+
query?: string;
205303
sourceMapUploads?: SourceMapUpload[];
206304
}
207305

208306
function SourceMapUploadsList({
307+
onClearSearch,
209308
isLoading,
210309
sourceMapUploads,
211-
emptyMessage,
212310
onDelete,
213311
project,
312+
query,
313+
docsLink,
214314
}: SourceMapUploadsListProps) {
215315
const organization = useOrganization();
216316

@@ -226,7 +326,14 @@ function SourceMapUploadsList({
226326
}
227327

228328
if (!sourceMapUploads || sourceMapUploads.length === 0) {
229-
return <EmptyMessage>{emptyMessage}</EmptyMessage>;
329+
return (
330+
<SourceMapsEmptyState
331+
project={project}
332+
query={query}
333+
onClearSearch={onClearSearch}
334+
docsLink={docsLink}
335+
/>
336+
);
230337
}
231338

232339
return (

0 commit comments

Comments
 (0)