Browse Source

feat(monitors): Add info to monitors header (#41257)

- Adds breadcrumbs to header
- Adds project icon into header
- More consistent with other headers in the app such as issue details

Before:
<img width="1213" alt="image"
src="https://user-images.githubusercontent.com/9372512/201309616-a23c6718-7a73-4770-b30c-82f5170ee91b.png">


After:
<img width="1214" alt="image"
src="https://user-images.githubusercontent.com/9372512/201309563-564aaed2-8824-4a8c-9a09-08ab03d0ef17.png">
David Wang 2 years ago
parent
commit
784ce79aca

+ 23 - 21
static/app/views/monitors/details.tsx

@@ -1,3 +1,4 @@
+import {Fragment} from 'react';
 import {RouteComponentProps} from 'react-router';
 
 import * as Layout from 'sentry/components/layouts/thirds';
@@ -43,27 +44,28 @@ class MonitorDetails extends AsyncView<Props, State> {
     }
 
     return (
-      <Layout.Body>
-        <Layout.Main fullWidth>
-          <MonitorHeader
-            monitor={monitor}
-            orgId={this.props.params.orgId}
-            onUpdate={this.onUpdate}
-          />
-
-          {!monitor.lastCheckIn && <MonitorOnboarding monitor={monitor} />}
-
-          <MonitorStats monitor={monitor} />
-
-          <MonitorIssues monitor={monitor} orgId={this.props.params.orgId} />
-
-          <Panel>
-            <PanelHeader>{t('Recent Check-ins')}</PanelHeader>
-
-            <MonitorCheckIns monitor={monitor} />
-          </Panel>
-        </Layout.Main>
-      </Layout.Body>
+      <Fragment>
+        <MonitorHeader
+          monitor={monitor}
+          orgId={this.props.params.orgId}
+          onUpdate={this.onUpdate}
+        />
+        <Layout.Body>
+          <Layout.Main fullWidth>
+            {!monitor.lastCheckIn && <MonitorOnboarding monitor={monitor} />}
+
+            <MonitorStats monitor={monitor} />
+
+            <MonitorIssues monitor={monitor} orgId={this.props.params.orgId} />
+
+            <Panel>
+              <PanelHeader>{t('Recent Check-ins')}</PanelHeader>
+
+              <MonitorCheckIns monitor={monitor} />
+            </Panel>
+          </Layout.Main>
+        </Layout.Body>
+      </Fragment>
     );
   }
 }

+ 75 - 23
static/app/views/monitors/monitorHeader.tsx

@@ -1,33 +1,85 @@
+import styled from '@emotion/styled';
+
+import Breadcrumbs from 'sentry/components/breadcrumbs';
+import {SectionHeading} from 'sentry/components/charts/styles';
+import IdBadge from 'sentry/components/idBadge';
+import * as Layout from 'sentry/components/layouts/thirds';
 import TimeSince from 'sentry/components/timeSince';
 import {t} from 'sentry/locale';
+import space from 'sentry/styles/space';
 
 import MonitorHeaderActions from './monitorHeaderActions';
 import MonitorIcon from './monitorIcon';
 
 type Props = React.ComponentProps<typeof MonitorHeaderActions>;
 
-const MonitorHeader = ({monitor, orgId, onUpdate}: Props) => (
-  <div className="release-details">
-    <div className="row">
-      <div className="col-sm-6 col-xs-10">
-        <h3>{monitor.name}</h3>
-        <div className="release-meta">{monitor.id}</div>
-      </div>
-      <div className="col-sm-2 hidden-xs">
-        <h6 className="nav-header">{t('Last Check-in')}</h6>
-        {monitor.lastCheckIn && <TimeSince date={monitor.lastCheckIn} />}
-      </div>
-      <div className="col-sm-2 hidden-xs">
-        <h6 className="nav-header">{t('Next Check-in')}</h6>
-        {monitor.nextCheckIn && <TimeSince date={monitor.nextCheckIn} />}
-      </div>
-      <div className="col-sm-2">
-        <h6 className="nav-header">{t('Status')}</h6>
-        <MonitorIcon status={monitor.status} size={16} />
-      </div>
-    </div>
-    <MonitorHeaderActions orgId={orgId} monitor={monitor} onUpdate={onUpdate} />
-  </div>
-);
+const MonitorHeader = ({monitor, orgId, onUpdate}: Props) => {
+  const crumbs = [
+    {
+      label: t('Monitors'),
+      to: `/organizations/${orgId}/monitors`,
+    },
+    {
+      label: t('Monitor Details'),
+    },
+  ];
+
+  return (
+    <Layout.Header>
+      <Layout.HeaderContent>
+        <Breadcrumbs crumbs={crumbs} />
+        <Layout.Title>
+          <MonitorName>
+            <IdBadge
+              project={monitor.project}
+              avatarSize={28}
+              hideName
+              avatarProps={{hasTooltip: true, tooltip: monitor.project.slug}}
+            />
+            {monitor.name}
+          </MonitorName>
+        </Layout.Title>
+        <MonitorId>{monitor.id}</MonitorId>
+      </Layout.HeaderContent>
+      <Layout.HeaderActions>
+        <MonitorHeaderActions orgId={orgId} monitor={monitor} onUpdate={onUpdate} />
+        <MonitorStats>
+          <MonitorStatLabel>{t('Last Check-in')}</MonitorStatLabel>
+          <MonitorStatLabel>{t('Next Check-in')}</MonitorStatLabel>
+          <MonitorStatLabel>{t('Status')}</MonitorStatLabel>
+          <div>{monitor.lastCheckIn && <TimeSince date={monitor.lastCheckIn} />}</div>
+          <div>{monitor.nextCheckIn && <TimeSince date={monitor.nextCheckIn} />}</div>
+          <MonitorIcon status={monitor.status} size={16} />
+        </MonitorStats>
+      </Layout.HeaderActions>
+    </Layout.Header>
+  );
+};
+
+const MonitorName = styled('div')`
+  display: grid;
+  grid-template-columns: max-content 1fr;
+  grid-column-gap: ${space(1)};
+  align-items: center;
+`;
+
+const MonitorId = styled('div')`
+  margin-top: ${space(1)};
+  color: ${p => p.theme.subText};
+`;
+
+const MonitorStats = styled('div')`
+  display: grid;
+  align-self: flex-end;
+  grid-template-columns: repeat(3, max-content);
+  grid-column-gap: ${space(4)};
+  grid-row-gap: ${space(0.5)};
+  margin-bottom: ${space(2)};
+`;
+
+const MonitorStatLabel = styled(SectionHeading)`
+  text-transform: uppercase;
+  font-size: ${p => p.theme.fontSizeSmall};
+`;
 
 export default MonitorHeader;

+ 1 - 0
static/app/views/monitors/monitorHeaderActions.tsx

@@ -117,6 +117,7 @@ const ButtonContainer = styled('div')`
   margin-bottom: ${space(3)};
   display: flex;
   flex-shrink: 1;
+  align-self: flex-end;
 `;
 
 export default MonitorHeaderActions;