issueViewQueryCount.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {motion} from 'framer-motion';
  4. import {space} from 'sentry/styles/space';
  5. import type {PageFilters} from 'sentry/types/core';
  6. import {getUtcDateString} from 'sentry/utils/dates';
  7. import useOrganization from 'sentry/utils/useOrganization';
  8. import usePageFilters from 'sentry/utils/usePageFilters';
  9. import type {IssueView} from 'sentry/views/issueList/issueViews/issueViews';
  10. import {useFetchIssueCounts} from 'sentry/views/issueList/queries/useFetchIssueCounts';
  11. const TAB_MAX_COUNT = 99;
  12. const constructCountTimeFrame = (
  13. pageFilters: PageFilters['datetime']
  14. ): {
  15. end?: string;
  16. start?: string;
  17. statsPeriod?: string;
  18. } => {
  19. if (pageFilters.period) {
  20. return {statsPeriod: pageFilters.period};
  21. }
  22. return {
  23. ...(pageFilters.start ? {start: getUtcDateString(pageFilters.start)} : {}),
  24. ...(pageFilters.end ? {end: getUtcDateString(pageFilters.end)} : {}),
  25. };
  26. };
  27. interface IssueViewQueryCountProps {
  28. view: IssueView;
  29. }
  30. export function IssueViewQueryCount({view}: IssueViewQueryCountProps) {
  31. const organization = useOrganization();
  32. const pageFilters = usePageFilters();
  33. const theme = useTheme();
  34. // TODO(msun): Once page filters are saved to views, remember to use the view's specific
  35. // page filters here instead of the global pageFilters, if they exist.
  36. const {
  37. data: queryCount,
  38. isLoading,
  39. isFetching,
  40. isError,
  41. } = useFetchIssueCounts({
  42. orgSlug: organization.slug,
  43. query: [view.unsavedChanges?.query ?? view.query],
  44. project: pageFilters.selection.projects,
  45. environment: pageFilters.selection.environments,
  46. ...constructCountTimeFrame(pageFilters.selection.datetime),
  47. });
  48. // The endpoint's response type looks like this: { <query1>: <count>, <query2>: <count> }
  49. // But this component only ever sends one query, so we can just get the count of the first key.
  50. // This is a bit hacky, but it avoids having to use a state to store the previous query
  51. // when the query changes and the new data is still being fetched.
  52. const defaultQuery =
  53. Object.keys(queryCount ?? {}).length > 0
  54. ? Object.keys(queryCount ?? {})[0]
  55. : undefined;
  56. const count = isError
  57. ? 0
  58. : queryCount?.[view.unsavedChanges?.query ?? view.query] ??
  59. queryCount?.[defaultQuery ?? ''] ??
  60. 0;
  61. return (
  62. <QueryCountBubble
  63. layout="size"
  64. animate={{
  65. backgroundColor: isFetching
  66. ? [theme.surface400, theme.surface100, theme.surface400]
  67. : `#00000000`,
  68. }}
  69. transition={{
  70. layout: {
  71. duration: 0.2,
  72. },
  73. default: {
  74. // Cuts animation short once the query has finished fetching
  75. duration: isFetching ? 2 : 0,
  76. repeat: isFetching ? Infinity : 0,
  77. ease: 'easeInOut',
  78. },
  79. }}
  80. >
  81. <motion.span
  82. // Prevents count from fading in if it's already cached on mount
  83. layout="preserve-aspect"
  84. initial={{opacity: isLoading ? 0 : 1}}
  85. animate={{opacity: isFetching ? 0 : 1}}
  86. transition={{duration: 0.1}}
  87. >
  88. {count > TAB_MAX_COUNT ? `${TAB_MAX_COUNT}+` : count}
  89. </motion.span>
  90. </QueryCountBubble>
  91. );
  92. }
  93. const QueryCountBubble = styled(motion.span)`
  94. line-height: 20px;
  95. font-size: ${p => p.theme.fontSizeExtraSmall};
  96. padding: 0 ${space(0.5)};
  97. min-width: 20px;
  98. display: flex;
  99. height: 16px;
  100. align-items: center;
  101. justify-content: center;
  102. border-radius: 10px;
  103. border: 1px solid ${p => p.theme.gray200};
  104. color: ${p => p.theme.gray300};
  105. margin-left: 0;
  106. `;