utils.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import type {Location} from 'history';
  2. import omit from 'lodash/omit';
  3. import {t} from 'sentry/locale';
  4. import type {Organization} from 'sentry/types/organization';
  5. import type {Project} from 'sentry/types/project';
  6. import {trackAnalytics} from 'sentry/utils/analytics';
  7. import {browserHistory} from 'sentry/utils/browserHistory';
  8. import type EventView from 'sentry/utils/discover/eventView';
  9. import getDuration from 'sentry/utils/duration/getDuration';
  10. import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
  11. import {formatFloat} from 'sentry/utils/number/formatFloat';
  12. import {formatPercentage} from 'sentry/utils/number/formatPercentage';
  13. import type {HistogramData} from 'sentry/utils/performance/histogram/types';
  14. import {decodeScalar} from 'sentry/utils/queryString';
  15. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  16. import type {AxisOption} from '../data';
  17. import {getTermHelp, PerformanceTerm} from '../data';
  18. import type {Rectangle} from '../transactionSummary/transactionVitals/types';
  19. import {platformToPerformanceType, ProjectPerformanceType} from '../utils';
  20. export const LEFT_AXIS_QUERY_KEY = 'left';
  21. export const RIGHT_AXIS_QUERY_KEY = 'right';
  22. type LandingDisplay = {
  23. field: LandingDisplayField;
  24. label: string;
  25. };
  26. export enum LandingDisplayField {
  27. ALL = 'all',
  28. FRONTEND_PAGELOAD = 'frontend_pageload',
  29. FRONTEND_OTHER = 'frontend_other',
  30. BACKEND = 'backend',
  31. MOBILE = 'mobile',
  32. }
  33. // TODO Abdullah Khan: Remove code for Web Vitals tab in performance landing
  34. // page when new starfish web vitals module is mature.
  35. export const LANDING_DISPLAYS = [
  36. {
  37. label: t('All Transactions'),
  38. field: LandingDisplayField.ALL,
  39. },
  40. {
  41. label: t('Frontend'),
  42. field: LandingDisplayField.FRONTEND_OTHER,
  43. },
  44. {
  45. label: t('Backend'),
  46. field: LandingDisplayField.BACKEND,
  47. },
  48. {
  49. label: t('Mobile'),
  50. field: LandingDisplayField.MOBILE,
  51. },
  52. ];
  53. export function excludeTransaction(
  54. transaction: string | React.ReactText,
  55. props: {eventView: EventView; location: Location}
  56. ) {
  57. const {eventView, location} = props;
  58. const searchConditions = new MutableSearch(eventView.query);
  59. searchConditions.addFilterValues('!transaction', [`${transaction}`]);
  60. browserHistory.push({
  61. pathname: location.pathname,
  62. query: {
  63. ...location.query,
  64. cursor: undefined,
  65. query: searchConditions.formatString(),
  66. },
  67. });
  68. }
  69. export function getLandingDisplayFromParam(location: Location) {
  70. const landingField = decodeScalar(location?.query?.landingDisplay);
  71. const display = LANDING_DISPLAYS.find(({field}) => field === landingField);
  72. return display;
  73. }
  74. export function getDefaultDisplayForPlatform(projects: Project[], eventView?: EventView) {
  75. const defaultDisplayField = getDefaultDisplayFieldForPlatform(projects, eventView);
  76. const defaultDisplay = LANDING_DISPLAYS.find(
  77. ({field}) => field === defaultDisplayField
  78. );
  79. return defaultDisplay || LANDING_DISPLAYS[0];
  80. }
  81. export function getCurrentLandingDisplay(
  82. location: Location,
  83. projects: Project[],
  84. eventView?: EventView
  85. ): LandingDisplay {
  86. const display = getLandingDisplayFromParam(location);
  87. if (display) {
  88. return display;
  89. }
  90. return getDefaultDisplayForPlatform(projects, eventView);
  91. }
  92. export function handleLandingDisplayChange(
  93. field: LandingDisplayField,
  94. location: Location,
  95. projects: Project[],
  96. organization: Organization,
  97. eventView?: EventView
  98. ) {
  99. // Transaction op can affect the display and show no results if it is explicitly set.
  100. const query = decodeScalar(location.query.query, '');
  101. const searchConditions = new MutableSearch(query);
  102. searchConditions.removeFilter('transaction.op');
  103. const queryWithConditions: Record<string, string> & {query: string} = {
  104. ...omit(location.query, ['landingDisplay', 'sort']),
  105. query: searchConditions.formatString(),
  106. };
  107. delete queryWithConditions[LEFT_AXIS_QUERY_KEY];
  108. delete queryWithConditions[RIGHT_AXIS_QUERY_KEY];
  109. const defaultDisplay = getDefaultDisplayFieldForPlatform(projects, eventView);
  110. const currentDisplay = getCurrentLandingDisplay(location, projects, eventView).field;
  111. const newQuery: {query: string; landingDisplay?: LandingDisplayField} =
  112. defaultDisplay === field
  113. ? {...queryWithConditions}
  114. : {...queryWithConditions, landingDisplay: field};
  115. trackAnalytics('performance_views.landingv3.display_change', {
  116. organization,
  117. change_to_display: field,
  118. default_display: defaultDisplay,
  119. current_display: currentDisplay,
  120. is_default: defaultDisplay === currentDisplay,
  121. });
  122. browserHistory.push({
  123. pathname: location.pathname,
  124. query: newQuery,
  125. });
  126. }
  127. export function getChartWidth(chartData: HistogramData, refPixelRect: Rectangle | null) {
  128. const distance = refPixelRect ? refPixelRect.point2.x - refPixelRect.point1.x : 0;
  129. const chartWidth = chartData.length * distance;
  130. return {
  131. chartWidth,
  132. };
  133. }
  134. export function getDefaultDisplayFieldForPlatform(
  135. projects: Project[],
  136. eventView?: EventView
  137. ) {
  138. if (!eventView) {
  139. return LandingDisplayField.ALL;
  140. }
  141. const projectIds = eventView.project;
  142. const performanceTypeToDisplay = {
  143. [ProjectPerformanceType.ANY]: LandingDisplayField.ALL,
  144. [ProjectPerformanceType.FRONTEND]: LandingDisplayField.FRONTEND_OTHER,
  145. [ProjectPerformanceType.BACKEND]: LandingDisplayField.BACKEND,
  146. [ProjectPerformanceType.MOBILE]: LandingDisplayField.MOBILE,
  147. };
  148. const performanceType = platformToPerformanceType(projects, projectIds);
  149. const landingField =
  150. performanceTypeToDisplay[performanceType as keyof typeof performanceTypeToDisplay] ??
  151. LandingDisplayField.ALL;
  152. return landingField;
  153. }
  154. type VitalCardDetail = {
  155. formatter: (value: number) => string | number;
  156. title: string;
  157. tooltip: string;
  158. };
  159. export const vitalCardDetails = (
  160. organization: Organization
  161. ): {[key: string]: VitalCardDetail | undefined} => {
  162. return {
  163. 'p75(transaction.duration)': {
  164. title: t('Duration (p75)'),
  165. tooltip: getTermHelp(organization, PerformanceTerm.P75),
  166. formatter: value => getDuration(value / 1000, value >= 1000 ? 3 : 0, true),
  167. },
  168. 'tpm()': {
  169. title: t('Throughput'),
  170. tooltip: getTermHelp(organization, PerformanceTerm.THROUGHPUT),
  171. formatter: (value: number | string) => formatAbbreviatedNumber(value),
  172. },
  173. 'failure_rate()': {
  174. title: t('Failure Rate'),
  175. tooltip: getTermHelp(organization, PerformanceTerm.FAILURE_RATE),
  176. formatter: value => formatPercentage(value, 2),
  177. },
  178. 'apdex()': {
  179. title: t('Apdex'),
  180. tooltip: getTermHelp(organization, PerformanceTerm.APDEX),
  181. formatter: value => formatFloat(value, 4),
  182. },
  183. 'p75(measurements.frames_slow_rate)': {
  184. title: t('Slow Frames (p75)'),
  185. tooltip: getTermHelp(organization, PerformanceTerm.SLOW_FRAMES),
  186. formatter: value => formatPercentage(value, 2),
  187. },
  188. 'p75(measurements.frames_frozen_rate)': {
  189. title: t('Frozen Frames (p75)'),
  190. tooltip: getTermHelp(organization, PerformanceTerm.FROZEN_FRAMES),
  191. formatter: value => formatPercentage(value, 2),
  192. },
  193. 'p75(measurements.app_start_cold)': {
  194. title: t('Cold Start (p75)'),
  195. tooltip: getTermHelp(organization, PerformanceTerm.APP_START_COLD),
  196. formatter: value => getDuration(value / 1000, value >= 1000 ? 3 : 0, true),
  197. },
  198. 'p75(measurements.app_start_warm)': {
  199. title: t('Warm Start (p75)'),
  200. tooltip: getTermHelp(organization, PerformanceTerm.APP_START_WARM),
  201. formatter: value => getDuration(value / 1000, value >= 1000 ? 3 : 0, true),
  202. },
  203. 'p75(measurements.stall_percentage)': {
  204. title: t('Stall Percentage (p75)'),
  205. tooltip: getTermHelp(organization, PerformanceTerm.STALL_PERCENTAGE),
  206. formatter: value => formatPercentage(value, 2),
  207. },
  208. };
  209. };
  210. export function getDisplayAxes(options: AxisOption[], location: Location) {
  211. const leftDefault = options.find(opt => opt.isLeftDefault) || options[0];
  212. const rightDefault = options.find(opt => opt.isRightDefault) || options[1];
  213. const leftAxis =
  214. options.find(opt => opt.value === location.query[LEFT_AXIS_QUERY_KEY]) || leftDefault;
  215. const rightAxis =
  216. options.find(opt => opt.value === location.query[RIGHT_AXIS_QUERY_KEY]) ||
  217. rightDefault;
  218. return {
  219. leftAxis,
  220. rightAxis,
  221. };
  222. }
  223. export function checkIsReactNative(eventView: EventView) {
  224. // only react native should contain the stall percentage column
  225. return Boolean(
  226. eventView.getFields().find(field => field.includes('measurements.stall_percentage'))
  227. );
  228. }