index.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import {PureComponent} from 'react';
  2. // eslint-disable-next-line no-restricted-imports
  3. import {withRouter, WithRouterProps} from 'react-router';
  4. import styled from '@emotion/styled';
  5. import {Observer} from 'mobx-react';
  6. import Alert from 'sentry/components/alert';
  7. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  8. import {Panel} from 'sentry/components/panels';
  9. import SearchBar from 'sentry/components/searchBar';
  10. import {t, tn} from 'sentry/locale';
  11. import space from 'sentry/styles/space';
  12. import {Organization} from 'sentry/types';
  13. import {EventTransaction} from 'sentry/types/event';
  14. import {objectIsEmpty} from 'sentry/utils';
  15. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  16. import * as QuickTraceContext from 'sentry/utils/performance/quickTrace/quickTraceContext';
  17. import {TraceError} from 'sentry/utils/performance/quickTrace/types';
  18. import withOrganization from 'sentry/utils/withOrganization';
  19. import * as AnchorLinkManager from './anchorLinkManager';
  20. import Filter from './filter';
  21. import TraceErrorList from './traceErrorList';
  22. import TraceView from './traceView';
  23. import {FocusedSpanIDMap, ParsedTraceType} from './types';
  24. import {getCumulativeAlertLevelFromErrors, parseTrace, scrollToSpan} from './utils';
  25. import WaterfallModel from './waterfallModel';
  26. type Props = {
  27. event: EventTransaction;
  28. organization: Organization;
  29. focusedSpanIds?: FocusedSpanIDMap;
  30. } & WithRouterProps;
  31. type State = {
  32. parsedTrace: ParsedTraceType;
  33. waterfallModel: WaterfallModel;
  34. };
  35. class SpansInterface extends PureComponent<Props, State> {
  36. state: State = {
  37. parsedTrace: parseTrace(this.props.event),
  38. waterfallModel: new WaterfallModel(this.props.event, this.props.focusedSpanIds),
  39. };
  40. static getDerivedStateFromProps(props: Readonly<Props>, state: State): State {
  41. if (state.waterfallModel.isEvent(props.event)) {
  42. return state;
  43. }
  44. return {
  45. ...state,
  46. parsedTrace: parseTrace(props.event),
  47. waterfallModel: new WaterfallModel(props.event, props.focusedSpanIds),
  48. };
  49. }
  50. handleSpanFilter = (searchQuery: string) => {
  51. const {waterfallModel} = this.state;
  52. const {organization} = this.props;
  53. waterfallModel.querySpanSearch(searchQuery);
  54. trackAdvancedAnalyticsEvent('performance_views.event_details.search_query', {
  55. organization,
  56. });
  57. };
  58. renderTraceErrorsAlert({
  59. isLoading,
  60. errors,
  61. parsedTrace,
  62. }: {
  63. errors: TraceError[] | undefined;
  64. isLoading: boolean;
  65. parsedTrace: ParsedTraceType;
  66. }) {
  67. if (isLoading) {
  68. return null;
  69. }
  70. if (!errors || errors.length <= 0) {
  71. return null;
  72. }
  73. // This is intentional as unbalanced string formatters in `tn()` are problematic
  74. const label =
  75. errors.length === 1
  76. ? t('There is an error event associated with this transaction event.')
  77. : tn(
  78. `There are %s error events associated with this transaction event.`,
  79. `There are %s error events associated with this transaction event.`,
  80. errors.length
  81. );
  82. return (
  83. <AlertContainer>
  84. <Alert type={getCumulativeAlertLevelFromErrors(errors)}>
  85. <ErrorLabel>{label}</ErrorLabel>
  86. <AnchorLinkManager.Consumer>
  87. {({scrollToHash}) => (
  88. <TraceErrorList
  89. trace={parsedTrace}
  90. errors={errors}
  91. onClickSpan={(event, spanId) => {
  92. return scrollToSpan(
  93. spanId,
  94. scrollToHash,
  95. this.props.location,
  96. this.props.organization
  97. )(event);
  98. }}
  99. />
  100. )}
  101. </AnchorLinkManager.Consumer>
  102. </Alert>
  103. </AlertContainer>
  104. );
  105. }
  106. render() {
  107. const {event, organization} = this.props;
  108. const {parsedTrace, waterfallModel} = this.state;
  109. return (
  110. <Container hasErrors={!objectIsEmpty(event.errors)}>
  111. <QuickTraceContext.Consumer>
  112. {quickTrace => (
  113. <AnchorLinkManager.Provider>
  114. {this.renderTraceErrorsAlert({
  115. isLoading: quickTrace?.isLoading || false,
  116. errors: quickTrace?.currentEvent?.errors,
  117. parsedTrace,
  118. })}
  119. <Observer>
  120. {() => {
  121. return (
  122. <Search>
  123. <Filter
  124. operationNameCounts={waterfallModel.operationNameCounts}
  125. operationNameFilter={waterfallModel.operationNameFilters}
  126. toggleOperationNameFilter={
  127. waterfallModel.toggleOperationNameFilter
  128. }
  129. />
  130. <StyledSearchBar
  131. defaultQuery=""
  132. query={waterfallModel.searchQuery || ''}
  133. placeholder={t('Search for spans')}
  134. onSearch={this.handleSpanFilter}
  135. />
  136. </Search>
  137. );
  138. }}
  139. </Observer>
  140. <Panel>
  141. <Observer>
  142. {() => {
  143. return (
  144. <TraceView
  145. waterfallModel={waterfallModel}
  146. organization={organization}
  147. />
  148. );
  149. }}
  150. </Observer>
  151. <GuideAnchorWrapper>
  152. <GuideAnchor target="span_tree" position="bottom" />
  153. </GuideAnchorWrapper>
  154. </Panel>
  155. </AnchorLinkManager.Provider>
  156. )}
  157. </QuickTraceContext.Consumer>
  158. </Container>
  159. );
  160. }
  161. }
  162. const GuideAnchorWrapper = styled('div')`
  163. height: 0;
  164. width: 0;
  165. margin-left: 50%;
  166. `;
  167. const Container = styled('div')<{hasErrors: boolean}>`
  168. ${p =>
  169. p.hasErrors &&
  170. `
  171. padding: ${space(2)} 0;
  172. @media (min-width: ${p.theme.breakpoints.small}) {
  173. padding: ${space(3)} 0 0 0;
  174. }
  175. `}
  176. `;
  177. const Search = styled('div')`
  178. display: grid;
  179. gap: ${space(2)};
  180. grid-template-columns: max-content 1fr;
  181. width: 100%;
  182. margin-bottom: ${space(2)};
  183. `;
  184. const StyledSearchBar = styled(SearchBar)`
  185. flex-grow: 1;
  186. `;
  187. const AlertContainer = styled('div')`
  188. margin-bottom: ${space(1)};
  189. `;
  190. const ErrorLabel = styled('div')`
  191. margin-bottom: ${space(1)};
  192. `;
  193. export default withRouter(withOrganization(SpansInterface));