stackTraceMiniFrame.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. export const FrameContainer = styled('div')`
  67. display: flex;
  68. flex-wrap: wrap;
  69. align-items: center;
  70. gap: ${space(0.5)};
  71. padding: ${space(1.5)} ${space(2)};
  72. font-family: ${p => p.theme.text.family};
  73. font-size: ${p => p.theme.fontSizeMedium};
  74. border-top: 1px solid ${p => p.theme.border};
  75. background: ${p => p.theme.surface200};
  76. `;
  77. const ProjectAvatarContainer = styled('div')`
  78. margin-right: ${space(1)};
  79. `;
  80. const Emphasize = styled('span')`
  81. color: ${p => p.theme.gray500};
  82. `;
  83. const Deemphasize = styled('span')`
  84. color: ${p => p.theme.gray300};
  85. `;
  86. const PushRight = styled('span')`
  87. margin-left: auto;
  88. `;
  89. interface SourceCodeIntegrationLinkProps {
  90. event: Parameters<typeof useStacktraceLink>[0]['event'];
  91. frame: Parameters<typeof useStacktraceLink>[0]['frame'];
  92. project: Project;
  93. }
  94. function SourceCodeIntegrationLink({
  95. event,
  96. project,
  97. frame,
  98. }: SourceCodeIntegrationLinkProps) {
  99. const organization = useOrganization();
  100. const {data: match, isLoading} = useStacktraceLink({
  101. event,
  102. frame,
  103. orgSlug: organization.slug,
  104. projectSlug: project.slug,
  105. });
  106. if (match?.config && match.sourceUrl && frame.lineNo && !isLoading) {
  107. return (
  108. <DeemphasizedExternalLink
  109. href={getIntegrationSourceUrl(
  110. match.config.provider.key,
  111. match.sourceUrl,
  112. frame.lineNo
  113. )}
  114. openInNewTab
  115. >
  116. <StyledIconWrapper>
  117. {getIntegrationIcon(match.config.provider.key, 'sm')}
  118. </StyledIconWrapper>
  119. {t('Open this line in %s', match.config.provider.name)}
  120. </DeemphasizedExternalLink>
  121. );
  122. }
  123. return null;
  124. }
  125. const DeemphasizedExternalLink = styled(ExternalLink)`
  126. display: flex;
  127. align-items: center;
  128. gap: ${space(0.75)};
  129. color: ${p => p.theme.gray300};
  130. `;
  131. const StyledIconWrapper = styled('span')`
  132. color: inherit;
  133. line-height: 0;
  134. `;