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/project';
  9. import {getIntegrationIcon, getIntegrationSourceUrl} from 'sentry/utils/integrationUtil';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import useProjects from 'sentry/utils/useProjects';
  12. import {MODULE_DOC_LINK} from 'sentry/views/performance/database/settings';
  13. import {useEventDetails} from 'sentry/views/starfish/queries/useEventDetails';
  14. interface Props {
  15. frame: Parameters<typeof useStacktraceLink>[0]['frame'];
  16. eventId?: string;
  17. projectId?: string;
  18. }
  19. export function StackTraceMiniFrame({frame, eventId, projectId}: Props) {
  20. const {projects} = useProjects();
  21. const project = projects.find(p => p.id === projectId);
  22. const {data: event} = useEventDetails({eventId, projectSlug: project?.slug});
  23. return (
  24. <FrameContainer>
  25. {project && (
  26. <ProjectAvatarContainer>
  27. <ProjectAvatar project={project} size={16} />
  28. </ProjectAvatarContainer>
  29. )}
  30. {frame.filename && <Emphasize>{frame?.filename}</Emphasize>}
  31. {frame.function && (
  32. <Fragment>
  33. <Deemphasize> {t('in')} </Deemphasize>
  34. <Emphasize>{frame?.function}</Emphasize>
  35. </Fragment>
  36. )}
  37. {frame.lineNo && (
  38. <Fragment>
  39. <Deemphasize> {t('at line')} </Deemphasize>
  40. <Emphasize>{frame?.lineNo}</Emphasize>
  41. </Fragment>
  42. )}
  43. {event && project && (
  44. <PushRight>
  45. <SourceCodeIntegrationLink event={event} project={project} frame={frame} />
  46. </PushRight>
  47. )}
  48. </FrameContainer>
  49. );
  50. }
  51. export function MissingFrame() {
  52. return (
  53. <FrameContainer>
  54. <Deemphasize>
  55. {tct(
  56. 'Could not find query source in the selected date range. Learn more in our [documentation:documentation].',
  57. {
  58. documentation: <ExternalLink href={`${MODULE_DOC_LINK}#query-sources`} />,
  59. }
  60. )}
  61. </Deemphasize>
  62. </FrameContainer>
  63. );
  64. }
  65. export const FrameContainer = styled('div')`
  66. display: flex;
  67. flex-wrap: wrap;
  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. `;