utils.tsx 8.2 KB

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