issueViewQueryCount.tsx 3.6 KB

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