projectAnrScoreCard.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {useEffect, useState} from 'react';
  2. import type {Location} from 'history';
  3. import pick from 'lodash/pick';
  4. import {doSessionsRequest} from 'sentry/actionCreators/sessions';
  5. import {shouldFetchPreviousPeriod} from 'sentry/components/charts/utils';
  6. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  7. import {parseStatsPeriod} from 'sentry/components/timeRangeSelector/utils';
  8. import {URL_PARAM} from 'sentry/constants/pageFilters';
  9. import {t} from 'sentry/locale';
  10. import type {PageFilters} from 'sentry/types/core';
  11. import type {Organization, SessionApiResponse} from 'sentry/types/organization';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import {getPeriod} from 'sentry/utils/duration/getPeriod';
  14. import useApi from 'sentry/utils/useApi';
  15. import {BigNumberWidget} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidget';
  16. import {
  17. getSessionTermDescription,
  18. SessionTerm,
  19. } from 'sentry/views/releases/utils/sessionTerm';
  20. type Props = {
  21. isProjectStabilized: boolean;
  22. location: Location;
  23. organization: Organization;
  24. selection: PageFilters;
  25. query?: string;
  26. };
  27. export function ProjectAnrScoreCard({
  28. isProjectStabilized,
  29. organization,
  30. selection,
  31. location,
  32. query,
  33. }: Props) {
  34. const {environments, projects, datetime} = selection;
  35. const {start, end, period} = datetime;
  36. const api = useApi();
  37. const [sessionsData, setSessionsData] = useState<SessionApiResponse | null>(null);
  38. const [previousSessionData, setPreviousSessionsData] =
  39. useState<SessionApiResponse | null>(null);
  40. useEffect(() => {
  41. let unmounted = false;
  42. const requestData = {
  43. orgSlug: organization.slug,
  44. field: ['anr_rate()'],
  45. environment: environments,
  46. project: projects,
  47. query,
  48. includeSeries: false,
  49. };
  50. doSessionsRequest(api, {...requestData, ...normalizeDateTimeParams(datetime)}).then(
  51. response => {
  52. if (unmounted) {
  53. return;
  54. }
  55. setSessionsData(response);
  56. }
  57. );
  58. return () => {
  59. unmounted = true;
  60. };
  61. }, [api, datetime, environments, organization.slug, projects, query]);
  62. useEffect(() => {
  63. let unmounted = false;
  64. if (
  65. !shouldFetchPreviousPeriod({
  66. start,
  67. end,
  68. period,
  69. })
  70. ) {
  71. setPreviousSessionsData(null);
  72. } else {
  73. const requestData = {
  74. orgSlug: organization.slug,
  75. field: ['anr_rate()'],
  76. environment: environments,
  77. project: projects,
  78. query,
  79. includeSeries: false,
  80. };
  81. const {start: previousStart} = parseStatsPeriod(
  82. getPeriod({period, start: undefined, end: undefined}, {shouldDoublePeriod: true})
  83. .statsPeriod!
  84. );
  85. const {start: previousEnd} = parseStatsPeriod(
  86. getPeriod({period, start: undefined, end: undefined}, {shouldDoublePeriod: false})
  87. .statsPeriod!
  88. );
  89. doSessionsRequest(api, {
  90. ...requestData,
  91. start: previousStart,
  92. end: previousEnd,
  93. }).then(response => {
  94. if (unmounted) {
  95. return;
  96. }
  97. setPreviousSessionsData(response);
  98. });
  99. }
  100. return () => {
  101. unmounted = true;
  102. };
  103. }, [start, end, period, api, organization.slug, environments, projects, query]);
  104. const value = sessionsData?.groups?.[0]?.totals['anr_rate()'] ?? null;
  105. const previousValue = previousSessionData?.groups?.[0]?.totals['anr_rate()'] ?? null;
  106. if (!isProjectStabilized) {
  107. return null;
  108. }
  109. const endpointPath = `/organizations/${organization.slug}/issues/`;
  110. const issueQuery = ['mechanism:[ANR,AppExitInfo]', query].join(' ').trim();
  111. const queryParams = {
  112. ...normalizeDateTimeParams(pick(location.query, [...Object.values(URL_PARAM)])),
  113. query: issueQuery,
  114. sort: 'freq',
  115. };
  116. const issueSearch = {
  117. pathname: endpointPath,
  118. query: queryParams,
  119. };
  120. return (
  121. <BigNumberWidget
  122. title={t('ANR Rate')}
  123. description={getSessionTermDescription(SessionTerm.ANR_RATE, null)}
  124. value={value ?? undefined}
  125. previousPeriodValue={previousValue ?? undefined}
  126. field="anr_rate()"
  127. preferredPolarity="-"
  128. meta={{
  129. fields: {
  130. 'anr_rate()': 'percentage',
  131. },
  132. }}
  133. actions={[
  134. {
  135. key: 'issue-search',
  136. label: t('View Issues'),
  137. to: issueSearch,
  138. onAction: () => {
  139. trackAnalytics('project_detail.open_anr_issues', {
  140. organization,
  141. });
  142. },
  143. },
  144. ]}
  145. />
  146. );
  147. }