Browse Source

feat(feedback): show widget setup banner if all projects have only crash reports (#66160)

<img width="1257" alt="SCR-20240301-lpey"
src="https://github.com/getsentry/sentry/assets/56095982/84f242a5-885e-488d-bfbd-6bf52c4710bc">

This dismissible banner shows up if the selected projects have
- at least 1 feedback set up
- at least 1 project where the widget is not set up
- at least 1 project that's eligible for widget set up

Clicking "set up" opens the fly-out sidebar to the relevant project.
It's basically an upgraded version of our what's new banner:

<img width="1251" alt="SCR-20240301-lonu"
src="https://github.com/getsentry/sentry/assets/56095982/ad874068-2786-4398-9ebb-8428b4ea8cf0">
Michelle Zhang 1 year ago
parent
commit
9bc3d0b07d

+ 49 - 0
static/app/components/feedback/feedbackOnboarding/feedbackWidgetBanner.tsx

@@ -0,0 +1,49 @@
+import type {CSSProperties} from 'react';
+
+import replaysDeadRageBackground from 'sentry-images/spot/replay-dead-rage-changelog.svg';
+
+import PageBanner from 'sentry/components/alerts/pageBanner';
+import {Button} from 'sentry/components/button';
+import {useFeedbackOnboardingSidebarPanel} from 'sentry/components/feedback/useFeedbackOnboarding';
+import {t} from 'sentry/locale';
+import useDismissAlert from 'sentry/utils/useDismissAlert';
+import useOrganization from 'sentry/utils/useOrganization';
+
+export default function FeedbackWidgetBanner({style}: {style?: CSSProperties}) {
+  const {activateSidebar} = useFeedbackOnboardingSidebarPanel();
+  const organization = useOrganization();
+
+  const {dismiss, isDismissed} = useDismissAlert({
+    key: `${organization.id}:feedback-widget-callout`,
+  });
+
+  if (isDismissed) {
+    return null;
+  }
+
+  return (
+    <PageBanner
+      style={style}
+      button={
+        <Button
+          priority="primary"
+          analyticsEventName="Clicked Feedback Onboarding CTA Button in Widget Callout Banner"
+          analyticsEventKey="feedback.widget-banner-cta-button-clicked"
+          onClick={activateSidebar}
+        >
+          {t('Set Up Now')}
+        </Button>
+      }
+      description={t(
+        'Want to receive user feedback at any time, not just when an error happens? Learn how to set up our customizable user feedback widget.'
+      )}
+      heading={t('Introducing the User Feedback Widget')}
+      icon={null}
+      image={replaysDeadRageBackground}
+      title={null}
+      onDismiss={() => {
+        dismiss();
+      }}
+    />
+  );
+}

+ 22 - 1
static/app/views/feedback/feedbackListPage.tsx

@@ -5,6 +5,7 @@ import styled from '@emotion/styled';
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import FeedbackFilters from 'sentry/components/feedback/feedbackFilters';
 import FeedbackItemLoader from 'sentry/components/feedback/feedbackItem/feedbackItemLoader';
+import FeedbackWidgetBanner from 'sentry/components/feedback/feedbackOnboarding/feedbackWidgetBanner';
 import FeedbackSearch from 'sentry/components/feedback/feedbackSearch';
 import FeedbackSetupPanel from 'sentry/components/feedback/feedbackSetupPanel';
 import FeedbackWhatsNewBanner from 'sentry/components/feedback/feedbackWhatsNewBanner';
@@ -20,9 +21,12 @@ import * as Layout from 'sentry/components/layouts/thirds';
 import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
 import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import {feedbackWidgetPlatforms} from 'sentry/data/platformCategories';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import useOrganization from 'sentry/utils/useOrganization';
+import usePageFilters from 'sentry/utils/usePageFilters';
+import useProjects from 'sentry/utils/useProjects';
 import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
 
 interface Props extends RouteComponentProps<{}, {}, {}> {}
@@ -33,10 +37,25 @@ export default function FeedbackListPage({}: Props) {
   const {hasSetupNewFeedback} = useHaveSelectedProjectsSetupNewFeedback();
 
   const showWhatsNewBanner = hasSetupOneFeedback && !hasSetupNewFeedback;
+  const hasNewOnboarding = organization.features.includes('user-feedback-onboarding');
 
   const feedbackSlug = useCurrentFeedbackId();
   const hasSlug = Boolean(feedbackSlug);
 
+  const pageFilters = usePageFilters();
+  const projects = useProjects();
+
+  const selectedProjects = projects.projects.filter(p =>
+    pageFilters.selection.projects.includes(Number(p.id))
+  );
+
+  // one selected project is widget eligible
+  const oneIsWidgetEligible = selectedProjects.some(p =>
+    feedbackWidgetPlatforms.includes(p.platform!)
+  );
+
+  const showWidgetBanner = showWhatsNewBanner && oneIsWidgetEligible && hasNewOnboarding;
+
   return (
     <SentryDocumentTitle title={t('User Feedback')} orgSlug={organization.slug}>
       <FullViewport>
@@ -60,7 +79,9 @@ export default function FeedbackListPage({}: Props) {
           <PageFiltersContainer>
             <ErrorBoundary>
               <LayoutGrid data-banner={showWhatsNewBanner}>
-                {showWhatsNewBanner ? (
+                {showWidgetBanner ? (
+                  <FeedbackWidgetBanner style={{gridArea: 'banner'}} />
+                ) : showWhatsNewBanner ? (
                   <FeedbackWhatsNewBanner style={{gridArea: 'banner'}} />
                 ) : null}
                 <FeedbackFilters style={{gridArea: 'filters'}} />