Browse Source

feat(crons): Add `actions` column (#44931)

Evan Purkhiser 2 years ago
parent
commit
b775816f17

+ 45 - 0
static/app/actionCreators/monitors.tsx

@@ -0,0 +1,45 @@
+import {
+  addErrorMessage,
+  addLoadingMessage,
+  clearIndicators,
+} from 'sentry/actionCreators/indicator';
+import {Client} from 'sentry/api';
+import {t} from 'sentry/locale';
+import {logException} from 'sentry/utils/logging';
+import {Monitor} from 'sentry/views/monitors/types';
+
+export async function deleteMonitor(api: Client, orgId: string, monitorId: string) {
+  addLoadingMessage(t('Deleting Monitor...'));
+
+  try {
+    await api.requestPromise(`/organizations/${orgId}/monitors/${monitorId}/`, {
+      method: 'DELETE',
+    });
+    clearIndicators();
+  } catch {
+    addErrorMessage(t('Unable to remove monitor.'));
+  }
+}
+
+export async function updateMonitor(
+  api: Client,
+  orgId: string,
+  monitorId: string,
+  data: Partial<Monitor>
+) {
+  addLoadingMessage();
+
+  try {
+    const resp = await api.requestPromise(
+      `/organizations/${orgId}/monitors/${monitorId}/`,
+      {method: 'PUT', data}
+    );
+    clearIndicators();
+    return resp;
+  } catch (err) {
+    logException(err);
+    addErrorMessage(t('Unable to update monitor.'));
+  }
+
+  return null;
+}

+ 8 - 36
static/app/views/monitors/monitorHeaderActions.tsx

@@ -1,16 +1,11 @@
 import {browserHistory} from 'react-router';
 
-import {
-  addErrorMessage,
-  addLoadingMessage,
-  clearIndicators,
-} from 'sentry/actionCreators/indicator';
+import {deleteMonitor, updateMonitor} from 'sentry/actionCreators/monitors';
 import {Button} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import Confirm from 'sentry/components/confirm';
 import {IconDelete, IconEdit, IconPause, IconPlay} from 'sentry/icons';
 import {t} from 'sentry/locale';
-import {logException} from 'sentry/utils/logging';
 import useApi from 'sentry/utils/useApi';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
@@ -26,41 +21,18 @@ type Props = {
 const MonitorHeaderActions = ({monitor, orgId, onUpdate}: Props) => {
   const api = useApi();
 
-  const handleDelete = () => {
-    const redirectPath = `/organizations/${orgId}/crons/`;
-    addLoadingMessage(t('Deleting Monitor...'));
-
-    api
-      .requestPromise(`/organizations/${orgId}/monitors/${monitor.id}/`, {
-        method: 'DELETE',
-      })
-      .then(() => {
-        browserHistory.push(normalizeUrl(redirectPath));
-      })
-      .catch(() => {
-        addErrorMessage(t('Unable to remove monitor.'));
-      });
+  const handleDelete = async () => {
+    await deleteMonitor(api, orgId, monitor.id);
+    browserHistory.push(normalizeUrl(`/organizations/${orgId}/crons/`));
   };
 
-  const updateMonitor = (data: Partial<Monitor>) => {
-    addLoadingMessage();
-    api
-      .requestPromise(`/organizations/${orgId}/monitors/${monitor.id}/`, {
-        method: 'PUT',
-        data,
-      })
-      .then(resp => {
-        clearIndicators();
-        onUpdate?.(resp);
-      })
-      .catch(err => {
-        logException(err);
-        addErrorMessage(t('Unable to update monitor.'));
-      });
+  const handleUpdate = async (data: Partial<Monitor>) => {
+    const resp = await updateMonitor(api, orgId, monitor.id, data);
+    onUpdate?.(resp);
   };
 
   const toggleStatus = () =>
-    updateMonitor({
+    handleUpdate({
       status:
         monitor.status === MonitorStatus.DISABLED
           ? MonitorStatus.ACTIVE

+ 7 - 1
static/app/views/monitors/monitors.tsx

@@ -141,12 +141,18 @@ class Monitors extends AsyncView<Props, State> {
                     t('Schedule'),
                     t('Next Checkin'),
                     t('Project'),
+                    t('Actions'),
                   ]}
                 >
                   {monitorList?.map(monitor => (
                     <MonitorRow
                       key={monitor.id}
                       monitor={monitor}
+                      onDelete={() => {
+                        this.setState({
+                          monitorList: monitorList.filter(m => m.id !== monitor.id),
+                        });
+                      }}
                       organization={organization}
                     />
                   ))}
@@ -186,7 +192,7 @@ const Filters = styled('div')`
 `;
 
 const StyledPanelTable = styled(PanelTable)`
-  grid-template-columns: 1fr max-content max-content max-content max-content;
+  grid-template-columns: 1fr max-content max-content max-content max-content max-content;
 `;
 
 const ButtonList = styled(ButtonBar)`

+ 54 - 1
static/app/views/monitors/row.tsx

@@ -2,20 +2,27 @@ import {Fragment} from 'react';
 import styled from '@emotion/styled';
 import cronstrue from 'cronstrue';
 
+import {deleteMonitor} from 'sentry/actionCreators/monitors';
+import {openConfirmModal} from 'sentry/components/confirm';
+import {DropdownMenu, MenuItemProps} from 'sentry/components/dropdownMenu';
 import IdBadge from 'sentry/components/idBadge';
 import Link from 'sentry/components/links/link';
 import TextOverflow from 'sentry/components/textOverflow';
 import TimeSince from 'sentry/components/timeSince';
+import {IconEllipsis} from 'sentry/icons';
 import {t, tct, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
 import {shouldUse24Hours} from 'sentry/utils/dates';
+import useApi from 'sentry/utils/useApi';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
 import {MonitorBadge} from './monitorBadge';
 import {Monitor, MonitorConfig, MonitorStatus, ScheduleType} from './types';
 
 interface MonitorRowProps {
   monitor: Monitor;
+  onDelete: () => void;
   organization: Organization;
 }
 
@@ -55,9 +62,37 @@ function scheduleAsText(config: MonitorConfig) {
   return t('Unknown schedule');
 }
 
-function MonitorRow({monitor, organization}: MonitorRowProps) {
+function MonitorRow({monitor, organization, onDelete}: MonitorRowProps) {
+  const api = useApi();
   const lastCheckin = <TimeSince unitStyle="regular" date={monitor.lastCheckIn} />;
 
+  const actions: MenuItemProps[] = [
+    {
+      key: 'edit',
+      label: t('Edit'),
+      to: normalizeUrl(`/organizations/${organization.slug}/crons/${monitor.id}/edit/`),
+    },
+    {
+      key: 'delete',
+      label: t('Delete'),
+      priority: 'danger',
+      onAction: () => {
+        openConfirmModal({
+          onConfirm: async () => {
+            await deleteMonitor(api, organization.slug, monitor.id);
+            onDelete();
+          },
+          header: t('Delete Monitor?'),
+          message: tct('Are you sure you want to permanently delete [name]?', {
+            name: monitor.name,
+          }),
+          confirmText: t('Delete Monitor'),
+          priority: 'danger',
+        });
+      },
+    },
+  ];
+
   return (
     <Fragment>
       <MonitorName>
@@ -100,6 +135,18 @@ function MonitorRow({monitor, organization}: MonitorRowProps) {
           avatarProps={{hasTooltip: true, tooltip: monitor.project.slug}}
         />
       </ProjectColumn>
+      <ActionsColumn>
+        <DropdownMenu
+          items={actions}
+          position="bottom-end"
+          triggerProps={{
+            'aria-label': t('Actions'),
+            size: 'xs',
+            icon: <IconEllipsis size="xs" />,
+            showChevron: false,
+          }}
+        />
+      </ActionsColumn>
     </Fragment>
   );
 }
@@ -132,3 +179,9 @@ const ProjectColumn = styled('div')`
   display: flex;
   align-items: center;
 `;
+
+const ActionsColumn = styled('div')`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;