Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface SourceMapsDebuggerModalProps extends ModalRenderProps {
orgSlug?: string;
}

const projectPlatformToDocsMap: Record<string, string> = {
export const projectPlatformToDocsMap: Record<string, string> = {
'node-azurefunctions': 'azure-functions',
'node-cloudflare-pages': 'cloudflare',
'node-cloudflare-workers': 'cloudflare',
Expand All @@ -171,6 +171,20 @@ const projectPlatformToDocsMap: Record<string, string> = {
'node-nestjs': 'nestjs',
'node-restify': 'restify',
'node-awslambda': 'aws-lambda',
'javascript-react': 'react',
'javascript-angular': 'angular',
'javascript-ember': 'ember',
'javascript-gatsby': 'gatsby',
'javascript-vue': 'vue',
'javascript-nextjs': 'nextjs',
'javascript-nuxt': 'nuxt',
'javascript-remix': 'remix',
'javascript-solid': 'solid',
'javascript-solidstart': 'solidstart',
'javascript-svelte': 'svelte',
'javascript-sveltekit': 'sveltekit',
'javascript-astro': 'astro',
'javascript-tanstackstart-react': 'tanstackstart-react',
};

function isReactNativeSDK({sdkName}: Pick<FrameSourceMapDebuggerData, 'sdkName'>) {
Expand All @@ -191,7 +205,7 @@ function getPlatform({
);
}

function getSourceMapsDocLinks(platform: string) {
export function getSourceMapsDocLinks(platform: string) {
if (platform === 'react-native') {
return {
sourcemaps: `https://docs.sentry.io/platforms/react-native/sourcemaps/`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ describe('ProjectSourceMaps', function () {
organization,
});

expect(await screen.findByText('No source map uploads found')).toBeInTheDocument();
expect(await screen.findByText('No source maps uploaded')).toBeInTheDocument();
});
});
});
123 changes: 115 additions & 8 deletions static/app/views/settings/projectSourceMaps/sourceMapsList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import {Fragment, useCallback, useMemo, useState} from 'react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';

import Access from 'sentry/components/acl/access';
import {CodeSnippet} from 'sentry/components/codeSnippet';
import Confirm from 'sentry/components/confirm';
import {Button, type ButtonProps} from 'sentry/components/core/button';
import {Tooltip} from 'sentry/components/core/tooltip';
import {DateTime} from 'sentry/components/dateTime';
import EmptyMessage from 'sentry/components/emptyMessage';
import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
import {
getSourceMapsDocLinks,
projectPlatformToDocsMap,
} from 'sentry/components/events/interfaces/sourceMapsDebuggerModal';
import ExternalLink from 'sentry/components/links/externalLink';
import Link from 'sentry/components/links/link';
import LoadingIndicator from 'sentry/components/loadingIndicator';
Expand All @@ -24,6 +30,7 @@ import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import type {SourceMapsArchive} from 'sentry/types/release';
import type {DebugIdBundle, DebugIdBundleAssociation} from 'sentry/types/sourceMaps';
import {defined} from 'sentry/utils';
import {keepPreviousData, useApiQuery} from 'sentry/utils/queryClient';
import {decodeScalar} from 'sentry/utils/queryString';
import useOrganization from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -135,7 +142,7 @@ function useSourceMapUploads({

export function SourceMapsList({location, router, project}: Props) {
const organization = useOrganization();
const query = decodeScalar(location.query.query);
const query = decodeScalar(location.query.query) ?? '';

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

Expand Down Expand Up @@ -165,16 +172,20 @@ export function SourceMapsList({location, router, project}: Props) {
[router, location]
);

const platformByProject = defined(project.platform)
? projectPlatformToDocsMap[project.platform]
: undefined;
const platform = platformByProject ?? project.platform ?? 'javascript';
const sourceMapsLinks = getSourceMapsDocLinks(platform);

return (
<Fragment>
<SettingsPageHeader title={t('Source Map Uploads')} />
<TextBlock>
{tct(
`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].`,
{
link: (
<ExternalLink href="https://docs.sentry.io/platforms/javascript/sourcemaps/" />
),
link: <ExternalLink href={sourceMapsLinks.sourcemaps} />,
}
)}
</TextBlock>
Expand All @@ -187,30 +198,119 @@ export function SourceMapsList({location, router, project}: Props) {
project={project}
sourceMapUploads={sourceMapUploads}
isLoading={isPending}
emptyMessage={t('No source map uploads found')}
query={query}
onClearSearch={() => handleSearch('')}
onDelete={id => {
deleteSourceMaps({bundleId: id, projectSlug: project.slug});
}}
docsLink={sourceMapsLinks.sourcemaps}
/>
<Pagination pageLinks={headers?.('Link') ?? ''} />
</Fragment>
);
}

function ReactNativeCallOut() {
const [selectedTab, setSelectedTab] = useState('expo');

return (
<div
css={css`
text-align: left;
display: grid;
gap: ${space(1)};
`}
Comment on lines +218 to +222
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not styled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have seen both in our code. What has been our preference? can update in a follow-up if needed.

>
<div>
{tct(
'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:',
{strong: <strong />}
)}
</div>
<CodeSnippet
dark
language="bash"
tabs={[
{label: 'Expo', value: 'expo'},
{label: 'React Native', value: 'react-native'},
]}
selectedTab={selectedTab}
onTabClick={value => setSelectedTab(value)}
>
{selectedTab === 'expo'
? '# 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'
: 'npx react-native run-android --mode release\nnpx react-native run-ios --mode Release'}
</CodeSnippet>
</div>
);
}

interface SourceMapsEmptyStateProps {
docsLink: string;
onClearSearch: () => void;
project: Project;
query?: string;
}

function SourceMapsEmptyState({
query,
onClearSearch,
project,
docsLink,
}: SourceMapsEmptyStateProps) {
return (
<Panel dashedBorder>
<EmptyMessage
title={
query
? t('No source maps uploads matching your search')
: t('No source maps uploaded')
}
description={
query
? tct(
'Try to modify or [clear:clear] your search to see all source maps uploads.',
{
clear: (
<Button
priority="link"
aria-label={t('Clear Search')}
onClick={onClearSearch}
/>
),
}
)
: tct(
'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.',
{
docs: <ExternalLink href={docsLink} />,
}
)
}
action={project.platform === 'react-native' ? <ReactNativeCallOut /> : undefined}
/>
</Panel>
);
}

interface SourceMapUploadsListProps {
emptyMessage: React.ReactNode;
docsLink: string;
isLoading: boolean;
onClearSearch: () => void;
onDelete: (id: string) => void;
project: Project;
query?: string;
sourceMapUploads?: SourceMapUpload[];
}

function SourceMapUploadsList({
onClearSearch,
isLoading,
sourceMapUploads,
emptyMessage,
onDelete,
project,
query,
docsLink,
}: SourceMapUploadsListProps) {
const organization = useOrganization();

Expand All @@ -226,7 +326,14 @@ function SourceMapUploadsList({
}

if (!sourceMapUploads || sourceMapUploads.length === 0) {
return <EmptyMessage>{emptyMessage}</EmptyMessage>;
return (
<SourceMapsEmptyState
project={project}
query={query}
onClearSearch={onClearSearch}
docsLink={docsLink}
/>
);
}

return (
Expand Down
Loading