useCrashFreeSessions.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import type {SessionApiResponse} from 'sentry/types/organization';
  2. import {percent} from 'sentry/utils';
  3. import {useApiQuery} from 'sentry/utils/queryClient';
  4. import {useLocation} from 'sentry/utils/useLocation';
  5. import useOrganization from 'sentry/utils/useOrganization';
  6. import useSessionAdoptionRate from 'sentry/views/insights/sessions/queries/useSessionProjectTotal';
  7. export default function useCrashFreeSessions() {
  8. const location = useLocation();
  9. const organization = useOrganization();
  10. const {
  11. data: sessionData,
  12. isPending,
  13. error,
  14. } = useApiQuery<SessionApiResponse>(
  15. [
  16. `/organizations/${organization.slug}/sessions/`,
  17. {
  18. query: {
  19. ...location.query,
  20. field: ['sum(session)'],
  21. groupBy: ['session.status', 'release'],
  22. },
  23. },
  24. ],
  25. {staleTime: 0}
  26. );
  27. const projectTotal = useSessionAdoptionRate();
  28. if (isPending) {
  29. return {
  30. series: [],
  31. isPending: true,
  32. error,
  33. };
  34. }
  35. if (!sessionData && !isPending) {
  36. return {
  37. series: [],
  38. isPending: false,
  39. error,
  40. };
  41. }
  42. const getStatusSeries = (status: string, groups: typeof sessionData.groups) =>
  43. groups.find(group => group.by['session.status'] === status)?.series['sum(session)'] ??
  44. [];
  45. // Maps release to its API response groups
  46. const releaseGroupMap = new Map<string, typeof sessionData.groups>();
  47. // Maps release to its adoption rate calculation
  48. const releaseAdoptionMap = new Map<string, number>();
  49. sessionData.groups.forEach(group => {
  50. const release = group.by.release?.toString() ?? '';
  51. if (!releaseGroupMap.has(release)) {
  52. releaseGroupMap.set(release, []);
  53. const releaseGroups = sessionData.groups.filter(
  54. g => g.by.release?.toString() === release
  55. );
  56. // Calculate total sessions for entire time period
  57. const totalSessionCount = releaseGroups.reduce(
  58. (acc, g) => acc + (g.totals['sum(session)'] ?? 0),
  59. 0
  60. );
  61. // Only consider releases with total sessions > 0
  62. if (totalSessionCount > 0) {
  63. releaseAdoptionMap.set(release, percent(totalSessionCount, projectTotal));
  64. }
  65. }
  66. if (releaseAdoptionMap.has(release)) {
  67. releaseGroupMap.get(release)!.push(group);
  68. }
  69. });
  70. // Get top 5 releases
  71. const topReleases = Array.from(releaseAdoptionMap.entries())
  72. .sort(([, a], [, b]) => b - a)
  73. .slice(0, 5)
  74. .map(([release]) => release);
  75. const series = topReleases.map(release => {
  76. const groups = releaseGroupMap.get(release)!;
  77. // Get all status series at once and calculate crash-free session percentage for each interval
  78. const seriesData = getStatusSeries('crashed', groups).map((crashedCount, idx) => {
  79. const intervalTotal = [
  80. crashedCount,
  81. getStatusSeries('abnormal', groups)[idx] || 0,
  82. getStatusSeries('crashed', groups)[idx] || 0,
  83. getStatusSeries('healthy', groups)[idx] || 0,
  84. ].reduce((sum, val) => sum + val, 0);
  85. return {
  86. name: sessionData.intervals[idx] ?? '',
  87. value: intervalTotal > 0 ? 1 - crashedCount / intervalTotal : 1,
  88. };
  89. });
  90. return {
  91. data: seriesData,
  92. seriesName: `crash_free_session_rate_${release}`,
  93. meta: {
  94. fields: {
  95. [`crash_free_session_rate_${release}`]: 'percentage' as const,
  96. time: 'date' as const,
  97. },
  98. units: {
  99. [`crash_free_session_rate_${release}`]: '%',
  100. },
  101. },
  102. };
  103. });
  104. return {series, releases: topReleases, isPending, error};
  105. }