utils.tsx 7.6 KB

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