utils.tsx 8.2 KB

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