usageStatsPerMin.tsx 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import styled from '@emotion/styled';
  2. import {DATA_CATEGORY_INFO} from 'sentry/constants';
  3. import {t} from 'sentry/locale';
  4. import type {DataCategoryInfo, Organization} from 'sentry/types';
  5. import {Outcome} from 'sentry/types';
  6. import {useApiQuery} from 'sentry/utils/queryClient';
  7. import type {UsageSeries} from './types';
  8. import {formatUsageWithUnits, getFormatUsageOptions} from './utils';
  9. type Props = {
  10. dataCategory: DataCategoryInfo['plural'];
  11. organization: Organization;
  12. projectIds: number[];
  13. };
  14. /**
  15. * Making 1 extra API call to display this number isn't very efficient.
  16. * The other approach would be to fetch the data in UsageStatsOrg with 1min
  17. * interval and roll it up on the frontend, but that (1) adds unnecessary
  18. * complexity as it's gnarly to fetch + rollup 90 days of 1min intervals,
  19. * (3) API resultset has a limit of 1000, so 90 days of 1min would not work.
  20. *
  21. * We're going with this approach for simplicity sake. By keeping the range
  22. * as small as possible, this call is quite fast.
  23. */
  24. function UsageStatsPerMin({dataCategory, organization, projectIds}: Props) {
  25. const {
  26. data: orgStats,
  27. isLoading,
  28. isError,
  29. } = useApiQuery<UsageSeries>(
  30. [
  31. `/organizations/${organization.slug}/stats_v2/`,
  32. {
  33. query: {
  34. statsPeriod: '5m', // Any value <1h will return current hour's data
  35. interval: '1m',
  36. groupBy: ['category', 'outcome'],
  37. project: projectIds,
  38. field: ['sum(quantity)'],
  39. },
  40. },
  41. ],
  42. {
  43. staleTime: 0,
  44. }
  45. );
  46. if (isLoading || isError || !orgStats || orgStats.intervals.length === 0) {
  47. return null;
  48. }
  49. const minuteData = (): string | undefined => {
  50. // The last minute in the series is still "in progress"
  51. // Read data from 2nd last element for the latest complete minute
  52. const {intervals, groups} = orgStats;
  53. const lastMin = Math.max(intervals.length - 2, 0);
  54. const eventsLastMin = groups.reduce((count, group) => {
  55. const {outcome, category} = group.by;
  56. // HACK: The backend enum are singular, but the frontend enums are plural
  57. if (!dataCategory.includes(`${category}`) || outcome !== Outcome.ACCEPTED) {
  58. return count;
  59. }
  60. count += group.series['sum(quantity)'][lastMin];
  61. return count;
  62. }, 0);
  63. return formatUsageWithUnits(
  64. eventsLastMin,
  65. dataCategory,
  66. getFormatUsageOptions(dataCategory)
  67. );
  68. };
  69. // Metrics stats ingestion is delayed, so we can't show this for metrics right now
  70. if (dataCategory === DATA_CATEGORY_INFO.metrics.plural) {
  71. return null;
  72. }
  73. return (
  74. <Wrapper>
  75. {minuteData()} {t('in last min')}
  76. </Wrapper>
  77. );
  78. }
  79. export default UsageStatsPerMin;
  80. const Wrapper = styled('div')`
  81. display: inline-block;
  82. color: ${p => p.theme.success};
  83. font-size: ${p => p.theme.fontSizeMedium};
  84. `;