Browse Source

feat(crons): Add disable button to monitor details (#61886)

Adds a button to the monitor header actions to toggle disabled / active
state of a monitor.

Looks like this

![clipboard.png](https://i.imgur.com/iBfR6u2.png)

When disabled

![clipboard.png](https://i.imgur.com/ircrN4H.png)
Evan Purkhiser 1 year ago
parent
commit
28f8f802e7

+ 1 - 1
static/app/views/monitors/components/detailsSidebar.tsx

@@ -57,7 +57,7 @@ export default function DetailsSidebar({monitorEnv, monitor}: Props) {
           )}
         </div>
         <div>
-          {monitorEnv?.nextCheckIn ? (
+          {monitor.status !== 'disabled' && monitorEnv?.nextCheckIn ? (
             <TimeSince
               unitStyle="regular"
               liveUpdateInterval="second"

+ 14 - 10
static/app/views/monitors/components/monitorHeaderActions.tsx

@@ -13,6 +13,8 @@ import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
 import {Monitor} from '../types';
 
+import {StatusToggleButton} from './statusToggleButton';
+
 type Props = {
   monitor: Monitor;
   onUpdate: (data: Monitor) => void;
@@ -45,26 +47,28 @@ function MonitorHeaderActions({monitor, orgId, onUpdate}: Props) {
     onUpdate?.(resp);
   };
 
-  const toggleStatus = () => handleUpdate({isMuted: !monitor.isMuted});
+  const toggleMute = () => handleUpdate({isMuted: !monitor.isMuted});
+
+  const toggleStatus = () =>
+    handleUpdate({status: monitor.status === 'active' ? 'disabled' : 'active'});
 
   return (
     <ButtonBar gap={1}>
       <FeedbackWidgetButton />
-      <Confirm
-        onConfirm={handleDelete}
-        message={t('Are you sure you want to permanently delete this cron monitor?')}
-      >
-        <Button size="sm" icon={<IconDelete />}>
-          {t('Delete')}
-        </Button>
-      </Confirm>
       <Button
         size="sm"
         icon={monitor.isMuted ? <IconSubscribed /> : <IconUnsubscribed />}
-        onClick={toggleStatus}
+        onClick={toggleMute}
       >
         {monitor.isMuted ? t('Unmute') : t('Mute')}
       </Button>
+      <StatusToggleButton size="sm" onClick={toggleStatus} monitor={monitor} />
+      <Confirm
+        onConfirm={handleDelete}
+        message={t('Are you sure you want to permanently delete this cron monitor?')}
+      >
+        <Button size="sm" icon={<IconDelete size="xs" />} aria-label={t('Delete')} />
+      </Confirm>
       <Button
         priority="primary"
         size="sm"

+ 23 - 0
static/app/views/monitors/components/statusToggleButton.tsx

@@ -0,0 +1,23 @@
+import {BaseButtonProps, Button} from 'sentry/components/button';
+import {IconPause, IconPlay} from 'sentry/icons';
+import {t} from 'sentry/locale';
+import {Monitor} from 'sentry/views/monitors/types';
+
+interface StatusToggleButtonProps extends BaseButtonProps {
+  monitor: Monitor;
+}
+
+function StatusToggleButton({monitor, ...props}: StatusToggleButtonProps) {
+  const {status} = monitor;
+  const isDisabeld = status === 'disabled';
+
+  const Icon = isDisabeld ? IconPlay : IconPause;
+
+  const label = isDisabeld
+    ? t('Reactive this monitor')
+    : t('Disable this monitor and discard incoming check-ins');
+
+  return <Button icon={<Icon />} aria-label={label} title={label} {...props} />;
+}
+
+export {StatusToggleButton};

+ 31 - 0
static/app/views/monitors/details.tsx

@@ -3,14 +3,18 @@ import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 import sortBy from 'lodash/sortBy';
 
+import {updateMonitor} from 'sentry/actionCreators/monitors';
+import Alert from 'sentry/components/alert';
 import * as Layout from 'sentry/components/layouts/thirds';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
 import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
 import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
+import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import {CronDetailsTimeline} from 'sentry/views/monitors/components/cronDetailsTimeline';
@@ -21,6 +25,7 @@ import MonitorHeader from './components/monitorHeader';
 import MonitorIssues from './components/monitorIssues';
 import MonitorStats from './components/monitorStats';
 import MonitorOnboarding from './components/onboarding';
+import {StatusToggleButton} from './components/statusToggleButton';
 import {Monitor} from './types';
 
 const DEFAULT_POLL_INTERVAL_MS = 5000;
@@ -32,6 +37,7 @@ function hasLastCheckIn(monitor: Monitor) {
 }
 
 function MonitorDetails({params, location}: Props) {
+  const api = useApi();
   const {selection} = usePageFilters();
 
   const organization = useOrganization();
@@ -70,6 +76,14 @@ function MonitorDetails({params, location}: Props) {
     setApiQueryData(queryClient, queryKey, updatedMonitor);
   }
 
+  const handleUpdate = async (data: Partial<Monitor>) => {
+    if (monitor === undefined) {
+      return;
+    }
+    const resp = await updateMonitor(api, organization.slug, monitor.slug, data);
+    onUpdate(resp);
+  };
+
   if (!monitor) {
     return (
       <Layout.Page>
@@ -90,6 +104,23 @@ function MonitorDetails({params, location}: Props) {
               <DatePageFilter />
               <EnvironmentPageFilter />
             </StyledPageFilterBar>
+            {monitor.status === 'disabled' && (
+              <Alert
+                type="muted"
+                showIcon
+                trailingItems={
+                  <StatusToggleButton
+                    monitor={monitor}
+                    size="xs"
+                    onClick={() => handleUpdate({status: 'active'})}
+                  >
+                    {t('Reactivate')}
+                  </StatusToggleButton>
+                }
+              >
+                {t('This monitor is disabled and is not accepting check-ins.')}
+              </Alert>
+            )}
             {!hasLastCheckIn(monitor) ? (
               <MonitorOnboarding monitor={monitor} />
             ) : (