utils.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import type {PlainRoute} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import type {Location, LocationDescriptor, Query} from 'history';
  4. import {space} from 'sentry/styles/space';
  5. import type {Organization} from 'sentry/types/organization';
  6. import {getDateFromTimestamp} from 'sentry/utils/dates';
  7. import type {TableDataRow} from 'sentry/utils/discover/discoverQuery';
  8. import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
  9. import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
  10. import {
  11. generateContinuousProfileFlamechartRouteWithQuery,
  12. generateProfileFlamechartRoute,
  13. } from 'sentry/utils/profiling/routes';
  14. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  15. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  16. import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
  17. import {TraceViewSources} from '../newTraceDetails/traceMetadataHeader';
  18. export enum DisplayModes {
  19. DURATION_PERCENTILE = 'durationpercentile',
  20. DURATION = 'duration',
  21. LATENCY = 'latency',
  22. TREND = 'trend',
  23. VITALS = 'vitals',
  24. USER_MISERY = 'usermisery',
  25. }
  26. export enum TransactionFilterOptions {
  27. FASTEST = 'fastest',
  28. SLOW = 'slow',
  29. OUTLIER = 'outlier',
  30. RECENT = 'recent',
  31. }
  32. export function generateTransactionSummaryRoute({
  33. orgSlug,
  34. subPath,
  35. }: {
  36. orgSlug: string;
  37. subPath?: string;
  38. }): string {
  39. return `/organizations/${orgSlug}/performance/summary/${subPath ? `${subPath}/` : ''}`;
  40. }
  41. // normalizes search conditions by removing any redundant search conditions before presenting them in:
  42. // - query strings
  43. // - search UI
  44. export function normalizeSearchConditions(query: string): MutableSearch {
  45. const filterParams = normalizeSearchConditionsWithTransactionName(query);
  46. // no need to include transaction as its already in the query params
  47. filterParams.removeFilter('transaction');
  48. return filterParams;
  49. }
  50. // normalizes search conditions by removing any redundant search conditions, but retains any transaction name
  51. export function normalizeSearchConditionsWithTransactionName(
  52. query: string
  53. ): MutableSearch {
  54. const filterParams = new MutableSearch(query);
  55. // remove any event.type queries since it is implied to apply to only transactions
  56. filterParams.removeFilter('event.type');
  57. return filterParams;
  58. }
  59. export function transactionSummaryRouteWithQuery({
  60. orgSlug,
  61. transaction,
  62. projectID,
  63. query,
  64. unselectedSeries = ['p100()', 'avg()'],
  65. display,
  66. trendFunction,
  67. trendColumn,
  68. showTransactions,
  69. additionalQuery,
  70. subPath,
  71. }: {
  72. orgSlug: string;
  73. query: Query;
  74. transaction: string;
  75. additionalQuery?: Record<string, string | undefined>;
  76. display?: DisplayModes;
  77. projectID?: string | string[];
  78. showTransactions?: TransactionFilterOptions;
  79. subPath?: string;
  80. trendColumn?: string;
  81. trendFunction?: string;
  82. unselectedSeries?: string | string[];
  83. }) {
  84. const pathname = generateTransactionSummaryRoute({
  85. orgSlug,
  86. subPath,
  87. });
  88. let searchFilter: typeof query.query;
  89. if (typeof query.query === 'string') {
  90. searchFilter = normalizeSearchConditions(query.query).formatString();
  91. } else {
  92. searchFilter = query.query;
  93. }
  94. return {
  95. pathname,
  96. query: {
  97. transaction,
  98. project: projectID,
  99. environment: query.environment,
  100. statsPeriod: query.statsPeriod,
  101. start: query.start,
  102. end: query.end,
  103. query: searchFilter,
  104. unselectedSeries,
  105. showTransactions,
  106. display,
  107. trendFunction,
  108. trendColumn,
  109. referrer: 'performance-transaction-summary',
  110. ...additionalQuery,
  111. },
  112. };
  113. }
  114. export function generateTraceLink(dateSelection) {
  115. return (
  116. organization: Organization,
  117. tableRow: TableDataRow,
  118. location: Location
  119. ): LocationDescriptor => {
  120. const traceId = `${tableRow.trace}`;
  121. if (!traceId) {
  122. return {};
  123. }
  124. return getTraceDetailsUrl({
  125. organization,
  126. traceSlug: traceId,
  127. dateSelection,
  128. timestamp: tableRow.timestamp,
  129. location,
  130. source: TraceViewSources.PERFORMANCE_TRANSACTION_SUMMARY,
  131. });
  132. };
  133. }
  134. export function generateTransactionIdLink(transactionName?: string) {
  135. return (
  136. organization: Organization,
  137. tableRow: TableDataRow,
  138. location: Location,
  139. spanId?: string
  140. ): LocationDescriptor => {
  141. return generateLinkToEventInTraceView({
  142. eventId: tableRow.id,
  143. timestamp: tableRow.timestamp,
  144. traceSlug: tableRow.trace?.toString(),
  145. projectSlug: tableRow['project.name']?.toString(),
  146. location,
  147. organization,
  148. spanId,
  149. transactionName,
  150. source: TraceViewSources.PERFORMANCE_TRANSACTION_SUMMARY,
  151. });
  152. };
  153. }
  154. export function generateProfileLink() {
  155. return (
  156. organization: Organization,
  157. tableRow: TableDataRow,
  158. _location: Location | undefined
  159. ) => {
  160. const projectSlug = tableRow['project.name'];
  161. const profileId = tableRow['profile.id'];
  162. if (projectSlug && profileId) {
  163. return generateProfileFlamechartRoute({
  164. orgSlug: organization.slug,
  165. projectSlug: String(tableRow['project.name']),
  166. profileId: String(profileId),
  167. });
  168. }
  169. const profilerId = tableRow['profiler.id'];
  170. const threadId = tableRow['thread.id'];
  171. const start =
  172. typeof tableRow['precise.start_ts'] === 'number'
  173. ? getDateFromTimestamp(tableRow['precise.start_ts'] * 1000)
  174. : null;
  175. const finish =
  176. typeof tableRow['precise.finish_ts'] === 'number'
  177. ? getDateFromTimestamp(tableRow['precise.finish_ts'] * 1000)
  178. : null;
  179. if (projectSlug && profilerId && threadId && start && finish) {
  180. const query: Record<string, string> = {tid: String(threadId)};
  181. if (tableRow.id && tableRow.trace) {
  182. query.eventId = String(tableRow.id);
  183. query.traceId = String(tableRow.trace);
  184. }
  185. return generateContinuousProfileFlamechartRouteWithQuery({
  186. orgSlug: organization.slug,
  187. projectSlug: String(projectSlug),
  188. profilerId: String(profilerId),
  189. start: start.toISOString(),
  190. end: finish.toISOString(),
  191. query,
  192. });
  193. }
  194. return {};
  195. };
  196. }
  197. export function generateReplayLink(routes: PlainRoute<any>[]) {
  198. const referrer = getRouteStringFromRoutes(routes);
  199. return (
  200. organization: Organization,
  201. tableRow: TableDataRow,
  202. _location: Location | undefined
  203. ): LocationDescriptor => {
  204. const replayId = tableRow.replayId;
  205. if (!replayId) {
  206. return {};
  207. }
  208. if (!tableRow.timestamp) {
  209. return {
  210. pathname: normalizeUrl(
  211. `/organizations/${organization.slug}/replays/${replayId}/`
  212. ),
  213. query: {
  214. referrer,
  215. },
  216. };
  217. }
  218. const transactionTimestamp = new Date(tableRow.timestamp).getTime();
  219. const transactionStartTimestamp = tableRow['transaction.duration']
  220. ? transactionTimestamp - (tableRow['transaction.duration'] as number)
  221. : undefined;
  222. return {
  223. pathname: normalizeUrl(`/organizations/${organization.slug}/replays/${replayId}/`),
  224. query: {
  225. event_t: transactionStartTimestamp,
  226. referrer,
  227. },
  228. };
  229. };
  230. }
  231. export const SidebarSpacer = styled('div')`
  232. margin-top: ${space(3)};
  233. `;