projectApdexScoreCard.tsx 4.2 KB

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