context.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import keyBy from 'lodash/keyBy';
  4. import ClippedBox from 'sentry/components/clippedBox';
  5. import ErrorBoundary from 'sentry/components/errorBoundary';
  6. import {StacktraceLink} from 'sentry/components/events/interfaces/frame/stacktraceLink';
  7. import {IconFlag} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import {
  11. CodecovStatusCode,
  12. Coverage,
  13. Frame,
  14. LineCoverage,
  15. Organization,
  16. SentryAppComponent,
  17. } from 'sentry/types';
  18. import {Event} from 'sentry/types/event';
  19. import {defined} from 'sentry/utils';
  20. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  21. import useProjects from 'sentry/utils/useProjects';
  22. import withOrganization from 'sentry/utils/withOrganization';
  23. import {parseAssembly} from '../utils';
  24. import {Assembly} from './assembly';
  25. import ContextLine from './contextLine';
  26. import {FrameRegisters} from './frameRegisters';
  27. import {FrameVariables} from './frameVariables';
  28. import {OpenInContextLine} from './openInContextLine';
  29. import useStacktraceLink from './useStacktraceLink';
  30. type Props = {
  31. components: Array<SentryAppComponent>;
  32. event: Event;
  33. frame: Frame;
  34. registers: {[key: string]: string};
  35. className?: string;
  36. emptySourceNotation?: boolean;
  37. expandable?: boolean;
  38. frameMeta?: Record<any, any>;
  39. hasAssembly?: boolean;
  40. hasContextRegisters?: boolean;
  41. hasContextSource?: boolean;
  42. hasContextVars?: boolean;
  43. isExpanded?: boolean;
  44. organization?: Organization;
  45. registersMeta?: Record<any, any>;
  46. };
  47. export function getCoverageColorClass(
  48. lines: [number, string][],
  49. lineCov: LineCoverage[],
  50. activeLineNo: number
  51. ): [Array<string>, boolean] {
  52. const lineCoverage = keyBy(lineCov, 0);
  53. let hasCoverage = false;
  54. const lineColors = lines.map(([lineNo]) => {
  55. const coverage = lineCoverage[lineNo]
  56. ? lineCoverage[lineNo][1]
  57. : Coverage.NOT_APPLICABLE;
  58. let color = '';
  59. switch (coverage) {
  60. case Coverage.COVERED:
  61. color = 'covered';
  62. break;
  63. case Coverage.NOT_COVERED:
  64. color = 'uncovered';
  65. break;
  66. case Coverage.PARTIAL:
  67. color = 'partial';
  68. break;
  69. case Coverage.NOT_APPLICABLE:
  70. // fallthrough
  71. default:
  72. break;
  73. }
  74. if (color !== '') {
  75. hasCoverage = true;
  76. }
  77. if (activeLineNo !== lineNo) {
  78. return color;
  79. }
  80. return color === '' ? 'active' : `active ${color}`;
  81. });
  82. return [lineColors, hasCoverage];
  83. }
  84. const Context = ({
  85. hasContextVars = false,
  86. hasContextSource = false,
  87. hasContextRegisters = false,
  88. isExpanded = false,
  89. hasAssembly = false,
  90. expandable = false,
  91. emptySourceNotation = false,
  92. registers,
  93. components,
  94. frame,
  95. event,
  96. organization,
  97. className,
  98. frameMeta,
  99. registersMeta,
  100. }: Props) => {
  101. const {projects} = useProjects();
  102. const project = useMemo(
  103. () => projects.find(p => p.id === event.projectID),
  104. [projects, event]
  105. );
  106. const {data, isLoading} = useStacktraceLink(
  107. {
  108. event,
  109. frame,
  110. orgSlug: organization?.slug || '',
  111. projectSlug: project?.slug,
  112. },
  113. {
  114. enabled:
  115. defined(organization) &&
  116. defined(project) &&
  117. !!organization.codecovAccess &&
  118. organization.features.includes('codecov-stacktrace-integration') &&
  119. isExpanded,
  120. }
  121. );
  122. const contextLines = isExpanded
  123. ? frame?.context
  124. : frame?.context?.filter(l => l[0] === frame.lineNo);
  125. const hasCoverageData =
  126. !isLoading && data?.codecov?.status === CodecovStatusCode.COVERAGE_EXISTS;
  127. const [lineColors = [], hasCoverage] =
  128. hasCoverageData && data!.codecov?.lineCoverage && !!frame.lineNo! && contextLines
  129. ? getCoverageColorClass(contextLines, data!.codecov?.lineCoverage, frame.lineNo)
  130. : [];
  131. useRouteAnalyticsParams(
  132. hasCoverageData
  133. ? {
  134. has_line_coverage: hasCoverage,
  135. }
  136. : {}
  137. );
  138. if (!hasContextSource && !hasContextVars && !hasContextRegisters && !hasAssembly) {
  139. return emptySourceNotation ? (
  140. <div className="empty-context">
  141. <StyledIconFlag size="xs" />
  142. <p>{t('No additional details are available for this frame.')}</p>
  143. </div>
  144. ) : null;
  145. }
  146. const startLineNo = hasContextSource ? frame.context[0][0] : 0;
  147. const hasStacktraceLink =
  148. frame.inApp &&
  149. !!frame.filename &&
  150. isExpanded &&
  151. organization?.features.includes('integrations-stacktrace-link');
  152. return (
  153. <Wrapper
  154. start={startLineNo}
  155. startLineNo={startLineNo}
  156. className={`${className} context ${isExpanded ? 'expanded' : ''}`}
  157. >
  158. {defined(frame.errors) && (
  159. <li className={expandable ? 'expandable error' : 'error'} key="errors">
  160. {frame.errors.join(', ')}
  161. </li>
  162. )}
  163. {frame.context &&
  164. contextLines.map((line, index) => {
  165. const isActive = frame.lineNo === line[0];
  166. const hasComponents = isActive && components.length > 0;
  167. const showStacktraceLink = hasStacktraceLink && isActive;
  168. return (
  169. <StyledContextLine
  170. key={index}
  171. line={line}
  172. isActive={isActive}
  173. colorClass={lineColors[index] ?? ''}
  174. >
  175. {hasComponents && (
  176. <ErrorBoundary mini>
  177. <OpenInContextLine
  178. key={index}
  179. lineNo={line[0]}
  180. filename={frame.filename || ''}
  181. components={components}
  182. />
  183. </ErrorBoundary>
  184. )}
  185. {showStacktraceLink && (
  186. <ErrorBoundary customComponent={null}>
  187. <StacktraceLink
  188. key={index}
  189. line={line[1]}
  190. frame={frame}
  191. event={event}
  192. />
  193. </ErrorBoundary>
  194. )}
  195. </StyledContextLine>
  196. );
  197. })}
  198. {hasContextVars && (
  199. <StyledClippedBox clipHeight={100}>
  200. <FrameVariables data={frame.vars ?? {}} meta={frameMeta?.vars} />
  201. </StyledClippedBox>
  202. )}
  203. {hasContextRegisters && (
  204. <FrameRegisters
  205. registers={registers}
  206. meta={registersMeta}
  207. deviceArch={event.contexts?.device?.arch}
  208. />
  209. )}
  210. {hasAssembly && (
  211. <Assembly {...parseAssembly(frame.package)} filePath={frame.absPath} />
  212. )}
  213. </Wrapper>
  214. );
  215. };
  216. export default withOrganization(Context);
  217. const StyledClippedBox = styled(ClippedBox)`
  218. padding: 0;
  219. `;
  220. const StyledIconFlag = styled(IconFlag)`
  221. margin-right: ${space(1)};
  222. `;
  223. const StyledContextLine = styled(ContextLine)`
  224. background: inherit;
  225. z-index: 1000;
  226. list-style: none;
  227. &::marker {
  228. content: none;
  229. }
  230. &:before {
  231. content: counter(frame);
  232. counter-increment: frame;
  233. text-align: center;
  234. padding-left: ${space(3)};
  235. padding-right: ${space(1.5)};
  236. margin-right: ${space(1.5)};
  237. display: inline-block;
  238. height: 24px;
  239. background: transparent;
  240. z-index: 1;
  241. min-width: 58px;
  242. border-right-style: solid;
  243. border-right-color: transparent;
  244. }
  245. &.covered:before {
  246. background: ${p => p.theme.green100};
  247. border-right-color: ${p => p.theme.green300};
  248. }
  249. &.uncovered:before {
  250. background: ${p => p.theme.red100};
  251. }
  252. &.partial:before {
  253. background: ${p => p.theme.yellow100};
  254. border-right-style: dashed;
  255. border-right-color: ${p => p.theme.yellow300};
  256. }
  257. &.active {
  258. background: ${p => p.theme.stacktraceActiveBackground};
  259. color: ${p => p.theme.stacktraceActiveText};
  260. }
  261. &.active.partial:before {
  262. mix-blend-mode: screen;
  263. background: ${p => p.theme.yellow200};
  264. }
  265. &.active.covered:before {
  266. mix-blend-mode: screen;
  267. background: ${p => p.theme.green200};
  268. }
  269. &.active.uncovered:before {
  270. mix-blend-mode: screen;
  271. background: ${p => p.theme.red200};
  272. }
  273. `;
  274. const Wrapper = styled('ol')<{startLineNo: number}>`
  275. counter-reset: frame ${p => p.startLineNo - 1};
  276. && {
  277. border-radius: 0 !important;
  278. }
  279. `;