utils.tsx 6.9 KB

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