Browse Source

feat(crons): Filter monitor details to specified environment (#46819)

Also reads status, next checkin, last checkin, etc from the monitor
environment
Evan Purkhiser 1 year ago
parent
commit
44d169b3b1

+ 4 - 3
static/app/views/monitors/components/monitorCheckIns.tsx

@@ -12,7 +12,7 @@ import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {defined} from 'sentry/utils';
 import useApiRequests from 'sentry/utils/useApiRequests';
-import {CheckInStatus, Monitor} from 'sentry/views/monitors/types';
+import {CheckInStatus, Monitor, MonitorEnvironment} from 'sentry/views/monitors/types';
 
 import CheckInIcon from './checkInIcon';
 
@@ -26,6 +26,7 @@ type CheckIn = {
 
 type Props = {
   monitor: Monitor;
+  monitorEnv: MonitorEnvironment;
   orgId: string;
 };
 
@@ -33,13 +34,13 @@ type State = {
   checkInList: CheckIn[];
 };
 
-const MonitorCheckIns = ({monitor, orgId}: Props) => {
+const MonitorCheckIns = ({monitor, monitorEnv, orgId}: Props) => {
   const {data, hasError, renderComponent} = useApiRequests<State>({
     endpoints: [
       [
         'checkInList',
         `/organizations/${orgId}/monitors/${monitor.slug}/checkins/`,
-        {query: {per_page: '10'}},
+        {query: {per_page: '10', environment: monitorEnv.name}},
         {paginate: true},
       ],
     ],

+ 22 - 11
static/app/views/monitors/components/monitorHeader.tsx

@@ -10,12 +10,17 @@ import {IconCopy} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 
-import {MonitorStatus} from '../types';
+import {Monitor, MonitorEnvironment, MonitorStatus} from '../types';
 
 import MonitorHeaderActions from './monitorHeaderActions';
 import MonitorIcon from './monitorIcon';
 
-type Props = React.ComponentProps<typeof MonitorHeaderActions>;
+interface Props {
+  monitor: Monitor;
+  onUpdate: (data: Monitor) => void;
+  orgId: string;
+  monitorEnv?: MonitorEnvironment;
+}
 
 const statusToLabel: Record<MonitorStatus, string> = {
   ok: t('Ok'),
@@ -25,7 +30,7 @@ const statusToLabel: Record<MonitorStatus, string> = {
   missed_checkin: t('Missed'),
 };
 
-const MonitorHeader = ({monitor, orgId, onUpdate}: Props) => {
+const MonitorHeader = ({monitor, monitorEnv, orgId, onUpdate}: Props) => {
   const crumbs = [
     {
       label: t('Crons'),
@@ -62,27 +67,33 @@ const MonitorHeader = ({monitor, orgId, onUpdate}: Props) => {
           <MonitorStatLabel>{t('Next Check-in')}</MonitorStatLabel>
           <MonitorStatLabel>{t('Status')}</MonitorStatLabel>
           <div>
-            {monitor.lastCheckIn && (
+            {monitorEnv?.lastCheckIn && (
               <TimeSince
                 unitStyle="regular"
                 liveUpdateInterval="second"
-                date={monitor.lastCheckIn}
+                date={monitorEnv.lastCheckIn}
               />
             )}
           </div>
           <div>
-            {monitor.nextCheckIn && (
+            {monitorEnv?.nextCheckIn && (
               <TimeSince
                 unitStyle="regular"
                 liveUpdateInterval="second"
-                date={monitor.nextCheckIn}
+                date={monitorEnv.nextCheckIn}
               />
             )}
           </div>
-          <Status>
-            <MonitorIcon status={monitor.status} size={16} />
-            <MonitorStatusLabel>{statusToLabel[monitor.status]}</MonitorStatusLabel>
-          </Status>
+          <div>
+            {monitorEnv?.status && (
+              <Status>
+                <MonitorIcon status={monitorEnv.status} size={16} />
+                <MonitorStatusLabel>
+                  {statusToLabel[monitorEnv.status]}
+                </MonitorStatusLabel>
+              </Status>
+            )}
+          </div>
         </MonitorStats>
       </Layout.HeaderActions>
     </Layout.Header>

+ 4 - 1
static/app/views/monitors/components/monitorIssues.tsx

@@ -5,10 +5,11 @@ import {t} from 'sentry/locale';
 import {getUtcDateString} from 'sentry/utils/dates';
 import usePageFilters from 'sentry/utils/usePageFilters';
 
-import {Monitor} from '../types';
+import {Monitor, MonitorEnvironment} from '../types';
 
 type Props = {
   monitor: Monitor;
+  monitorEnv: MonitorEnvironment;
   orgId: string;
 };
 
@@ -35,6 +36,8 @@ const MonitorIssues = ({orgId, monitor}: Props) => {
           statsPeriod: period,
         };
 
+  // TODO(epurkhiser): We probably want to filter on envrionemnt
+
   return (
     <GroupList
       orgId={orgId}

+ 4 - 2
static/app/views/monitors/components/monitorStats.tsx

@@ -13,10 +13,11 @@ import theme from 'sentry/utils/theme';
 import useApiRequests from 'sentry/utils/useApiRequests';
 import usePageFilters from 'sentry/utils/usePageFilters';
 
-import {Monitor, MonitorStat} from '../types';
+import {Monitor, MonitorEnvironment, MonitorStat} from '../types';
 
 type Props = {
   monitor: Monitor;
+  monitorEnv: MonitorEnvironment;
   orgId: string;
 };
 
@@ -24,7 +25,7 @@ type State = {
   stats: MonitorStat[] | null;
 };
 
-const MonitorStats = ({monitor, orgId}: Props) => {
+const MonitorStats = ({monitor, monitorEnv, orgId}: Props) => {
   const {selection} = usePageFilters();
   const {start, end, period} = selection.datetime;
 
@@ -48,6 +49,7 @@ const MonitorStats = ({monitor, orgId}: Props) => {
             since: since.toString(),
             until: until.toString(),
             resolution: '1d',
+            environment: monitorEnv.name,
           },
         },
       ],

+ 58 - 61
static/app/views/monitors/details.tsx

@@ -4,14 +4,13 @@ import styled from '@emotion/styled';
 
 import DatePageFilter from 'sentry/components/datePageFilter';
 import * as Layout from 'sentry/components/layouts/thirds';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
 import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {space} from 'sentry/styles/space';
-import {Organization} from 'sentry/types';
-import withRouteAnalytics, {
-  WithRouteAnalyticsProps,
-} from 'sentry/utils/routeAnalytics/withRouteAnalytics';
-import withOrganization from 'sentry/utils/withOrganization';
-import AsyncView from 'sentry/views/asyncView';
+import {useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
+import useOrganization from 'sentry/utils/useOrganization';
+import usePageFilters from 'sentry/utils/usePageFilters';
 
 import MonitorCheckIns from './components/monitorCheckIns';
 import MonitorHeader from './components/monitorHeader';
@@ -20,88 +19,86 @@ import MonitorStats from './components/monitorStats';
 import MonitorOnboarding from './components/onboarding';
 import {Monitor} from './types';
 
-type Props = AsyncView['props'] &
-  WithRouteAnalyticsProps &
-  RouteComponentProps<{monitorSlug: string}, {}> & {
-    organization: Organization;
-  };
+type Props = RouteComponentProps<{monitorSlug: string}, {}>;
 
-type State = AsyncView['state'] & {
-  monitor: Monitor | null;
-};
+function MonitorDetails({params, location}: Props) {
+  const {selection} = usePageFilters();
 
-class MonitorDetails extends AsyncView<Props, State> {
-  get orgSlug() {
-    return this.props.organization.slug;
-  }
+  const organization = useOrganization();
+  const queryClient = useQueryClient();
 
-  getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
-    const {params, location} = this.props;
-    return [
-      [
-        'monitor',
-        `/organizations/${this.orgSlug}/monitors/${params.monitorSlug}/`,
-        {query: location.query},
-      ],
-    ];
-  }
+  // TODO(epurkhiser): For now we just use the fist environment OR production
+  // if we have all environments selected
+  const environment = selection.environments[0] ?? 'production';
 
-  getTitle() {
-    if (this.state.monitor) {
-      return `${this.state.monitor.name} - Monitors - ${this.orgSlug}`;
-    }
-    return `Monitors - ${this.orgSlug}`;
-  }
+  const queryKey = [
+    `/organizations/${organization.slug}/monitors/${params.monitorSlug}/`,
+    {query: {...location.query, environment}},
+  ] as const;
 
-  onUpdate = (data: Monitor) =>
-    this.setState(state => ({monitor: {...state.monitor, ...data}}));
+  const {data: monitor} = useApiQuery<Monitor>(queryKey, {staleTime: 0});
 
-  onRequestSuccess(response) {
-    this.props.setEventNames(
-      'monitors.details_page_viewed',
-      'Monitors: Details Page Viewed'
-    );
-    this.props.setRouteAnalyticsParams({
-      empty_state: !response.data?.lastCheckIn,
-    });
+  function onUpdate(data: Monitor) {
+    queryClient.setQueryData(queryKey, data);
   }
 
-  renderBody() {
-    const {monitor} = this.state;
+  if (!monitor) {
+    return (
+      <Layout.Page>
+        <LoadingIndicator />
+      </Layout.Page>
+    );
+  }
 
-    if (monitor === null) {
-      return null;
-    }
+  const monitorEnv = monitor.environments.find(env => env.name === environment);
 
-    return (
+  return (
+    <SentryDocumentTitle title={`Crons - ${monitor.name}`}>
       <Layout.Page>
-        <MonitorHeader monitor={monitor} orgId={this.orgSlug} onUpdate={this.onUpdate} />
+        <MonitorHeader
+          monitor={monitor}
+          monitorEnv={monitorEnv}
+          orgId={organization.slug}
+          onUpdate={onUpdate}
+        />
         <Layout.Body>
           <Layout.Main fullWidth>
-            {!monitor.lastCheckIn ? (
-              <MonitorOnboarding orgId={this.orgSlug} monitor={monitor} />
+            {!monitorEnv?.lastCheckIn ? (
+              <MonitorOnboarding orgId={organization.slug} monitor={monitor} />
             ) : (
               <Fragment>
                 <StyledPageFilterBar condensed>
                   <DatePageFilter alignDropdown="left" />
                 </StyledPageFilterBar>
 
-                <MonitorStats monitor={monitor} orgId={this.orgSlug} />
-
-                <MonitorIssues monitor={monitor} orgId={this.orgSlug} />
-
-                <MonitorCheckIns monitor={monitor} orgId={this.orgSlug} />
+                <MonitorStats
+                  orgId={organization.slug}
+                  monitor={monitor}
+                  monitorEnv={monitorEnv}
+                />
+
+                <MonitorIssues
+                  orgId={organization.slug}
+                  monitor={monitor}
+                  monitorEnv={monitorEnv}
+                />
+
+                <MonitorCheckIns
+                  orgId={organization.slug}
+                  monitor={monitor}
+                  monitorEnv={monitorEnv}
+                />
               </Fragment>
             )}
           </Layout.Main>
         </Layout.Body>
       </Layout.Page>
-    );
-  }
+    </SentryDocumentTitle>
+  );
 }
 
 const StyledPageFilterBar = styled(PageFilterBar)`
   margin-bottom: ${space(2)};
 `;
 
-export default withRouteAnalytics(withOrganization(MonitorDetails));
+export default MonitorDetails;