stackTraceMiniFrame.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  4. import useStacktraceLink from 'sentry/components/events/interfaces/frame/useStacktraceLink';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import {t, tct} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {Project} from 'sentry/types';
  9. import {getIntegrationIcon, getIntegrationSourceUrl} from 'sentry/utils/integrationUtil';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import useProjects from 'sentry/utils/useProjects';
  12. import {useEventDetails} from 'sentry/views/starfish/queries/useEventDetails';
  13. interface Props {
  14. frame: Parameters<typeof useStacktraceLink>[0]['frame'];
  15. eventId?: string;
  16. projectId?: string;
  17. }
  18. export function StackTraceMiniFrame({frame, eventId, projectId}: Props) {
  19. const {projects} = useProjects();
  20. const project = projects.find(p => p.id === projectId);
  21. const {data: event} = useEventDetails({eventId, projectSlug: project?.slug});
  22. return (
  23. <FrameContainer>
  24. {project && (
  25. <ProjectAvatarContainer>
  26. <ProjectAvatar project={project} size={16} />
  27. </ProjectAvatarContainer>
  28. )}
  29. {frame.filename && <Emphasize>{frame?.filename}</Emphasize>}
  30. {frame.function && (
  31. <Fragment>
  32. <Deemphasize> {t('in')} </Deemphasize>
  33. <Emphasize>{frame?.function}</Emphasize>
  34. </Fragment>
  35. )}
  36. {frame.lineNo && (
  37. <Fragment>
  38. <Deemphasize> {t('at line')} </Deemphasize>
  39. <Emphasize>{frame?.lineNo}</Emphasize>
  40. </Fragment>
  41. )}
  42. {event && project && (
  43. <PushRight>
  44. <SourceCodeIntegrationLink event={event} project={project} frame={frame} />
  45. </PushRight>
  46. )}
  47. </FrameContainer>
  48. );
  49. }
  50. export function MissingFrame() {
  51. return (
  52. <FrameContainer>
  53. <Deemphasize>
  54. {tct(
  55. 'Could not find query source in the selected date range. Learn more in our [documentation:documentation].',
  56. {
  57. documentation: (
  58. <ExternalLink href="https://docs.sentry.io/product/performance/queries/#query-sources" />
  59. ),
  60. }
  61. )}
  62. </Deemphasize>
  63. </FrameContainer>
  64. );
  65. }
  66. const FrameContainer = styled('div')`
  67. display: flex;
  68. align-items: center;
  69. gap: ${space(0.5)};
  70. padding: ${space(1.5)} ${space(2)};
  71. font-family: ${p => p.theme.text.family};
  72. font-size: ${p => p.theme.fontSizeMedium};
  73. border-top: 1px solid ${p => p.theme.border};
  74. background: ${p => p.theme.surface200};
  75. `;
  76. const ProjectAvatarContainer = styled('div')`
  77. margin-right: ${space(1)};
  78. `;
  79. const Emphasize = styled('span')`
  80. color: ${p => p.theme.gray500};
  81. `;
  82. const Deemphasize = styled('span')`
  83. color: ${p => p.theme.gray300};
  84. `;
  85. const PushRight = styled('span')`
  86. margin-left: auto;
  87. `;
  88. interface SourceCodeIntegrationLinkProps {
  89. event: Parameters<typeof useStacktraceLink>[0]['event'];
  90. frame: Parameters<typeof useStacktraceLink>[0]['frame'];
  91. project: Project;
  92. }
  93. function SourceCodeIntegrationLink({
  94. event,
  95. project,
  96. frame,
  97. }: SourceCodeIntegrationLinkProps) {
  98. const organization = useOrganization();
  99. const {data: match, isLoading} = useStacktraceLink({
  100. event,
  101. frame,
  102. orgSlug: organization.slug,
  103. projectSlug: project.slug,
  104. });
  105. if (match?.config && match.sourceUrl && frame.lineNo && !isLoading) {
  106. return (
  107. <DeemphasizedExternalLink
  108. href={getIntegrationSourceUrl(
  109. match.config.provider.key,
  110. match.sourceUrl,
  111. frame.lineNo
  112. )}
  113. openInNewTab
  114. >
  115. <StyledIconWrapper>
  116. {getIntegrationIcon(match.config.provider.key, 'sm')}
  117. </StyledIconWrapper>
  118. {t('Open this line in %s', match.config.provider.name)}
  119. </DeemphasizedExternalLink>
  120. );
  121. }
  122. return null;
  123. }
  124. const DeemphasizedExternalLink = styled(ExternalLink)`
  125. display: flex;
  126. align-items: center;
  127. gap: ${space(0.75)};
  128. color: ${p => p.theme.gray300};
  129. `;
  130. const StyledIconWrapper = styled('span')`
  131. color: inherit;
  132. line-height: 0;
  133. `;