sessions.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import compact from 'lodash/compact';
  2. import moment from 'moment';
  3. import {
  4. DateTimeObject,
  5. getDiffInMinutes,
  6. ONE_WEEK,
  7. TWO_WEEKS,
  8. } from 'app/components/charts/utils';
  9. import {SessionApiResponse, SessionField} from 'app/types';
  10. import {SeriesDataUnit} from 'app/types/echarts';
  11. import {defined, percent} from 'app/utils';
  12. import {getCrashFreePercent} from 'app/views/releases/utils';
  13. export function getCount(groups: SessionApiResponse['groups'] = [], field: SessionField) {
  14. return groups.reduce((acc, group) => acc + group.totals[field], 0);
  15. }
  16. export function getCrashCount(
  17. groups: SessionApiResponse['groups'] = [],
  18. field: SessionField
  19. ) {
  20. return getCount(
  21. groups.filter(({by}) => by['session.status'] === 'crashed'),
  22. field
  23. );
  24. }
  25. export function getCrashFreeRate(
  26. groups: SessionApiResponse['groups'] = [],
  27. field: SessionField
  28. ) {
  29. const totalCount = groups.reduce((acc, group) => acc + group.totals[field], 0);
  30. const crashedCount = getCrashCount(groups, field);
  31. return !defined(totalCount) || totalCount === 0
  32. ? null
  33. : getCrashFreePercent(100 - percent(crashedCount ?? 0, totalCount ?? 0));
  34. }
  35. export function getCrashFreeSeries(
  36. groups: SessionApiResponse['groups'] = [],
  37. intervals: SessionApiResponse['intervals'] = [],
  38. field: SessionField
  39. ): SeriesDataUnit[] {
  40. return compact(
  41. intervals.map((interval, i) => {
  42. const intervalTotalSessions = groups.reduce(
  43. (acc, group) => acc + group.series[field][i],
  44. 0
  45. );
  46. const intervalCrashedSessions =
  47. groups.find(group => group.by['session.status'] === 'crashed')?.series[field][
  48. i
  49. ] ?? 0;
  50. const crashedSessionsPercent = percent(
  51. intervalCrashedSessions,
  52. intervalTotalSessions
  53. );
  54. if (intervalTotalSessions === 0) {
  55. return null;
  56. }
  57. return {
  58. name: interval,
  59. value: getCrashFreePercent(100 - crashedSessionsPercent),
  60. };
  61. })
  62. );
  63. }
  64. export function getAdoptionSeries(
  65. releaseGroups: SessionApiResponse['groups'] = [],
  66. allGroups: SessionApiResponse['groups'] = [],
  67. intervals: SessionApiResponse['intervals'] = [],
  68. field: SessionField
  69. ): SeriesDataUnit[] {
  70. return intervals.map((interval, i) => {
  71. const intervalReleaseSessions = releaseGroups.reduce(
  72. (acc, group) => acc + group.series[field][i],
  73. 0
  74. );
  75. const intervalTotalSessions = allGroups.reduce(
  76. (acc, group) => acc + group.series[field][i],
  77. 0
  78. );
  79. const intervalAdoption = percent(intervalReleaseSessions, intervalTotalSessions);
  80. return {
  81. name: interval,
  82. value: Math.round(intervalAdoption),
  83. };
  84. });
  85. }
  86. type GetSessionsIntervalOptions = {
  87. highFidelity?: boolean;
  88. };
  89. export function getSessionsInterval(
  90. datetimeObj: DateTimeObject,
  91. {highFidelity}: GetSessionsIntervalOptions = {}
  92. ) {
  93. const diffInMinutes = getDiffInMinutes(datetimeObj);
  94. if (diffInMinutes > TWO_WEEKS) {
  95. return '1d';
  96. }
  97. if (diffInMinutes > ONE_WEEK) {
  98. return '6h';
  99. }
  100. // limit on backend for sub-hour session resolution is set to six hours
  101. if (highFidelity && diffInMinutes < 360) {
  102. if (diffInMinutes <= 30) {
  103. return '1m';
  104. }
  105. return '5m';
  106. }
  107. return '1h';
  108. }
  109. // Sessions API can only round intervals to the closest hour - this is especially problematic when using sub-hour resolution.
  110. // We filter out results that are out of bounds on frontend and recalculate totals.
  111. export function filterSessionsInTimeWindow(
  112. sessions: SessionApiResponse,
  113. start?: string,
  114. end?: string
  115. ) {
  116. if (!start || !end) {
  117. return sessions;
  118. }
  119. const filteredIndexes: number[] = [];
  120. const intervals = sessions.intervals.filter((interval, index) => {
  121. const isBetween = moment(interval).isBetween(start, end, undefined, '[]');
  122. if (isBetween) {
  123. filteredIndexes.push(index);
  124. }
  125. return isBetween;
  126. });
  127. const groups = sessions.groups.map(group => {
  128. const series = {};
  129. const totals = {};
  130. Object.keys(group.series).forEach(field => {
  131. series[field] = group.series[field].filter((value, index) => {
  132. const isBetween = filteredIndexes.includes(index);
  133. if (isBetween) {
  134. totals[field] = (totals[field] ?? 0) + value;
  135. }
  136. return isBetween;
  137. });
  138. });
  139. return {...group, series, totals};
  140. });
  141. return {
  142. start: intervals[0],
  143. end: intervals[intervals.length - 1],
  144. query: sessions.query,
  145. intervals,
  146. groups,
  147. };
  148. }