Browse Source

feat(crons): Add basic alert rule editing (#49887)

When editing monitors you can now specify the target user for the
auto-generated alert rule.

<img width="674" alt="image"
src="https://github.com/getsentry/sentry/assets/1421724/04691b52-3913-43b6-9b8e-e8e83709c55a">

---------

Co-authored-by: Richard Ortenberg <richard.ortenberg@sentry.io>
Evan Purkhiser 1 year ago
parent
commit
5a0147de0e

+ 40 - 32
static/app/views/monitors/components/monitorForm.tsx

@@ -45,6 +45,14 @@ const SCHEDULE_OPTIONS: RadioOption<string>[] = [
 const DEFAULT_MONITOR_TYPE = 'cron_job';
 const DEFAULT_CRONTAB = '0 0 * * *';
 
+// Maps the value from the SentryMemberTeamSelectorField -> the expected alert
+// rule key and vice-versa.
+//
+// XXX(epurkhiser): For whatever reason the rules API wants the team and member
+// to be capitalized.
+const RULE_TARGET_MAP = {team: 'Team', member: 'Member'} as const;
+const RULES_SELECTOR_MAP = {Team: 'team', Member: 'member'} as const;
+
 export const DEFAULT_MAX_RUNTIME = 30;
 
 const getIntervals = (n: number): SelectValue<string>[] => [
@@ -78,9 +86,7 @@ function transformData(_data: Record<string, any>, model: FormModel) {
         // See SentryMemberTeamSelectorField to understand why these are strings
         const [type, id] = item.split(':');
 
-        // XXX(epurkhiser): For whateve reason the rules API wants the team and
-        // mebmer to be capitalized.
-        const targetType = {team: 'Team', member: 'Member'}[type];
+        const targetType = RULE_TARGET_MAP[type];
 
         return {targetType, targetIdentifier: id};
       });
@@ -179,6 +185,10 @@ function MonitorForm({
   const isSuperuser = isActiveSuperuser();
   const filteredProjects = projects.filter(project => isSuperuser || project.isMember);
 
+  const alertRule = monitor?.alertRule?.targets.map(
+    target => `${RULES_SELECTOR_MAP[target.targetType]}:${target.targetIdentifier}`
+  );
+
   return (
     <Form
       allowUndo
@@ -193,6 +203,7 @@ function MonitorForm({
               slug: monitor.slug,
               type: monitor.type ?? DEFAULT_MONITOR_TYPE,
               project: monitor.project.slug,
+              alertRule,
               ...formDataFromConfig(monitor.type, monitor.config),
             }
           : {
@@ -362,35 +373,32 @@ function MonitorForm({
             inline={false}
           />
         </InputGroup>
-        {(monitor === undefined || monitor.config.alert_rule_id) && (
-          <Fragment>
-            <StyledListItem>{t('Notify members')}</StyledListItem>
-            <ListItemSubText>
-              {t(
-                'Tell us who to notify when a check-in reaches the thresholds above or has an error. You can send notifications to members or teams.'
-              )}
-            </ListItemSubText>
-            <InputGroup>
-              {monitor === undefined ? (
-                <StyledSentryMemberTeamSelectorField
-                  name="alertRule"
-                  multiple
-                  stacked
-                  inline={false}
-                />
-              ) : (
-                <AlertLink
-                  priority="muted"
-                  to={normalizeUrl(
-                    `/alerts/rules/${monitor.project.slug}/${monitor.config.alert_rule_id}/`
-                  )}
-                >
-                  {t('Customize this monitors notification configuration in Alerts')}
-                </AlertLink>
-              )}
-            </InputGroup>
-          </Fragment>
-        )}
+        <Fragment>
+          <StyledListItem>{t('Notify members')}</StyledListItem>
+          <ListItemSubText>
+            {t(
+              'Tell us who to notify when a check-in reaches the thresholds above or has an error. You can send notifications to members or teams.'
+            )}
+          </ListItemSubText>
+          <InputGroup>
+            {monitor?.config.alert_rule_id && (
+              <AlertLink
+                priority="muted"
+                to={normalizeUrl(
+                  `/alerts/rules/${monitor.project.slug}/${monitor.config.alert_rule_id}/`
+                )}
+              >
+                {t('Customize this monitors notification configuration in Alerts')}
+              </AlertLink>
+            )}
+            <StyledSentryMemberTeamSelectorField
+              name="alertRule"
+              multiple
+              stacked
+              inline={false}
+            />
+          </InputGroup>
+        </Fragment>
       </StyledList>
     </Form>
   );

+ 12 - 4
static/app/views/monitors/edit.tsx

@@ -9,7 +9,12 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
 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 {
+  ApiQueryKey,
+  setApiQueryData,
+  useApiQuery,
+  useQueryClient,
+} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import {useParams} from 'sentry/utils/useParams';
@@ -24,19 +29,22 @@ export default function EditMonitor() {
   const organization = useOrganization();
   const queryClient = useQueryClient();
 
-  const queryKeyUrl = `/organizations/${organization.slug}/monitors/${monitorSlug}/`;
+  const queryKey: ApiQueryKey = [
+    `/organizations/${organization.slug}/monitors/${monitorSlug}/`,
+    {query: {expand: ['alertRule']}},
+  ];
 
   const {
     isLoading,
     isError,
     data: monitor,
     refetch,
-  } = useApiQuery<Monitor>([queryKeyUrl], {
+  } = useApiQuery<Monitor>(queryKey, {
     staleTime: 0,
   });
 
   function onSubmitSuccess(data: Monitor) {
-    setApiQueryData(queryClient, [queryKeyUrl], data);
+    setApiQueryData(queryClient, queryKey, data);
     browserHistory.push(
       normalizeUrl({
         pathname: `/organizations/${organization.slug}/crons/${data.slug}/`,

+ 6 - 0
static/app/views/monitors/types.tsx

@@ -87,6 +87,12 @@ export interface Monitor {
   slug: string;
   status: ObjectStatus;
   type: MonitorType;
+  alertRule?: {
+    targets: Array<{
+      targetIdentifier: number;
+      targetType: 'Member' | 'Team';
+    }>;
+  };
 }
 
 export interface MonitorStat {