aggregateSpanDiff.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {Location} from 'history';
  2. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  3. import {DataSection} from 'sentry/components/events/styles';
  4. import GridEditable, {
  5. COL_WIDTH_UNDEFINED,
  6. GridColumnOrder,
  7. } from 'sentry/components/gridEditable';
  8. import Link from 'sentry/components/links/link';
  9. import LoadingIndicator from 'sentry/components/loadingIndicator';
  10. import TextOverflow from 'sentry/components/textOverflow';
  11. import {Tooltip} from 'sentry/components/tooltip';
  12. import {t} from 'sentry/locale';
  13. import {Event, Organization} from 'sentry/types';
  14. import {defined} from 'sentry/utils';
  15. import {NumericChange, renderHeadCell} from 'sentry/utils/performance/regression/table';
  16. import {useRelativeDateTime} from 'sentry/utils/profiling/hooks/useRelativeDateTime';
  17. import {useApiQuery} from 'sentry/utils/queryClient';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import {spanDetailsRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionSpans/spanDetails/utils';
  21. interface SpanDiff {
  22. p95_after: number;
  23. p95_before: number;
  24. score: number;
  25. span_description: string;
  26. span_group: string;
  27. span_op: string;
  28. spm_after: number;
  29. spm_before: number;
  30. }
  31. interface UseFetchAdvancedAnalysisProps {
  32. breakpoint: string;
  33. end: string;
  34. projectId: string;
  35. start: string;
  36. transaction: string;
  37. }
  38. interface RenderBodyCellProps {
  39. column: GridColumnOrder<string>;
  40. end: string;
  41. location: Location;
  42. organization: Organization;
  43. projectId: string;
  44. row: SpanDiff;
  45. start: string;
  46. transaction: string;
  47. }
  48. function useFetchAdvancedAnalysis({
  49. transaction,
  50. start,
  51. end,
  52. breakpoint,
  53. projectId,
  54. }: UseFetchAdvancedAnalysisProps) {
  55. const organization = useOrganization();
  56. return useApiQuery<SpanDiff[]>(
  57. [
  58. `/organizations/${organization.slug}/events-root-cause-analysis/`,
  59. {
  60. query: {
  61. transaction,
  62. project: projectId,
  63. start,
  64. end,
  65. breakpoint,
  66. per_page: 10,
  67. },
  68. },
  69. ],
  70. {
  71. staleTime: 60000,
  72. retry: false,
  73. }
  74. );
  75. }
  76. function getColumns() {
  77. return [
  78. {key: 'span_op', name: t('Span Operation'), width: 200},
  79. {key: 'span_description', name: t('Description'), width: COL_WIDTH_UNDEFINED},
  80. {key: 'spm', name: t('Span Frequency'), width: COL_WIDTH_UNDEFINED},
  81. {key: 'p95', name: t('P95'), width: COL_WIDTH_UNDEFINED},
  82. ];
  83. }
  84. function renderBodyCell({
  85. column,
  86. row,
  87. organization,
  88. transaction,
  89. projectId,
  90. location,
  91. start,
  92. end,
  93. }: RenderBodyCellProps) {
  94. if (column.key === 'span_description') {
  95. const label = row[column.key] || t('unnamed span');
  96. return (
  97. <Tooltip title={label} showOnlyOnOverflow>
  98. <TextOverflow>
  99. <Link
  100. to={spanDetailsRouteWithQuery({
  101. orgSlug: organization.slug,
  102. spanSlug: {op: row.span_op, group: row.span_group},
  103. transaction,
  104. projectID: projectId,
  105. query: {
  106. ...location.query,
  107. statsPeriod: undefined,
  108. query: undefined,
  109. start,
  110. end,
  111. },
  112. })}
  113. >
  114. {label}
  115. </Link>
  116. </TextOverflow>
  117. </Tooltip>
  118. );
  119. }
  120. if (['p95', 'spm'].includes(column.key)) {
  121. const beforeRawValue = row[`${column.key}_before`];
  122. const afterRawValue = row[`${column.key}_after`];
  123. return (
  124. <NumericChange
  125. columnKey={column.key}
  126. beforeRawValue={beforeRawValue}
  127. afterRawValue={afterRawValue}
  128. />
  129. );
  130. }
  131. return row[column.key];
  132. }
  133. function AggregateSpanDiff({event, projectId}: {event: Event; projectId: string}) {
  134. const location = useLocation();
  135. const organization = useOrganization();
  136. const {transaction, breakpoint} = event?.occurrence?.evidenceData ?? {};
  137. const breakpointTimestamp = new Date(breakpoint * 1000).toISOString();
  138. const {start, end} = useRelativeDateTime({
  139. anchor: breakpoint,
  140. relativeDays: 7,
  141. });
  142. const {data, isLoading, isError} = useFetchAdvancedAnalysis({
  143. transaction,
  144. start: (start as Date).toISOString(),
  145. end: (end as Date).toISOString(),
  146. breakpoint: breakpointTimestamp,
  147. projectId,
  148. });
  149. if (isLoading) {
  150. return <LoadingIndicator />;
  151. }
  152. let content;
  153. if (isError) {
  154. content = (
  155. <EmptyStateWarning>
  156. <p>{t('Oops! Something went wrong fetching span diffs')}</p>
  157. </EmptyStateWarning>
  158. );
  159. } else if (!defined(data) || data.length === 0) {
  160. content = (
  161. <EmptyStateWarning>
  162. <p>{t('Unable to find significant differences in spans')}</p>
  163. </EmptyStateWarning>
  164. );
  165. } else {
  166. content = (
  167. <GridEditable
  168. isLoading={isLoading}
  169. data={data}
  170. location={location}
  171. columnOrder={getColumns()}
  172. columnSortBy={[]}
  173. grid={{
  174. renderHeadCell,
  175. renderBodyCell: (column, row) =>
  176. renderBodyCell({
  177. column,
  178. row,
  179. organization,
  180. transaction,
  181. projectId,
  182. location,
  183. start: (start as Date).toISOString(),
  184. end: (end as Date).toISOString(),
  185. }),
  186. }}
  187. />
  188. );
  189. }
  190. return (
  191. <DataSection>
  192. <strong>{t('Span Analysis:')}</strong>
  193. {content}
  194. </DataSection>
  195. );
  196. }
  197. export default AggregateSpanDiff;