Browse Source

feat(alerts): Add new onboarding illustration (#19682)

Evan Purkhiser 4 years ago
parent
commit
8a1640cdf0

+ 1 - 0
src/sentry/static/sentry/app/components/onboardingPanel.tsx

@@ -49,6 +49,7 @@ const StyledBox = styled('div')`
 `;
 
 const IlloBox = styled(StyledBox)`
+  position: relative;
   min-height: 100px;
   max-width: 300px;
   margin: ${space(2)} auto;

+ 52 - 69
src/sentry/static/sentry/app/views/alerts/list/index.tsx

@@ -33,6 +33,7 @@ import {promptsUpdate} from 'app/actionCreators/prompts';
 import {Incident} from '../types';
 import {TableLayout, TitleAndSparkLine} from './styles';
 import AlertListRow from './row';
+import Onboarding from './onboarding';
 
 const DEFAULT_QUERY_STATUS = 'open';
 
@@ -162,31 +163,23 @@ class IncidentsList extends AsyncComponent<Props, State & AsyncComponent['state'
     navigateTo(`/settings/${params.orgId}/projects/:projectId/alerts/`, router);
   };
 
-  tryRenderFirstVisit() {
+  tryRenderOnboarding() {
     const {firstVisitShown} = this.state;
 
     if (!firstVisitShown) {
       return null;
     }
 
-    return (
-      <WelcomeEmptyMessage
-        leftAligned
-        size="medium"
-        title={t('Find the signal in the noise')}
-        description={t(
-          'You’ve got 5 minutes, 2 million lines of code, and an inbox with 300 new messages. Alerts tell you what went wrong and why.'
-        )}
-        action={
-          <ButtonBar gap={1}>
-            <Button size="small" external href={DOCS_URL}>
-              {t('View Features')}
-            </Button>
-            <AddAlertRuleButton {...this.props} />
-          </ButtonBar>
-        }
-      />
+    const actions = (
+      <React.Fragment>
+        <Button size="small" external href={DOCS_URL}>
+          {t('View Features')}
+        </Button>
+        <AddAlertRuleButton {...this.props} />
+      </React.Fragment>
     );
+
+    return <Onboarding actions={actions} />;
   }
 
   tryRenderEmpty() {
@@ -222,13 +215,7 @@ class IncidentsList extends AsyncComponent<Props, State & AsyncComponent['state'
   }
 
   renderList() {
-    const {
-      loading,
-      incidentList,
-      incidentListPageLinks,
-      hasAlertRule,
-      firstVisitShown,
-    } = this.state;
+    const {loading, incidentList, incidentListPageLinks, hasAlertRule} = this.state;
 
     const {orgId} = this.props.params;
     const allProjectsFromIncidents = new Set(
@@ -243,45 +230,46 @@ class IncidentsList extends AsyncComponent<Props, State & AsyncComponent['state'
 
     return (
       <React.Fragment>
-        <Panel>
-          {!loading && !firstVisitShown && (
-            <StyledPanelHeader>
-              <TableLayout status={status}>
-                <PaddedTitleAndSparkLine status={status}>
-                  <div>{t('Alert')}</div>
-                  {status === 'open' && <div>{t('Graph')}</div>}
-                </PaddedTitleAndSparkLine>
-                <div>{t('Project')}</div>
-                <div>{t('Triggered')}</div>
-                {status === 'closed' && <div>{t('Duration')}</div>}
-                {status === 'closed' && <div>{t('Resolved')}</div>}
-              </TableLayout>
-            </StyledPanelHeader>
-          )}
-          {showLoadingIndicator ? (
-            <LoadingIndicator />
-          ) : (
-            this.tryRenderFirstVisit() ??
-            this.tryRenderEmpty() ?? (
-              <PanelBody>
-                <Projects orgId={orgId} slugs={Array.from(allProjectsFromIncidents)}>
-                  {({initiallyLoaded, projects}) =>
-                    incidentList.map(incident => (
-                      <AlertListRow
-                        key={incident.id}
-                        projectsLoaded={initiallyLoaded}
-                        projects={projects}
-                        incident={incident}
-                        orgId={orgId}
-                        filteredStatus={status}
-                      />
-                    ))
-                  }
-                </Projects>
-              </PanelBody>
-            )
-          )}
-        </Panel>
+        {this.tryRenderOnboarding() ?? (
+          <Panel>
+            {!loading && (
+              <StyledPanelHeader>
+                <TableLayout status={status}>
+                  <PaddedTitleAndSparkLine status={status}>
+                    <div>{t('Alert')}</div>
+                    {status === 'open' && <div>{t('Graph')}</div>}
+                  </PaddedTitleAndSparkLine>
+                  <div>{t('Project')}</div>
+                  <div>{t('Triggered')}</div>
+                  {status === 'closed' && <div>{t('Duration')}</div>}
+                  {status === 'closed' && <div>{t('Resolved')}</div>}
+                </TableLayout>
+              </StyledPanelHeader>
+            )}
+            {showLoadingIndicator ? (
+              <LoadingIndicator />
+            ) : (
+              this.tryRenderEmpty() ?? (
+                <PanelBody>
+                  <Projects orgId={orgId} slugs={Array.from(allProjectsFromIncidents)}>
+                    {({initiallyLoaded, projects}) =>
+                      incidentList.map(incident => (
+                        <AlertListRow
+                          key={incident.id}
+                          projectsLoaded={initiallyLoaded}
+                          projects={projects}
+                          incident={incident}
+                          orgId={orgId}
+                          filteredStatus={status}
+                        />
+                      ))
+                    }
+                  </Projects>
+                </PanelBody>
+              )
+            )}
+          </Panel>
+        )}
         <Pagination pageLinks={incidentListPageLinks} />
       </React.Fragment>
     );
@@ -443,9 +431,4 @@ const Actions = styled(ButtonBar)`
   height: 32px;
 `;
 
-const WelcomeEmptyMessage = styled(EmptyMessage)`
-  margin: ${space(4)};
-  max-width: 550px;
-`;
-
 export default withOrganization(IncidentsListContainer);

+ 55 - 0
src/sentry/static/sentry/app/views/alerts/list/onboarding.tsx

@@ -0,0 +1,55 @@
+import React from 'react';
+import styled from '@emotion/styled';
+
+import {t} from 'app/locale';
+import ButtonBar from 'app/components/buttonBar';
+import OnboardingPanel from 'app/components/onboardingPanel';
+import emptyStateImg from 'app/../images/spot/alerts-empty-state.svg';
+
+type Props = {
+  actions: React.ReactNode;
+};
+
+function Onboarding({actions}: Props) {
+  return (
+    <OnboardingPanel image={<AlertsImage src={emptyStateImg} />}>
+      <h3>{t('More signal, less noise')}</h3>
+      <p>
+        {t(
+          'Not every error is worth an email. Set your own rules for alerts you need, with information that helps.'
+        )}
+      </p>
+      <ButtonList gap={1}>{actions}</ButtonList>
+    </OnboardingPanel>
+  );
+}
+
+const AlertsImage = styled('img')`
+  @media (min-width: ${p => p.theme.breakpoints[0]}) {
+    user-select: none;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    width: 220px;
+    margin-top: auto;
+    margin-bottom: auto;
+    transform: translateX(-50%);
+    left: 50%;
+  }
+
+  @media (min-width: ${p => p.theme.breakpoints[2]}) {
+    transform: translateX(-60%);
+    width: 280px;
+  }
+
+  @media (min-width: ${p => p.theme.breakpoints[3]}) {
+    transform: translateX(-75%);
+    width: 320px;
+  }
+`;
+
+const ButtonList = styled(ButtonBar)`
+  grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
+`;
+
+export default Onboarding;

+ 2 - 1
src/sentry/static/sentry/app/views/performance/onboarding.tsx

@@ -39,9 +39,10 @@ function Onboarding() {
 
 const PerfImage = styled('img')`
   @media (min-width: ${p => p.theme.breakpoints[0]}) {
+    max-width: unset;
     user-select: none;
     position: absolute;
-    top: 0;
+    top: 50px;
     bottom: 0;
     width: 450px;
     margin-top: auto;

File diff suppressed because it is too large
+ 0 - 0
src/sentry/static/sentry/images/spot/alerts-empty-state.svg


+ 1 - 1
tests/js/spec/views/alerts/list/index.spec.jsx

@@ -145,7 +145,7 @@ describe('IncidentsList', function() {
     wrapper.update();
 
     expect(wrapper.find('PanelItem')).toHaveLength(0);
-    expect(wrapper.text()).toContain('Alerts tell you what went wrong and why');
+    expect(wrapper.find('Onboarding').text()).toContain('More signal, less noise');
   });
 
   it('displays empty state (rules not yet created)', async function() {

Some files were not shown because too many files changed in this diff