utils.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {Location, LocationDescriptor, Query} from 'history';
  2. import Duration from 'app/components/duration';
  3. import {ALL_ACCESS_PROJECTS} from 'app/constants/globalSelectionHeader';
  4. import {backend, frontend, mobile} from 'app/data/platformCategories';
  5. import {GlobalSelection, OrganizationSummary, Project} from 'app/types';
  6. import {defined} from 'app/utils';
  7. import {statsPeriodToDays} from 'app/utils/dates';
  8. import EventView from 'app/utils/discover/eventView';
  9. import {getDuration} from 'app/utils/formatters';
  10. import getCurrentSentryReactTransaction from 'app/utils/getCurrentSentryReactTransaction';
  11. import {decodeScalar} from 'app/utils/queryString';
  12. import {MutableSearch} from 'app/utils/tokenizeSearch';
  13. /**
  14. * Performance type can used to determine a default view or which specific field should be used by default on pages
  15. * where we don't want to wait for transaction data to return to determine how to display aspects of a page.
  16. */
  17. export enum PROJECT_PERFORMANCE_TYPE {
  18. ANY = 'any', // Fallback to transaction duration
  19. FRONTEND = 'frontend',
  20. BACKEND = 'backend',
  21. FRONTEND_OTHER = 'frontend_other',
  22. MOBILE = 'mobile',
  23. }
  24. const FRONTEND_PLATFORMS: string[] = [...frontend];
  25. const BACKEND_PLATFORMS: string[] = [...backend];
  26. const MOBILE_PLATFORMS: string[] = [...mobile];
  27. export function platformToPerformanceType(
  28. projects: Project[],
  29. projectIds: readonly number[]
  30. ) {
  31. if (projectIds.length === 0 || projectIds[0] === ALL_ACCESS_PROJECTS) {
  32. return PROJECT_PERFORMANCE_TYPE.ANY;
  33. }
  34. const selectedProjects = projects.filter(p => projectIds.includes(parseInt(p.id, 10)));
  35. if (selectedProjects.length === 0 || selectedProjects.some(p => !p.platform)) {
  36. return PROJECT_PERFORMANCE_TYPE.ANY;
  37. }
  38. if (
  39. selectedProjects.every(project =>
  40. FRONTEND_PLATFORMS.includes(project.platform as string)
  41. )
  42. ) {
  43. return PROJECT_PERFORMANCE_TYPE.FRONTEND;
  44. }
  45. if (
  46. selectedProjects.every(project =>
  47. BACKEND_PLATFORMS.includes(project.platform as string)
  48. )
  49. ) {
  50. return PROJECT_PERFORMANCE_TYPE.BACKEND;
  51. }
  52. if (
  53. selectedProjects.every(project =>
  54. MOBILE_PLATFORMS.includes(project.platform as string)
  55. )
  56. ) {
  57. return PROJECT_PERFORMANCE_TYPE.MOBILE;
  58. }
  59. return PROJECT_PERFORMANCE_TYPE.ANY;
  60. }
  61. /**
  62. * Used for transaction summary to determine appropriate columns on a page, since there is no display field set for the page.
  63. */
  64. export function platformAndConditionsToPerformanceType(
  65. projects: Project[],
  66. eventView: EventView
  67. ) {
  68. const performanceType = platformToPerformanceType(projects, eventView.project);
  69. if (performanceType === PROJECT_PERFORMANCE_TYPE.FRONTEND) {
  70. const conditions = new MutableSearch(eventView.query);
  71. const ops = conditions.getFilterValues('!transaction.op');
  72. if (ops.some(op => op === 'pageload')) {
  73. return PROJECT_PERFORMANCE_TYPE.FRONTEND_OTHER;
  74. }
  75. }
  76. return performanceType;
  77. }
  78. /**
  79. * Used for transaction summary to check the view itself, since it can have conditions which would exclude it from having vitals aside from platform.
  80. */
  81. export function isSummaryViewFrontendPageLoad(eventView: EventView, projects: Project[]) {
  82. return (
  83. platformAndConditionsToPerformanceType(projects, eventView) ===
  84. PROJECT_PERFORMANCE_TYPE.FRONTEND
  85. );
  86. }
  87. export function isSummaryViewFrontend(eventView: EventView, projects: Project[]) {
  88. return (
  89. platformAndConditionsToPerformanceType(projects, eventView) ===
  90. PROJECT_PERFORMANCE_TYPE.FRONTEND ||
  91. platformAndConditionsToPerformanceType(projects, eventView) ===
  92. PROJECT_PERFORMANCE_TYPE.FRONTEND_OTHER
  93. );
  94. }
  95. export function getPerformanceLandingUrl(organization: OrganizationSummary): string {
  96. return `/organizations/${organization.slug}/performance/`;
  97. }
  98. export function getPerformanceTrendsUrl(organization: OrganizationSummary): string {
  99. return `/organizations/${organization.slug}/performance/trends/`;
  100. }
  101. export function getTransactionSearchQuery(location: Location, query: string = '') {
  102. return decodeScalar(location.query.query, query).trim();
  103. }
  104. export function getTransactionDetailsUrl(
  105. organization: OrganizationSummary,
  106. eventSlug: string,
  107. transaction: string,
  108. query: Query
  109. ): LocationDescriptor {
  110. return {
  111. pathname: `/organizations/${organization.slug}/performance/${eventSlug}/`,
  112. query: {
  113. ...query,
  114. transaction,
  115. },
  116. };
  117. }
  118. export function getTransactionComparisonUrl({
  119. organization,
  120. baselineEventSlug,
  121. regressionEventSlug,
  122. transaction,
  123. query,
  124. }: {
  125. organization: OrganizationSummary;
  126. baselineEventSlug: string;
  127. regressionEventSlug: string;
  128. transaction: string;
  129. query: Query;
  130. }): LocationDescriptor {
  131. return {
  132. pathname: `/organizations/${organization.slug}/performance/compare/${baselineEventSlug}/${regressionEventSlug}/`,
  133. query: {
  134. ...query,
  135. transaction,
  136. },
  137. };
  138. }
  139. export function addRoutePerformanceContext(selection: GlobalSelection) {
  140. const transaction = getCurrentSentryReactTransaction();
  141. const days = statsPeriodToDays(
  142. selection.datetime.period,
  143. selection.datetime.start,
  144. selection.datetime.end
  145. );
  146. const oneDay = 86400;
  147. const seconds = Math.floor(days * oneDay);
  148. transaction?.setTag('query.period', seconds.toString());
  149. let groupedPeriod = '>30d';
  150. if (seconds <= oneDay) groupedPeriod = '<=1d';
  151. else if (seconds <= oneDay * 7) groupedPeriod = '<=7d';
  152. else if (seconds <= oneDay * 14) groupedPeriod = '<=14d';
  153. else if (seconds <= oneDay * 30) groupedPeriod = '<=30d';
  154. transaction?.setTag('query.period.grouped', groupedPeriod);
  155. }
  156. export function getTransactionName(location: Location): string | undefined {
  157. const {transaction} = location.query;
  158. return decodeScalar(transaction);
  159. }
  160. type DurationProps = {abbreviation?: boolean};
  161. type SecondsProps = {seconds: number} & DurationProps;
  162. type MillisecondsProps = {milliseconds: number} & DurationProps;
  163. type PerformanceDurationProps = SecondsProps | MillisecondsProps;
  164. const hasMilliseconds = (props: PerformanceDurationProps): props is MillisecondsProps => {
  165. return defined((props as MillisecondsProps).milliseconds);
  166. };
  167. export function PerformanceDuration(props: SecondsProps);
  168. export function PerformanceDuration(props: MillisecondsProps);
  169. export function PerformanceDuration(props: PerformanceDurationProps) {
  170. const normalizedSeconds = hasMilliseconds(props)
  171. ? props.milliseconds / 1000
  172. : props.seconds;
  173. return (
  174. <Duration
  175. abbreviation={props.abbreviation}
  176. seconds={normalizedSeconds}
  177. fixedDigits={normalizedSeconds > 1 ? 2 : 0}
  178. />
  179. );
  180. }
  181. export function getPerformanceDuration(milliseconds: number) {
  182. return getDuration(milliseconds / 1000, milliseconds > 1000 ? 2 : 0, true);
  183. }