broadcasts.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
  2. import {markBroadcastsAsSeen} from 'sentry/actionCreators/broadcasts';
  3. import DemoModeGate from 'sentry/components/acl/demoModeGate';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import {BroadcastPanelItem} from 'sentry/components/sidebar/broadcastPanelItem';
  6. import SidebarItem from 'sentry/components/sidebar/sidebarItem';
  7. import SidebarPanel from 'sentry/components/sidebar/sidebarPanel';
  8. import SidebarPanelEmpty from 'sentry/components/sidebar/sidebarPanelEmpty';
  9. import {IconBroadcast} from 'sentry/icons';
  10. import {t} from 'sentry/locale';
  11. import type {Broadcast} from 'sentry/types/system';
  12. import {useApiQuery} from 'sentry/utils/queryClient';
  13. import useApi from 'sentry/utils/useApi';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import usePrevious from 'sentry/utils/usePrevious';
  16. import type {CommonSidebarProps} from './types';
  17. import {SidebarPanelKey} from './types';
  18. const MARK_SEEN_DELAY = 1000;
  19. const POLLER_DELAY = 600000; // 10 minute poll (60 * 10 * 1000)
  20. export function Broadcasts({
  21. orientation,
  22. collapsed,
  23. currentPanel,
  24. hidePanel,
  25. onShowPanel,
  26. }: CommonSidebarProps) {
  27. const api = useApi();
  28. const organization = useOrganization();
  29. const previousPanel = usePrevious(currentPanel);
  30. const [hasSeenAllPosts, setHasSeenAllPosts] = useState(false);
  31. const markSeenTimeoutRef = useRef<number | undefined>(undefined);
  32. const {isPending, data: broadcasts = []} = useApiQuery<Broadcast[]>(
  33. [`/organizations/${organization.slug}/broadcasts/`],
  34. {
  35. staleTime: 0,
  36. refetchInterval: POLLER_DELAY,
  37. refetchOnWindowFocus: true,
  38. }
  39. );
  40. const unseenPostIds = useMemo(
  41. () => broadcasts.filter(item => !item.hasSeen).map(item => item.id),
  42. [broadcasts]
  43. );
  44. const markSeen = useCallback(async () => {
  45. if (unseenPostIds.length === 0) {
  46. return;
  47. }
  48. await markBroadcastsAsSeen(api, unseenPostIds);
  49. }, [api, unseenPostIds]);
  50. const handleShowPanel = useCallback(() => {
  51. if (markSeenTimeoutRef.current) {
  52. window.clearTimeout(markSeenTimeoutRef.current);
  53. }
  54. markSeenTimeoutRef.current = window.setTimeout(() => {
  55. markSeen();
  56. }, MARK_SEEN_DELAY);
  57. onShowPanel();
  58. }, [onShowPanel, markSeen]);
  59. useEffect(() => {
  60. if (
  61. previousPanel === SidebarPanelKey.BROADCASTS &&
  62. currentPanel !== SidebarPanelKey.BROADCASTS
  63. ) {
  64. setHasSeenAllPosts(true);
  65. }
  66. }, [previousPanel, currentPanel]);
  67. useEffect(() => {
  68. return () => {
  69. if (markSeenTimeoutRef.current) {
  70. window.clearTimeout(markSeenTimeoutRef.current);
  71. }
  72. };
  73. }, []);
  74. return (
  75. <DemoModeGate>
  76. <SidebarItem
  77. data-test-id="sidebar-broadcasts"
  78. orientation={orientation}
  79. collapsed={collapsed}
  80. active={currentPanel === SidebarPanelKey.BROADCASTS}
  81. badge={hasSeenAllPosts ? undefined : unseenPostIds?.length}
  82. icon={<IconBroadcast size="md" />}
  83. label={t("What's new")}
  84. onClick={handleShowPanel}
  85. id="broadcasts"
  86. />
  87. {currentPanel === SidebarPanelKey.BROADCASTS && (
  88. <SidebarPanel
  89. data-test-id="sidebar-broadcasts-panel"
  90. orientation={orientation}
  91. collapsed={collapsed}
  92. title={t("What's new in Sentry")}
  93. hidePanel={hidePanel}
  94. >
  95. {isPending ? (
  96. <LoadingIndicator />
  97. ) : broadcasts.length === 0 ? (
  98. <SidebarPanelEmpty>
  99. {t('No recent updates from the Sentry team.')}
  100. </SidebarPanelEmpty>
  101. ) : (
  102. broadcasts.map(item => (
  103. <BroadcastPanelItem
  104. key={item.id}
  105. hasSeen={item.hasSeen}
  106. title={item.title}
  107. message={item.message}
  108. link={item.link}
  109. mediaUrl={item.mediaUrl}
  110. category={item.category}
  111. />
  112. ))
  113. )}
  114. </SidebarPanel>
  115. )}
  116. </DemoModeGate>
  117. );
  118. }