Browse Source

ref(whats-new): Convert class to functional component & use named export (#78250)

Priscila Oliveira 5 months ago
parent
commit
32b616bfff

+ 1 - 3
static/app/components/sidebar/broadcasts.spec.tsx

@@ -4,7 +4,7 @@ import {OrganizationFixture} from 'sentry-fixture/organization';
 import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import {BROADCAST_CATEGORIES} from 'sentry/components/sidebar/broadcastPanelItem';
-import Broadcasts from 'sentry/components/sidebar/broadcasts';
+import {Broadcasts} from 'sentry/components/sidebar/broadcasts';
 import {SidebarPanelKey} from 'sentry/components/sidebar/types';
 import type {Broadcast} from 'sentry/types/system';
 import {trackAnalytics} from 'sentry/utils/analytics';
@@ -43,7 +43,6 @@ describe('Broadcasts', function () {
         currentPanel={SidebarPanelKey.BROADCASTS}
         onShowPanel={() => jest.fn()}
         hidePanel={jest.fn()}
-        organization={organization}
       />
     );
 
@@ -67,7 +66,6 @@ describe('Broadcasts', function () {
         currentPanel={SidebarPanelKey.BROADCASTS}
         onShowPanel={() => jest.fn()}
         hidePanel={jest.fn()}
-        organization={organization}
       />
     );
 

+ 100 - 169
static/app/components/sidebar/broadcasts.tsx

@@ -1,7 +1,6 @@
-import {Component, Fragment, useEffect} from 'react';
+import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 
-import {getAllBroadcasts, markBroadcastsAsSeen} from 'sentry/actionCreators/broadcasts';
-import type {Client} from 'sentry/api';
+import {markBroadcastsAsSeen} from 'sentry/actionCreators/broadcasts';
 import DemoModeGate from 'sentry/components/acl/demoModeGate';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {BroadcastPanelItem} from 'sentry/components/sidebar/broadcastPanelItem';
@@ -10,9 +9,11 @@ import SidebarPanel from 'sentry/components/sidebar/sidebarPanel';
 import SidebarPanelEmpty from 'sentry/components/sidebar/sidebarPanelEmpty';
 import {IconBroadcast} from 'sentry/icons';
 import {t} from 'sentry/locale';
-import type {Organization} from 'sentry/types/organization';
 import type {Broadcast} from 'sentry/types/system';
-import withApi from 'sentry/utils/withApi';
+import {useApiQuery} from 'sentry/utils/queryClient';
+import useApi from 'sentry/utils/useApi';
+import useOrganization from 'sentry/utils/useOrganization';
+import usePrevious from 'sentry/utils/usePrevious';
 
 import type {CommonSidebarProps} from './types';
 import {SidebarPanelKey} from './types';
@@ -20,184 +21,114 @@ import {SidebarPanelKey} from './types';
 const MARK_SEEN_DELAY = 1000;
 const POLLER_DELAY = 600000; // 10 minute poll (60 * 10 * 1000)
 
-type Props = CommonSidebarProps & {
-  api: Client;
-  organization: Organization;
-};
-
-type State = {
-  broadcasts: Broadcast[];
-  loading: boolean;
-};
-
-function BroadcastSidebarContent({
+export function Broadcasts({
   orientation,
   collapsed,
-  loading,
-  broadcasts,
+  currentPanel,
   hidePanel,
-  onResetCounter,
-}: {
-  broadcasts: Broadcast[];
-  loading: boolean;
-  onResetCounter: () => void;
-} & Pick<CommonSidebarProps, 'orientation' | 'collapsed' | 'hidePanel'>) {
-  useEffect(() => {
-    return () => {
-      onResetCounter();
-    };
-  }, [onResetCounter]);
-
-  return (
-    <SidebarPanel
-      data-test-id="sidebar-broadcasts-panel"
-      orientation={orientation}
-      collapsed={collapsed}
-      title={t("What's new in Sentry")}
-      hidePanel={hidePanel}
-    >
-      {loading ? (
-        <LoadingIndicator />
-      ) : broadcasts.length === 0 ? (
-        <SidebarPanelEmpty>
-          {t('No recent updates from the Sentry team.')}
-        </SidebarPanelEmpty>
-      ) : (
-        broadcasts.map(item => (
-          <BroadcastPanelItem
-            key={item.id}
-            hasSeen={item.hasSeen}
-            title={item.title}
-            message={item.message}
-            link={item.link}
-            mediaUrl={item.mediaUrl}
-            category={item.category}
-          />
-        ))
-      )}
-    </SidebarPanel>
+  onShowPanel,
+}: CommonSidebarProps) {
+  const api = useApi();
+  const organization = useOrganization();
+  const previousPanel = usePrevious(currentPanel);
+
+  const [hasSeenAllPosts, setHasSeenAllPosts] = useState(false);
+  const markSeenTimeoutRef = useRef<number | undefined>(undefined);
+
+  const {isPending, data: broadcasts = []} = useApiQuery<Broadcast[]>(
+    [`/organizations/${organization.slug}/broadcasts/`],
+    {
+      staleTime: 0,
+      refetchInterval: POLLER_DELAY,
+      refetchOnWindowFocus: true,
+    }
   );
-}
-
-class Broadcasts extends Component<Props, State> {
-  state: State = {
-    broadcasts: [],
-    loading: true,
-  };
-
-  componentDidMount() {
-    this.fetchData();
-    document.addEventListener('visibilitychange', this.handleVisibilityChange);
-  }
 
-  componentWillUnmount() {
-    window.clearTimeout(this.markSeenTimeout);
-    window.clearTimeout(this.pollingTimeout);
-
-    document.removeEventListener('visibilitychange', this.handleVisibilityChange);
-  }
-
-  pollingTimeout: number | undefined = undefined;
-  markSeenTimeout: number | undefined = undefined;
+  const unseenPostIds = useMemo(
+    () => broadcasts.filter(item => !item.hasSeen).map(item => item.id),
+    [broadcasts]
+  );
 
-  startPolling() {
-    if (this.pollingTimeout) {
-      this.stopPolling();
+  const markSeen = useCallback(async () => {
+    if (unseenPostIds.length === 0) {
+      return;
     }
-    this.pollingTimeout = window.setTimeout(this.fetchData, POLLER_DELAY);
-  }
-
-  stopPolling() {
-    window.clearTimeout(this.pollingTimeout);
-    this.pollingTimeout = undefined;
-  }
 
-  fetchData = async () => {
-    if (this.pollingTimeout) {
-      this.stopPolling();
-    }
+    await markBroadcastsAsSeen(api, unseenPostIds);
+  }, [api, unseenPostIds]);
 
-    try {
-      const data = await getAllBroadcasts(this.props.api, this.props.organization.slug);
-      this.setState({loading: false, broadcasts: data || []});
-    } catch {
-      this.setState({loading: false});
+  const handleShowPanel = useCallback(() => {
+    if (markSeenTimeoutRef.current) {
+      window.clearTimeout(markSeenTimeoutRef.current);
     }
 
-    this.startPolling();
-  };
-
-  /**
-   * If tab/window loses visibility (note: this is different than focus), stop
-   * polling for broadcasts data, otherwise, if it gains visibility, start
-   * polling again.
-   */
-  handleVisibilityChange = () =>
-    document.hidden ? this.stopPolling() : this.startPolling();
+    markSeenTimeoutRef.current = window.setTimeout(() => {
+      markSeen();
+    }, MARK_SEEN_DELAY);
 
-  handleShowPanel = () => {
-    window.clearTimeout(this.markSeenTimeout);
+    onShowPanel();
+  }, [onShowPanel, markSeen]);
 
-    this.markSeenTimeout = window.setTimeout(this.markSeen, MARK_SEEN_DELAY);
-    this.props.onShowPanel();
-  };
-
-  markSeen = async () => {
-    const unseenBroadcastIds = this.unseenIds;
-    if (unseenBroadcastIds.length === 0) {
-      return;
+  useEffect(() => {
+    if (
+      previousPanel === SidebarPanelKey.BROADCASTS &&
+      currentPanel !== SidebarPanelKey.BROADCASTS
+    ) {
+      setHasSeenAllPosts(true);
     }
+  }, [previousPanel, currentPanel]);
 
-    await markBroadcastsAsSeen(this.props.api, unseenBroadcastIds);
-  };
-
-  get unseenIds() {
-    return this.state.broadcasts
-      ? this.state.broadcasts.filter(item => !item.hasSeen).map(item => item.id)
-      : [];
-  }
-
-  handleResetCounter = () => {
-    this.setState(state => ({
-      broadcasts: state.broadcasts.map(item => ({...item, hasSeen: true})),
-    }));
-  };
-
-  render() {
-    const {orientation, collapsed, currentPanel, hidePanel} = this.props;
-    const {broadcasts, loading} = this.state;
-
-    const unseenPosts = this.unseenIds;
-
-    return (
-      <DemoModeGate>
-        <Fragment>
-          <SidebarItem
-            data-test-id="sidebar-broadcasts"
-            orientation={orientation}
-            collapsed={collapsed}
-            active={currentPanel === SidebarPanelKey.BROADCASTS}
-            badge={unseenPosts.length}
-            icon={<IconBroadcast size="md" />}
-            label={t("What's new")}
-            onClick={this.handleShowPanel}
-            id="broadcasts"
-          />
-
-          {currentPanel === SidebarPanelKey.BROADCASTS && (
-            <BroadcastSidebarContent
-              loading={loading}
-              hidePanel={hidePanel}
-              broadcasts={broadcasts}
-              collapsed={collapsed}
-              orientation={orientation}
-              onResetCounter={this.handleResetCounter}
-            />
+  useEffect(() => {
+    return () => {
+      if (markSeenTimeoutRef.current) {
+        window.clearTimeout(markSeenTimeoutRef.current);
+      }
+    };
+  }, []);
+
+  return (
+    <DemoModeGate>
+      <SidebarItem
+        data-test-id="sidebar-broadcasts"
+        orientation={orientation}
+        collapsed={collapsed}
+        active={currentPanel === SidebarPanelKey.BROADCASTS}
+        badge={hasSeenAllPosts ? undefined : unseenPostIds?.length}
+        icon={<IconBroadcast size="md" />}
+        label={t("What's new")}
+        onClick={handleShowPanel}
+        id="broadcasts"
+      />
+
+      {currentPanel === SidebarPanelKey.BROADCASTS && (
+        <SidebarPanel
+          data-test-id="sidebar-broadcasts-panel"
+          orientation={orientation}
+          collapsed={collapsed}
+          title={t("What's new in Sentry")}
+          hidePanel={hidePanel}
+        >
+          {isPending ? (
+            <LoadingIndicator />
+          ) : broadcasts.length === 0 ? (
+            <SidebarPanelEmpty>
+              {t('No recent updates from the Sentry team.')}
+            </SidebarPanelEmpty>
+          ) : (
+            broadcasts.map(item => (
+              <BroadcastPanelItem
+                key={item.id}
+                hasSeen={item.hasSeen}
+                title={item.title}
+                message={item.message}
+                link={item.link}
+                mediaUrl={item.mediaUrl}
+                category={item.category}
+              />
+            ))
           )}
-        </Fragment>
-      </DemoModeGate>
-    );
-  }
+        </SidebarPanel>
+      )}
+    </DemoModeGate>
+  );
 }
-
-export default withApi(Broadcasts);

+ 1 - 2
static/app/components/sidebar/index.tsx

@@ -74,7 +74,7 @@ import MetricsOnboardingSidebar from 'sentry/views/metrics/ddmOnboarding/sidebar
 
 import {ProfilingOnboardingSidebar} from '../profiling/profilingOnboardingSidebar';
 
-import Broadcasts from './broadcasts';
+import {Broadcasts} from './broadcasts';
 import SidebarHelp from './help';
 import OnboardingStatus from './onboardingStatus';
 import ServiceIncidents from './serviceIncidents';
@@ -822,7 +822,6 @@ function Sidebar() {
                 currentPanel={activePanel}
                 onShowPanel={() => togglePanel(SidebarPanelKey.BROADCASTS)}
                 hidePanel={hidePanel}
-                organization={organization}
               />
               <ServiceIncidents
                 orientation={orientation}