utils.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import type {Location, LocationDescriptorObject} from 'history';
  2. import {PAGE_URL_PARAM} from 'sentry/constants/pageFilters';
  3. import type {DateString} from 'sentry/types/core';
  4. import type {Organization} from 'sentry/types/organization';
  5. import {getTimeStampFromTableDateField} from 'sentry/utils/dates';
  6. import type {
  7. EventLite,
  8. TraceError,
  9. TraceFull,
  10. TraceFullDetailed,
  11. TraceSplitResults,
  12. } from 'sentry/utils/performance/quickTrace/types';
  13. import {isTraceSplitResult, reduceTrace} from 'sentry/utils/performance/quickTrace/utils';
  14. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  15. import type {DomainView} from 'sentry/views/insights/pages/useFilters';
  16. import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
  17. import {getPerformanceBaseUrl} from 'sentry/views/performance/utils';
  18. import {
  19. TRACE_SOURCE_TO_NON_INSIGHT_ROUTES,
  20. type TraceViewSources,
  21. } from '../newTraceDetails/traceHeader/breadcrumbs';
  22. import {DEFAULT_TRACE_ROWS_LIMIT} from './limitExceededMessage';
  23. import type {TraceInfo} from './types';
  24. function getBaseTraceUrl(
  25. organization: Organization,
  26. source?: TraceViewSources,
  27. view?: DomainView
  28. ) {
  29. if (view) {
  30. return getPerformanceBaseUrl(organization.slug, view);
  31. }
  32. const url =
  33. source && source in TRACE_SOURCE_TO_NON_INSIGHT_ROUTES
  34. ? TRACE_SOURCE_TO_NON_INSIGHT_ROUTES[source]
  35. : 'performance';
  36. return normalizeUrl(`/organizations/${organization.slug}/${url}`);
  37. }
  38. export function getTraceDetailsUrl({
  39. organization,
  40. traceSlug,
  41. dateSelection,
  42. timestamp,
  43. spanId,
  44. eventId,
  45. targetId,
  46. demo,
  47. location,
  48. source,
  49. view,
  50. }: {
  51. // @TODO add a type for dateSelection
  52. dateSelection: any;
  53. location: Location;
  54. organization: Organization;
  55. traceSlug: string;
  56. demo?: string;
  57. eventId?: string;
  58. source?: TraceViewSources;
  59. spanId?: string;
  60. // targetId represents the span id of the transaction. It will replace eventId once all links
  61. // to trace view are updated to use spand ids of transactions instead of event ids.
  62. targetId?: string;
  63. timestamp?: string | number;
  64. view?: DomainView;
  65. }): LocationDescriptorObject {
  66. const baseUrl = getBaseTraceUrl(organization, source, view);
  67. const queryParams: Record<string, string | number | undefined | DateString | string[]> =
  68. {
  69. ...location.query,
  70. statsPeriod: dateSelection.statsPeriod,
  71. [PAGE_URL_PARAM.PAGE_START]: dateSelection.start,
  72. [PAGE_URL_PARAM.PAGE_END]: dateSelection.end,
  73. };
  74. if (shouldForceRouteToOldView(organization, timestamp)) {
  75. return {
  76. pathname: normalizeUrl(`${baseUrl}/trace/${traceSlug}/`),
  77. query: queryParams,
  78. };
  79. }
  80. if (organization.features.includes('trace-view-v1')) {
  81. if (spanId) {
  82. const path: TraceTree.NodePath[] = [`span-${spanId}`, `txn-${targetId ?? eventId}`];
  83. queryParams.node = path;
  84. }
  85. return {
  86. pathname: normalizeUrl(`${baseUrl}/trace/${traceSlug}/`),
  87. query: {
  88. ...queryParams,
  89. timestamp: getTimeStampFromTableDateField(timestamp),
  90. eventId,
  91. targetId,
  92. demo,
  93. source,
  94. },
  95. };
  96. }
  97. if (organization.features.includes('trace-view-load-more')) {
  98. queryParams.limit = DEFAULT_TRACE_ROWS_LIMIT;
  99. }
  100. return {
  101. pathname: normalizeUrl(`${baseUrl}/trace/${traceSlug}/`),
  102. query: queryParams,
  103. };
  104. }
  105. /**
  106. * Single tenant, on-premise etc. users may not have span extraction enabled.
  107. *
  108. * This code can be removed at the time we're sure all STs have rolled out span extraction.
  109. */
  110. export function shouldForceRouteToOldView(
  111. organization: Organization,
  112. timestamp: string | number | undefined
  113. ) {
  114. const usableTimestamp = getTimeStampFromTableDateField(timestamp);
  115. if (!usableTimestamp) {
  116. // Timestamps must always be provided for the new view, if it doesn't exist, fall back to the old view.
  117. return true;
  118. }
  119. return (
  120. organization.extraOptions?.traces.checkSpanExtractionDate &&
  121. organization.extraOptions?.traces.spansExtractionDate > usableTimestamp
  122. );
  123. }
  124. function transactionVisitor() {
  125. return (accumulator: TraceInfo, event: TraceFullDetailed) => {
  126. for (const error of event.errors ?? []) {
  127. accumulator.errors.add(error.event_id);
  128. }
  129. for (const performanceIssue of event.performance_issues ?? []) {
  130. accumulator.performanceIssues.add(performanceIssue.event_id);
  131. }
  132. accumulator.transactions.add(event.event_id);
  133. accumulator.projects.add(event.project_slug);
  134. accumulator.startTimestamp = Math.min(
  135. accumulator.startTimestamp,
  136. event.start_timestamp
  137. );
  138. accumulator.endTimestamp = Math.max(accumulator.endTimestamp, event.timestamp);
  139. accumulator.maxGeneration = Math.max(accumulator.maxGeneration, event.generation);
  140. return accumulator;
  141. };
  142. }
  143. export function hasTraceData(
  144. traces: TraceFullDetailed[] | null | undefined,
  145. orphanErrors: TraceError[] | undefined
  146. ): boolean {
  147. return Boolean(
  148. (traces && traces.length > 0) || (orphanErrors && orphanErrors.length > 0)
  149. );
  150. }
  151. export function getTraceSplitResults<U extends TraceFullDetailed | TraceFull | EventLite>(
  152. trace: TraceSplitResults<U> | U[],
  153. organization: Organization
  154. ) {
  155. let transactions: U[] | undefined;
  156. let orphanErrors: TraceError[] | undefined;
  157. if (
  158. trace &&
  159. organization.features.includes('performance-tracing-without-performance') &&
  160. isTraceSplitResult<TraceSplitResults<U>, U[]>(trace)
  161. ) {
  162. orphanErrors = trace.orphan_errors;
  163. transactions = trace.transactions;
  164. }
  165. return {transactions, orphanErrors};
  166. }
  167. export function getTraceInfo(
  168. traces: TraceFullDetailed[] = [],
  169. orphanErrors: TraceError[] = []
  170. ) {
  171. const initial = {
  172. projects: new Set<string>(),
  173. errors: new Set<string>(),
  174. performanceIssues: new Set<string>(),
  175. transactions: new Set<string>(),
  176. startTimestamp: Number.MAX_SAFE_INTEGER,
  177. endTimestamp: 0,
  178. maxGeneration: 0,
  179. trailingOrphansCount: 0,
  180. };
  181. const transactionsInfo = traces.reduce(
  182. (info: TraceInfo, trace: TraceFullDetailed) =>
  183. reduceTrace<TraceInfo>(trace, transactionVisitor(), info),
  184. initial
  185. );
  186. // Accumulate orphan error information.
  187. return orphanErrors.reduce((accumulator: TraceInfo, event: TraceError) => {
  188. accumulator.errors.add(event.event_id);
  189. accumulator.trailingOrphansCount++;
  190. if (event.timestamp) {
  191. accumulator.startTimestamp = Math.min(accumulator.startTimestamp, event.timestamp);
  192. accumulator.endTimestamp = Math.max(accumulator.endTimestamp, event.timestamp);
  193. }
  194. return accumulator;
  195. }, transactionsInfo);
  196. }
  197. export function shortenErrorTitle(title: string): string {
  198. return title.split(':')[0]!;
  199. }
  200. export function isRootTransaction(trace: TraceFullDetailed): boolean {
  201. // Root transactions has no parent_span_id
  202. return trace.parent_span_id === null;
  203. }