Browse Source

feat(orphan-errors-banner): Added banner logic for orphan errors. (#65351)

Added banner for promoting perf setup, when trace only has orphan
errors:
![Screenshot 2024-02-16 at 2 15 36
PM](https://github.com/getsentry/sentry/assets/60121741/d173c775-8d08-4952-8e30-c00d49833d61)
defaults to old banner when project platform doesn't support perf
onboarding sidebar

---------

Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
Abdkhan14 1 year ago
parent
commit
a5b8887476
1 changed files with 175 additions and 13 deletions
  1. 175 13
      static/app/views/performance/traceDetails/content.tsx

+ 175 - 13
static/app/views/performance/traceDetails/content.tsx

@@ -2,16 +2,24 @@ import {Component, createRef, Fragment} from 'react';
 import type {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
+import emptyStateImg from 'sentry-images/spot/performance-empty-state.svg';
+
 import {Alert} from 'sentry/components/alert';
 import GuideAnchor from 'sentry/components/assistant/guideAnchor';
+import {Button} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import DiscoverButton from 'sentry/components/discoverButton';
+import {DropdownMenu} from 'sentry/components/dropdownMenu';
 import * as Layout from 'sentry/components/layouts/thirds';
 import ExternalLink from 'sentry/components/links/externalLink';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {SidebarPanelKey} from 'sentry/components/sidebar/types';
 import TimeSince from 'sentry/components/timeSince';
+import {withPerformanceOnboarding} from 'sentry/data/platformCategories';
+import {IconClose} from 'sentry/icons';
 import {t, tct, tn} from 'sentry/locale';
+import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
 import {space} from 'sentry/styles/space';
 import type {Organization} from 'sentry/types';
 import {defined} from 'sentry/utils';
@@ -29,6 +37,8 @@ import type {
 } from 'sentry/utils/performance/quickTrace/types';
 import {filterTrace, reduceTrace} from 'sentry/utils/performance/quickTrace/utils';
 import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
+import useDismissAlert from 'sentry/utils/useDismissAlert';
+import useProjects from 'sentry/utils/useProjects';
 import Breadcrumb from 'sentry/views/performance/breadcrumb';
 import {MetaData} from 'sentry/views/performance/transactionDetails/styles';
 
@@ -303,19 +313,8 @@ class TraceDetailsContent extends Component<Props, State> {
           </ExternalLink>
         </Alert>
       );
-    } else if (orphanErrors && orphanErrors.length > 1) {
-      warning = (
-        <Alert type="info" showIcon>
-          {tct(
-            "The good news is we know these errors are related to each other. The bad news is that we can't tell you more than that. If you haven't already, [tracingLink: configure performance monitoring for your SDKs] to learn more about service interactions.",
-            {
-              tracingLink: (
-                <ExternalLink href="https://docs.sentry.io/product/performance/getting-started/" />
-              ),
-            }
-          )}
-        </Alert>
-      );
+    } else if (orphanErrors && orphanErrors.length > 0) {
+      warning = <OnlyOrphanErrorWarnings orphanErrors={orphanErrors} />;
     }
 
     return warning;
@@ -422,6 +421,169 @@ class TraceDetailsContent extends Component<Props, State> {
   }
 }
 
+type OnlyOrphanErrorWarningsProps = {
+  orphanErrors: TraceError[];
+};
+function OnlyOrphanErrorWarnings({orphanErrors}: OnlyOrphanErrorWarningsProps) {
+  const {projects} = useProjects();
+  const projectSlug = orphanErrors[0] ? orphanErrors[0].project_slug : '';
+  const project = projects.find(p => p.slug === projectSlug);
+  const LOCAL_STORAGE_KEY = `${project?.id}:performance-orphan-error-onboarding-banner-hide`;
+  const currentPlatform = project?.platform;
+  const hasPerformanceOnboarding = currentPlatform
+    ? withPerformanceOnboarding.has(currentPlatform)
+    : false;
+
+  const {dismiss: snooze, isDismissed: isSnoozed} = useDismissAlert({
+    key: LOCAL_STORAGE_KEY,
+    expirationDays: 7,
+  });
+
+  const {dismiss, isDismissed} = useDismissAlert({
+    key: LOCAL_STORAGE_KEY,
+    expirationDays: 365,
+  });
+
+  if (!orphanErrors.length) {
+    return null;
+  }
+
+  if (!hasPerformanceOnboarding) {
+    return (
+      <Alert type="info" showIcon>
+        {tct(
+          "The good news is we know these errors are related to each other. The bad news is that we can't tell you more than that. If you haven't already, [tracingLink: configure performance monitoring for your SDKs] to learn more about service interactions.",
+          {
+            tracingLink: (
+              <ExternalLink href="https://docs.sentry.io/product/performance/getting-started/" />
+            ),
+          }
+        )}
+      </Alert>
+    );
+  }
+
+  if (isDismissed || isSnoozed) {
+    return null;
+  }
+
+  return (
+    <BannerWrapper>
+      <div>
+        <BannerTitle>{t('Connect with Dots')}</BannerTitle>
+        <BannerDescription>
+          {t(
+            "Want to know why this string of errors happened? If you haven't already, update your SDK with this snippet to figure out what operations are running between your errors."
+          )}
+        </BannerDescription>
+        <ButtonsWrapper>
+          <ActionButton>
+            <Button
+              priority="primary"
+              onClick={event => {
+                event.preventDefault();
+                SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING);
+              }}
+            >
+              {t('Update SDKs')}
+            </Button>
+          </ActionButton>
+          <ActionButton>
+            <Button href="https://docs.sentry.io/product/performance/" external>
+              {t('Learn More')}
+            </Button>
+          </ActionButton>
+        </ButtonsWrapper>
+      </div>
+      {<Background image={emptyStateImg} />}
+      <CloseDropdownMenu
+        position="bottom-end"
+        triggerProps={{
+          showChevron: false,
+          borderless: true,
+          icon: <IconClose color="subText" />,
+        }}
+        size="xs"
+        items={[
+          {
+            key: 'dismiss',
+            label: t('Dismiss'),
+            onAction: () => {
+              dismiss();
+            },
+          },
+          {
+            key: 'snooze',
+            label: t('Snooze'),
+            onAction: () => {
+              snooze();
+            },
+          },
+        ]}
+      />
+    </BannerWrapper>
+  );
+}
+
+const BannerWrapper = styled('div')`
+  position: relative;
+  border: 1px solid ${p => p.theme.border};
+  border-radius: ${p => p.theme.borderRadius};
+  padding: 26px;
+  margin: ${space(1)} 0;
+  background: linear-gradient(
+    90deg,
+    ${p => p.theme.backgroundSecondary}00 0%,
+    ${p => p.theme.backgroundSecondary}FF 70%,
+    ${p => p.theme.backgroundSecondary}FF 100%
+  );
+`;
+
+const ButtonsWrapper = styled('div')`
+  display: flex;
+  align-items: center;
+  gap: ${space(0.5)};
+`;
+
+const BannerTitle = styled('div')`
+  font-size: ${p => p.theme.fontSizeExtraLarge};
+  margin-bottom: ${space(1)};
+  font-weight: 600;
+`;
+
+const BannerDescription = styled('div')`
+  margin-bottom: ${space(1.5)};
+`;
+
+const CloseDropdownMenu = styled(DropdownMenu)`
+  position: absolute;
+  display: block;
+  top: ${space(1)};
+  right: ${space(1)};
+  color: ${p => p.theme.white};
+  cursor: pointer;
+  z-index: 1;
+`;
+
+const Background = styled('div')<{image: any}>`
+  display: flex;
+  justify-self: flex-end;
+  position: absolute;
+  top: 6px;
+  right: 49px;
+  height: 100%;
+  width: 100%;
+  max-width: 413px;
+  background-image: url(${p => p.image});
+  background-repeat: no-repeat;
+  background-size: contain;
+`;
+
+const ActionButton = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+`;
+
 const StyledLoadingIndicator = styled(LoadingIndicator)`
   margin-bottom: 0;
 `;