projectApdexScoreCard.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {shouldFetchPreviousPeriod} from 'sentry/components/charts/utils';
  2. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  3. import {parseStatsPeriod} from 'sentry/components/timeRangeSelector/utils';
  4. import {t} from 'sentry/locale';
  5. import type {PageFilters} from 'sentry/types/core';
  6. import type {Organization} from 'sentry/types/organization';
  7. import type {TableData} from 'sentry/utils/discover/discoverQuery';
  8. import {getPeriod} from 'sentry/utils/duration/getPeriod';
  9. import {useApiQuery} from 'sentry/utils/queryClient';
  10. import {BigNumberWidget} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidget';
  11. import {WidgetFrame} from 'sentry/views/dashboards/widgets/common/widgetFrame';
  12. import {getTermHelp, PerformanceTerm} from 'sentry/views/performance/data';
  13. import MissingPerformanceButtons from '../missingFeatureButtons/missingPerformanceButtons';
  14. type Props = {
  15. isProjectStabilized: boolean;
  16. organization: Organization;
  17. selection: PageFilters;
  18. hasTransactions?: boolean;
  19. query?: string;
  20. };
  21. const useApdex = (props: Props) => {
  22. const {organization, selection, isProjectStabilized, hasTransactions, query} = props;
  23. const isEnabled = !!(
  24. organization.features.includes('performance-view') &&
  25. isProjectStabilized &&
  26. hasTransactions
  27. );
  28. const {projects, environments: environments, datetime} = selection;
  29. const {period} = datetime;
  30. const {start: previousStart} = parseStatsPeriod(
  31. getPeriod({period, start: undefined, end: undefined}, {shouldDoublePeriod: true})
  32. .statsPeriod!
  33. );
  34. const {start: previousEnd} = parseStatsPeriod(
  35. getPeriod({period, start: undefined, end: undefined}, {shouldDoublePeriod: false})
  36. .statsPeriod!
  37. );
  38. const commonQuery = {
  39. environment: environments,
  40. project: projects.map(proj => String(proj)),
  41. field: ['apdex()'],
  42. query: ['event.type:transaction count():>0', query].join(' ').trim(),
  43. };
  44. const currentQuery = useApiQuery<TableData>(
  45. [
  46. `/organizations/${organization.slug}/events/`,
  47. {
  48. query: {
  49. ...commonQuery,
  50. ...normalizeDateTimeParams(datetime),
  51. },
  52. },
  53. ],
  54. {staleTime: 0, enabled: isEnabled}
  55. );
  56. const isPreviousPeriodEnabled = shouldFetchPreviousPeriod({
  57. start: datetime.start,
  58. end: datetime.end,
  59. period: datetime.period,
  60. });
  61. const previousQuery = useApiQuery<TableData>(
  62. [
  63. `/organizations/${organization.slug}/events/`,
  64. {
  65. query: {
  66. ...commonQuery,
  67. start: previousStart,
  68. end: previousEnd,
  69. },
  70. },
  71. ],
  72. {
  73. staleTime: 0,
  74. enabled: isEnabled && isPreviousPeriodEnabled,
  75. }
  76. );
  77. return {
  78. data: currentQuery.data,
  79. previousData: previousQuery.data,
  80. isLoading:
  81. currentQuery.isPending || (previousQuery.isPending && isPreviousPeriodEnabled),
  82. error: currentQuery.error || previousQuery.error,
  83. refetch: () => {
  84. currentQuery.refetch();
  85. previousQuery.refetch();
  86. },
  87. };
  88. };
  89. function ProjectApdexScoreCard(props: Props) {
  90. const {organization, hasTransactions} = props;
  91. const {data, previousData, isLoading, error, refetch} = useApdex(props);
  92. const apdex = Number(data?.data?.[0]?.['apdex()']) || undefined;
  93. const previousApdex = Number(previousData?.data?.[0]?.['apdex()']) || undefined;
  94. const cardTitle = t('Apdex');
  95. const cardHelp = getTermHelp(organization, PerformanceTerm.APDEX);
  96. if (!hasTransactions || !organization.features.includes('performance-view')) {
  97. return (
  98. <WidgetFrame title={cardTitle} description={cardHelp}>
  99. <MissingPerformanceButtons organization={organization} />
  100. </WidgetFrame>
  101. );
  102. }
  103. return (
  104. <BigNumberWidget
  105. title={cardTitle}
  106. description={cardHelp}
  107. data={[
  108. {
  109. 'apdex()': apdex,
  110. },
  111. ]}
  112. previousPeriodData={[
  113. {
  114. 'apdex()': previousApdex,
  115. },
  116. ]}
  117. meta={{
  118. fields: {
  119. 'apdex()': 'number',
  120. },
  121. }}
  122. preferredPolarity="+"
  123. isLoading={isLoading}
  124. error={error ?? undefined}
  125. onRetry={refetch}
  126. />
  127. );
  128. }
  129. export default ProjectApdexScoreCard;