|
@@ -2,16 +2,28 @@ import {Fragment} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
|
|
|
import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
|
|
|
+import useStacktraceLink from 'sentry/components/events/interfaces/frame/useStacktraceLink';
|
|
|
+import ExternalLink from 'sentry/components/links/externalLink';
|
|
|
import {t} from 'sentry/locale';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
-import {Frame, Project} from 'sentry/types';
|
|
|
+import {Project} from 'sentry/types';
|
|
|
+import {getIntegrationIcon, getIntegrationSourceUrl} from 'sentry/utils/integrationUtil';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+import useProjects from 'sentry/utils/useProjects';
|
|
|
+import {useEventDetails} from 'sentry/views/starfish/queries/useEventDetails';
|
|
|
|
|
|
interface Props {
|
|
|
- frame: Partial<Pick<Frame, 'absPath' | 'colNo' | 'function' | 'lineNo'>>;
|
|
|
- project?: Project;
|
|
|
+ frame: Parameters<typeof useStacktraceLink>[0]['frame'];
|
|
|
+ eventId?: string;
|
|
|
+ projectId?: string;
|
|
|
}
|
|
|
|
|
|
-export function StackTraceMiniFrame({frame, project}: Props) {
|
|
|
+export function StackTraceMiniFrame({frame, eventId, projectId}: Props) {
|
|
|
+ const {projects} = useProjects();
|
|
|
+ const project = projects.find(p => p.id === projectId);
|
|
|
+
|
|
|
+ const {data: event} = useEventDetails({eventId, projectSlug: project?.slug});
|
|
|
+
|
|
|
return (
|
|
|
<FrameContainer>
|
|
|
{project && (
|
|
@@ -19,7 +31,7 @@ export function StackTraceMiniFrame({frame, project}: Props) {
|
|
|
<ProjectAvatar project={project} size={16} />
|
|
|
</ProjectAvatarContainer>
|
|
|
)}
|
|
|
- {frame.absPath && <Emphasize>{frame?.absPath}</Emphasize>}
|
|
|
+ {frame.filename && <Emphasize>{frame?.filename}</Emphasize>}
|
|
|
{frame.function && (
|
|
|
<Fragment>
|
|
|
<Deemphasize> {t('in')} </Deemphasize>
|
|
@@ -32,6 +44,12 @@ export function StackTraceMiniFrame({frame, project}: Props) {
|
|
|
<Emphasize>{frame?.lineNo}</Emphasize>
|
|
|
</Fragment>
|
|
|
)}
|
|
|
+
|
|
|
+ {event && project && (
|
|
|
+ <PushRight>
|
|
|
+ <SourceCodeIntegrationLink event={event} project={project} frame={frame} />
|
|
|
+ </PushRight>
|
|
|
+ )}
|
|
|
</FrameContainer>
|
|
|
);
|
|
|
}
|
|
@@ -61,3 +79,59 @@ const Emphasize = styled('span')`
|
|
|
const Deemphasize = styled('span')`
|
|
|
color: ${p => p.theme.gray300};
|
|
|
`;
|
|
|
+
|
|
|
+const PushRight = styled('span')`
|
|
|
+ margin-left: auto;
|
|
|
+`;
|
|
|
+
|
|
|
+interface SourceCodeIntegrationLinkProps {
|
|
|
+ event: Parameters<typeof useStacktraceLink>[0]['event'];
|
|
|
+ frame: Parameters<typeof useStacktraceLink>[0]['frame'];
|
|
|
+ project: Project;
|
|
|
+}
|
|
|
+function SourceCodeIntegrationLink({
|
|
|
+ event,
|
|
|
+ project,
|
|
|
+ frame,
|
|
|
+}: SourceCodeIntegrationLinkProps) {
|
|
|
+ const organization = useOrganization();
|
|
|
+
|
|
|
+ const {data: match, isLoading} = useStacktraceLink({
|
|
|
+ event,
|
|
|
+ frame,
|
|
|
+ orgSlug: organization.slug,
|
|
|
+ projectSlug: project.slug,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (match && match.config && match.sourceUrl && frame.lineNo && !isLoading) {
|
|
|
+ return (
|
|
|
+ <DeemphasizedExternalLink
|
|
|
+ href={getIntegrationSourceUrl(
|
|
|
+ match.config.provider.key,
|
|
|
+ match.sourceUrl,
|
|
|
+ frame.lineNo
|
|
|
+ )}
|
|
|
+ openInNewTab
|
|
|
+ >
|
|
|
+ <StyledIconWrapper>
|
|
|
+ {getIntegrationIcon(match.config.provider.key, 'sm')}
|
|
|
+ </StyledIconWrapper>
|
|
|
+ {t('Open this line in %s', match.config.provider.name)}
|
|
|
+ </DeemphasizedExternalLink>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+const DeemphasizedExternalLink = styled(ExternalLink)`
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: ${space(0.75)};
|
|
|
+ color: ${p => p.theme.gray300};
|
|
|
+`;
|
|
|
+
|
|
|
+const StyledIconWrapper = styled('span')`
|
|
|
+ color: inherit;
|
|
|
+ line-height: 0;
|
|
|
+`;
|