stackTraceMiniFrame.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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 {useEventDetails} from 'sentry/views/insights/common/queries/useEventDetails';
  13. import {MODULE_DOC_LINK} from 'sentry/views/insights/database/settings';
  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. type MissingFrameProps = {
  52. system?: string;
  53. };
  54. export function MissingFrame({system}: MissingFrameProps) {
  55. const documentation = <ExternalLink href={`${MODULE_DOC_LINK}#query-sources`} />;
  56. const errorMessage =
  57. system === 'mongodb'
  58. ? tct(
  59. 'Query sources are not currently supported for MongoDB queries. Learn more in our [documentation:documentation].',
  60. {documentation}
  61. )
  62. : tct(
  63. 'Could not find query source in the selected date range. Learn more in our [documentation:documentation].',
  64. {documentation}
  65. );
  66. return (
  67. <FrameContainer>
  68. <Deemphasize>{errorMessage}</Deemphasize>
  69. </FrameContainer>
  70. );
  71. }
  72. export const FrameContainer = styled('div')`
  73. display: flex;
  74. flex-wrap: wrap;
  75. align-items: center;
  76. gap: ${space(0.5)};
  77. padding: ${space(1.5)} ${space(2)};
  78. font-family: ${p => p.theme.text.family};
  79. font-size: ${p => p.theme.fontSizeMedium};
  80. border-top: 1px solid ${p => p.theme.border};
  81. background: ${p => p.theme.surface200};
  82. `;
  83. const ProjectAvatarContainer = styled('div')`
  84. margin-right: ${space(1)};
  85. `;
  86. const Emphasize = styled('span')`
  87. color: ${p => p.theme.gray500};
  88. `;
  89. const Deemphasize = styled('span')`
  90. color: ${p => p.theme.gray300};
  91. `;
  92. const PushRight = styled('span')`
  93. margin-left: auto;
  94. `;
  95. interface SourceCodeIntegrationLinkProps {
  96. event: Parameters<typeof useStacktraceLink>[0]['event'];
  97. frame: Parameters<typeof useStacktraceLink>[0]['frame'];
  98. project: Project;
  99. }
  100. function SourceCodeIntegrationLink({
  101. event,
  102. project,
  103. frame,
  104. }: SourceCodeIntegrationLinkProps) {
  105. const organization = useOrganization();
  106. const {data: match, isPending} = useStacktraceLink({
  107. event,
  108. frame,
  109. orgSlug: organization.slug,
  110. projectSlug: project.slug,
  111. });
  112. if (match?.config && match.sourceUrl && frame.lineNo && !isPending) {
  113. return (
  114. <DeemphasizedExternalLink
  115. href={getIntegrationSourceUrl(
  116. match.config.provider.key,
  117. match.sourceUrl,
  118. frame.lineNo
  119. )}
  120. openInNewTab
  121. >
  122. <StyledIconWrapper>
  123. {getIntegrationIcon(match.config.provider.key, 'sm')}
  124. </StyledIconWrapper>
  125. {t('Open this line in %s', match.config.provider.name)}
  126. </DeemphasizedExternalLink>
  127. );
  128. }
  129. return null;
  130. }
  131. const DeemphasizedExternalLink = styled(ExternalLink)`
  132. display: flex;
  133. align-items: center;
  134. gap: ${space(0.75)};
  135. color: ${p => p.theme.gray300};
  136. `;
  137. const StyledIconWrapper = styled('span')`
  138. color: inherit;
  139. line-height: 0;
  140. `;