index.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Component} from 'react';
  2. import * as ReactRouter from 'react-router';
  3. import styled from '@emotion/styled';
  4. import Alert from 'app/components/alert';
  5. import {Panel} from 'app/components/panels';
  6. import SearchBar from 'app/components/searchBar';
  7. import {IconWarning} from 'app/icons';
  8. import {t, tn} from 'app/locale';
  9. import space from 'app/styles/space';
  10. import {Organization} from 'app/types';
  11. import {EventTransaction} from 'app/types/event';
  12. import {objectIsEmpty} from 'app/utils';
  13. import * as QuickTraceContext from 'app/utils/performance/quickTrace/quickTraceContext';
  14. import withOrganization from 'app/utils/withOrganization';
  15. import * as AnchorLinkManager from './anchorLinkManager';
  16. import Filter, {
  17. ActiveOperationFilter,
  18. noFilter,
  19. toggleAllFilters,
  20. toggleFilter,
  21. } from './filter';
  22. import TraceView from './traceView';
  23. import {ParsedTraceType} from './types';
  24. import {parseTrace} from './utils';
  25. type Props = {
  26. event: EventTransaction;
  27. organization: Organization;
  28. } & ReactRouter.WithRouterProps;
  29. type State = {
  30. parsedTrace: ParsedTraceType;
  31. searchQuery: string | undefined;
  32. operationNameFilters: ActiveOperationFilter;
  33. };
  34. class SpansInterface extends Component<Props, State> {
  35. state: State = {
  36. searchQuery: undefined,
  37. parsedTrace: parseTrace(this.props.event),
  38. operationNameFilters: noFilter,
  39. };
  40. static getDerivedStateFromProps(props: Readonly<Props>, state: State): State {
  41. return {
  42. ...state,
  43. parsedTrace: parseTrace(props.event),
  44. };
  45. }
  46. handleSpanFilter = (searchQuery: string) => {
  47. this.setState({
  48. searchQuery: searchQuery || undefined,
  49. });
  50. };
  51. renderTraceErrorsAlert({
  52. isLoading,
  53. numOfErrors,
  54. }: {
  55. isLoading: boolean;
  56. numOfErrors: number;
  57. }) {
  58. if (isLoading) {
  59. return null;
  60. }
  61. if (numOfErrors === 0) {
  62. return null;
  63. }
  64. const label = tn(
  65. 'There is an error event associated with this transaction event.',
  66. `There are %s error events associated with this transaction event.`,
  67. numOfErrors
  68. );
  69. return (
  70. <AlertContainer>
  71. <Alert type="error" icon={<IconWarning size="md" />}>
  72. {label}
  73. </Alert>
  74. </AlertContainer>
  75. );
  76. }
  77. toggleOperationNameFilter = (operationName: string) => {
  78. this.setState(prevState => ({
  79. operationNameFilters: toggleFilter(prevState.operationNameFilters, operationName),
  80. }));
  81. };
  82. toggleAllOperationNameFilters = (operationNames: string[]) => {
  83. this.setState(prevState => {
  84. return {
  85. operationNameFilters: toggleAllFilters(
  86. prevState.operationNameFilters,
  87. operationNames
  88. ),
  89. };
  90. });
  91. };
  92. render() {
  93. const {event, organization} = this.props;
  94. const {parsedTrace} = this.state;
  95. return (
  96. <Container hasErrors={!objectIsEmpty(event.errors)}>
  97. <QuickTraceContext.Consumer>
  98. {quickTrace => (
  99. <AnchorLinkManager.Provider>
  100. {this.renderTraceErrorsAlert({
  101. isLoading: quickTrace?.isLoading || false,
  102. numOfErrors: quickTrace?.currentEvent?.errors?.length ?? 0,
  103. })}
  104. <Search>
  105. <Filter
  106. parsedTrace={parsedTrace}
  107. operationNameFilter={this.state.operationNameFilters}
  108. toggleOperationNameFilter={this.toggleOperationNameFilter}
  109. toggleAllOperationNameFilters={this.toggleAllOperationNameFilters}
  110. />
  111. <StyledSearchBar
  112. defaultQuery=""
  113. query={this.state.searchQuery || ''}
  114. placeholder={t('Search for spans')}
  115. onSearch={this.handleSpanFilter}
  116. />
  117. </Search>
  118. <Panel>
  119. <TraceView
  120. event={event}
  121. searchQuery={this.state.searchQuery}
  122. organization={organization}
  123. parsedTrace={parsedTrace}
  124. operationNameFilters={this.state.operationNameFilters}
  125. />
  126. </Panel>
  127. </AnchorLinkManager.Provider>
  128. )}
  129. </QuickTraceContext.Consumer>
  130. </Container>
  131. );
  132. }
  133. }
  134. const Container = styled('div')<{hasErrors: boolean}>`
  135. ${p =>
  136. p.hasErrors &&
  137. `
  138. padding: ${space(2)} 0;
  139. @media (min-width: ${p.theme.breakpoints[0]}) {
  140. padding: ${space(3)} 0 0 0;
  141. }
  142. `}
  143. `;
  144. const Search = styled('div')`
  145. display: flex;
  146. width: 100%;
  147. margin-bottom: ${space(1)};
  148. `;
  149. const StyledSearchBar = styled(SearchBar)`
  150. flex-grow: 1;
  151. `;
  152. const AlertContainer = styled('div')`
  153. margin-bottom: ${space(1)};
  154. `;
  155. export default ReactRouter.withRouter(withOrganization(SpansInterface));