utils.tsx 6.5 KB

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