Browse Source

feat(ui): Move "Incident Rules" into a new "Alert Rules" (#15331)

This creates a new "Alert Rules" clone that includes old alert rules and new incident alert rules (but separated using a tab navigation). This is only a stepping stone until we have updated mocks.
Billy Vong 5 years ago
parent
commit
11223d0f44

+ 77 - 33
src/sentry/static/sentry/app/routes.jsx

@@ -217,7 +217,15 @@ function routes() {
         }
         component={errorHandler(LazyLoad)}
       />
-      <Route name="Alerts" path="alerts/">
+
+      <Route
+        name="Alerts"
+        path="alerts/"
+        component={errorHandler(LazyLoad)}
+        componentPromise={() =>
+          import(/* webpackChunkName: "ProjectAlerts" */ 'app/views/settings/projectAlerts')
+        }
+      >
         <IndexRedirect to="rules/" />
         <Route
           path="settings/"
@@ -253,6 +261,74 @@ function routes() {
         </Route>
       </Route>
 
+      <Route
+        name="Alerts"
+        path="alerts-v2/"
+        component={errorHandler(LazyLoad)}
+        componentPromise={() =>
+          import(/* webpackChunkName: "ProjectAlertsNew" */ 'app/views/settings/projectAlerts/new')
+        }
+      >
+        <IndexRedirect to="issue-rules/" />
+        <Route
+          path="settings/"
+          name="Settings"
+          component={errorHandler(LazyLoad)}
+          componentPromise={() =>
+            import(/* webpackChunkName: "ProjectAlertSettings" */ 'app/views/settings/projectAlerts/projectAlertSettings')
+          }
+        />
+        <Route path="issue-rules/" name="Rules" component={null}>
+          <IndexRoute
+            component={errorHandler(LazyLoad)}
+            componentPromise={() =>
+              import(/* webpackChunkName: "ProjectAlertRules" */ 'app/views/settings/projectAlerts/projectAlertRulesNew')
+            }
+          />
+          <Route
+            path="new/"
+            name="New"
+            component={errorHandler(LazyLoad)}
+            componentPromise={() =>
+              import(/* webpackChunkName: "ProjectAlertRuleDetails" */ 'app/views/settings/projectAlerts/projectAlertRuleDetails')
+            }
+          />
+          <Route
+            path=":ruleId/"
+            name="Edit"
+            componentPromise={() =>
+              import(/* webpackChunkName: "ProjectAlertRuleDetails" */ 'app/views/settings/projectAlerts/projectAlertRuleDetails')
+            }
+            component={errorHandler(LazyLoad)}
+          />
+        </Route>
+
+        <Route path="event-rules/" name="Event Rules" component={null}>
+          <IndexRoute
+            componentPromise={() =>
+              import(/* webpackChunkName: "IncidentRulesList" */ 'app/views/settings/incidentRules/list')
+            }
+            component={errorHandler(LazyLoad)}
+          />
+          <Route
+            name="New Incident Rule"
+            path="new/"
+            componentPromise={() =>
+              import(/* webpackChunkName: "IncidentRulesCreate" */ 'app/views/settings/incidentRules/create')
+            }
+            component={errorHandler(LazyLoad)}
+          />
+          <Route
+            name="Edit Incident Rule"
+            path=":incidentRuleId/"
+            componentPromise={() =>
+              import(/* webpackChunkName: "IncidentRulesDetails" */ 'app/views/settings/incidentRules/details')
+            }
+            component={errorHandler(LazyLoad)}
+          />
+        </Route>
+      </Route>
+
       <Route
         name="Environments"
         path="environments/"
@@ -536,38 +612,6 @@ function routes() {
         />
       </Route>
 
-      <Route
-        name="Incident Rules"
-        path="incident-rules/"
-        componentPromise={() =>
-          import(/* webpackChunkName: "IncidentRules" */ 'app/views/settings/incidentRules')
-        }
-        component={errorHandler(LazyLoad)}
-      >
-        <IndexRoute
-          componentPromise={() =>
-            import(/* webpackChunkName: "IncidentRulesList" */ 'app/views/settings/incidentRules/list')
-          }
-          component={errorHandler(LazyLoad)}
-        />
-        <Route
-          name="New Incident Rule"
-          path="new/"
-          componentPromise={() =>
-            import(/* webpackChunkName: "IncidentRulesCreate" */ 'app/views/settings/incidentRules/create')
-          }
-          component={errorHandler(LazyLoad)}
-        />
-        <Route
-          name="Edit Incident Rule"
-          path=":incidentRuleId/"
-          componentPromise={() =>
-            import(/* webpackChunkName: "IncidentRulesDetails" */ 'app/views/settings/incidentRules/details')
-          }
-          component={errorHandler(LazyLoad)}
-        />
-      </Route>
-
       <Route
         path="rate-limits/"
         name="Rate Limits"

+ 2 - 0
src/sentry/static/sentry/app/types/index.tsx

@@ -2,6 +2,7 @@ import {SpanEntry} from 'app/components/events/interfaces/spans/types';
 import {API_SCOPES} from 'app/constants';
 import {Field} from 'app/views/settings/components/forms/type';
 import {Params} from 'react-router/lib/Router';
+import {PlainRoute} from 'react-router/lib/Route';
 import {Location} from 'history';
 
 export type ObjectStatus =
@@ -598,6 +599,7 @@ export type SentryAppComponent = {
 export type RouterProps = {
   params: Params;
   location: Location;
+  routes: PlainRoute[];
 };
 
 export type ActiveExperiments = {

+ 0 - 8
src/sentry/static/sentry/app/views/incidents/list/index.tsx

@@ -149,14 +149,6 @@ class IncidentsListContainer extends React.Component<Props> {
             </PageHeading>
 
             <Actions>
-              <Button
-                priority="default"
-                size="small"
-                to={`/settings/${orgId}/incident-rules/`}
-                icon="icon-settings"
-              >
-                {t('Manage Rules')}
-              </Button>
               <div className="btn-group">
                 <Button
                   to={{pathname, query: allIncidentsQuery}}

+ 16 - 10
src/sentry/static/sentry/app/views/settings/incidentRules/create.tsx

@@ -1,30 +1,36 @@
 import {RouteComponentProps} from 'react-router/lib/Router';
 import React from 'react';
 
-import {t} from 'app/locale';
-import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
+import recreateRoute from 'app/utils/recreateRoute';
 
 import RuleForm from './ruleForm';
 
-type RouteParams = {orgId: string};
+type RouteParams = {
+  orgId: string;
+  projectId: string;
+};
 type Props = {};
 
 class IncidentRulesCreate extends React.Component<
   RouteComponentProps<RouteParams, {}> & Props
 > {
   handleSubmitSuccess = data => {
-    const {orgId} = this.props.params;
-    this.props.router.push(`/settings/${orgId}/incident-rules/${data.id}/`);
+    const {params, routes, location} = this.props;
+
+    this.props.router.push(
+      recreateRoute(`${data.id}/`, {params, routes, location, stepBack: -1})
+    );
   };
 
   render() {
-    const {orgId} = this.props.params;
+    const {orgId, projectId} = this.props.params;
 
     return (
-      <div>
-        <SettingsPageHeader title={t('New Incident Rule')} />
-        <RuleForm orgId={orgId} onSubmitSuccess={this.handleSubmitSuccess} />
-      </div>
+      <RuleForm
+        orgId={orgId}
+        projectId={projectId}
+        onSubmitSuccess={this.handleSubmitSuccess}
+      />
     );
   }
 }

+ 5 - 5
src/sentry/static/sentry/app/views/settings/incidentRules/details.tsx

@@ -20,6 +20,7 @@ import withProjects from 'app/utils/withProjects';
 
 type RouteParams = {
   orgId: string;
+  projectId: string;
   incidentRuleId: string;
 };
 
@@ -143,15 +144,14 @@ class IncidentRulesDetails extends AsyncView<
   };
 
   renderBody() {
-    const {orgId, incidentRuleId} = this.props.params;
+    const {orgId, projectId, incidentRuleId} = this.props.params;
     const {rule} = this.state;
 
     return (
-      <div>
-        <SettingsPageHeader title={t('Edit Incident Rule')} />
-
+      <React.Fragment>
         <RuleForm
           saveOnBlur
+          projectId={projectId}
           orgId={orgId}
           incidentRuleId={incidentRuleId}
           initialData={rule}
@@ -177,7 +177,7 @@ class IncidentRulesDetails extends AsyncView<
           onDelete={this.handleDeleteTrigger}
           onEdit={this.handleEditTrigger}
         />
-      </div>
+      </React.Fragment>
     );
   }
 }

+ 25 - 39
src/sentry/static/sentry/app/views/settings/incidentRules/list.tsx

@@ -10,7 +10,7 @@ import Button from 'app/components/button';
 import Confirm from 'app/components/confirm';
 import EmptyMessage from 'app/views/settings/components/emptyMessage';
 import LoadingIndicator from 'app/components/loadingIndicator';
-import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
+import recreateRoute from 'app/utils/recreateRoute';
 import space from 'app/styles/space';
 
 import {IncidentRule} from './types';
@@ -23,6 +23,7 @@ type State = {
 
 type RouteParams = {
   orgId: string;
+  projectId: string;
 };
 
 type Props = RouteComponentProps<RouteParams, {}>;
@@ -60,44 +61,29 @@ class IncidentRulesList extends AsyncView<Props, State> {
   }
 
   renderBody() {
-    const {orgId} = this.props.params;
-    const action = (
-      <Button
-        priority="primary"
-        size="small"
-        to={`/settings/${orgId}/incident-rules/new/`}
-        icon="icon-circle-add"
-      >
-        {t('Create New Rule')}
-      </Button>
-    );
-
     const isLoading = this.state.loading;
-
     const isEmpty = !isLoading && !this.state.rules.length;
 
     return (
-      <div>
-        <SettingsPageHeader title={t('Incident Rules')} action={action} />
-        <Panel>
-          <GridPanelHeader>
-            <NameColumn>{t('Name')}</NameColumn>
+      <Panel>
+        <GridPanelHeader>
+          <NameColumn>{t('Name')}</NameColumn>
 
-            <div>{t('Metric')}</div>
+          <div>{t('Metric')}</div>
 
-            <div>{t('Threshold')}</div>
-          </GridPanelHeader>
+          <div>{t('Threshold')}</div>
+        </GridPanelHeader>
 
-          <PanelBody>
-            {isLoading && <LoadingIndicator />}
+        <PanelBody>
+          {isLoading && <LoadingIndicator />}
 
-            {!isLoading &&
-              !isEmpty &&
-              this.state.rules.map(rule => (
+          {!isLoading &&
+            !isEmpty &&
+            this.state.rules.map(rule => {
+              const ruleLink = recreateRoute(`${rule.id}/`, this.props);
+              return (
                 <RuleRow key={rule.id}>
-                  <RuleLink to={`/settings/${orgId}/incident-rules/${rule.id}/`}>
-                    {rule.name}
-                  </RuleLink>
+                  <RuleLink to={ruleLink}>{rule.name}</RuleLink>
 
                   <MetricName>{getMetricDisplayName(rule.aggregations[0])}</MetricName>
 
@@ -108,7 +94,7 @@ class IncidentRulesList extends AsyncView<Props, State> {
 
                     <Actions>
                       <Button
-                        to={`/settings/${orgId}/incident-rules/${rule.id}/`}
+                        to={ruleLink}
                         size="small"
                         icon="icon-edit"
                         aria-label={t('Edit Rule')}
@@ -131,14 +117,14 @@ class IncidentRulesList extends AsyncView<Props, State> {
                     </Actions>
                   </ThresholdColumn>
                 </RuleRow>
-              ))}
-
-            {!isLoading && isEmpty && (
-              <EmptyMessage>{t('No Incident rules have been created yet.')}</EmptyMessage>
-            )}
-          </PanelBody>
-        </Panel>
-      </div>
+              );
+            })}
+
+          {!isLoading && isEmpty && (
+            <EmptyMessage>{t('No Incident rules have been created yet.')}</EmptyMessage>
+          )}
+        </PanelBody>
+      </Panel>
     );
   }
 }

+ 6 - 18
src/sentry/static/sentry/app/views/settings/incidentRules/ruleForm.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import {Client} from 'app/api';
-import {Organization, Project} from 'app/types';
+import {Organization} from 'app/types';
 import {t} from 'app/locale';
 import Form from 'app/views/settings/components/forms/form';
 import FormField from 'app/views/settings/components/forms/formField';
@@ -9,7 +9,6 @@ import JsonForm from 'app/views/settings/components/forms/jsonForm';
 import SearchBar from 'app/views/events/searchBar';
 import withApi from 'app/utils/withApi';
 import withOrganization from 'app/utils/withOrganization';
-import withProjects from 'app/utils/withProjects';
 
 import {AlertRuleAggregations, IncidentRule, TimeWindow} from './types';
 import getMetricDisplayName from './utils/getMetricDisplayName';
@@ -20,7 +19,6 @@ type Props = {
   api: Client;
   organization: Organization;
   initialData?: IncidentRule;
-  projects: Project[];
 };
 
 type TimeWindowMapType = {[key in TimeWindow]: string};
@@ -39,7 +37,7 @@ const TIME_WINDOW_MAP: TimeWindowMapType = {
 
 class RuleForm extends React.Component<Props> {
   render() {
-    const {organization, projects} = this.props;
+    const {organization} = this.props;
 
     return (
       <JsonForm
@@ -55,17 +53,6 @@ class RuleForm extends React.Component<Props> {
                 placeholder: t('My Incident Rule Name'),
                 required: true,
               },
-              {
-                name: 'projects',
-                type: 'select',
-                label: t('Project'),
-                help: t('Select a project that this rule will apply to'),
-                choices: projects.map(({slug}) => [slug, slug]),
-                getValue: value => [value],
-                setValue: value => (value.length ? value[0] : []),
-                placeholder: t('Select a project'),
-                required: true,
-              },
               {
                 name: 'aggregations',
                 type: 'select',
@@ -132,6 +119,7 @@ class RuleForm extends React.Component<Props> {
 type RuleFormContainerProps = {
   initialData?: IncidentRule;
   orgId: string;
+  projectId: string;
   incidentRuleId?: string;
   saveOnBlur?: boolean;
 } & React.ComponentProps<typeof RuleForm> & {
@@ -140,6 +128,7 @@ type RuleFormContainerProps = {
 
 function RuleFormContainer({
   orgId,
+  projectId,
   incidentRuleId,
   initialData,
   saveOnBlur,
@@ -155,13 +144,12 @@ function RuleFormContainer({
       initialData={{
         query: '',
         aggregations: DEFAULT_METRIC,
-        projects: [],
+        projects: [projectId],
         includeAllProjects: false,
         excludedProjects: [],
 
         // TODO(incidents): Temp values
         alertThreshold: 5,
-        project_id: 1,
         resolveThreshold: 1,
         thresholdType: 0,
         timeWindow: 60,
@@ -176,4 +164,4 @@ function RuleFormContainer({
 }
 
 export {RuleFormContainer};
-export default withProjects(withApi(withOrganization(RuleFormContainer)));
+export default withApi(withOrganization(RuleFormContainer));

+ 0 - 7
src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx

@@ -32,13 +32,6 @@ const organizationNavigation = [
         description: t('Manage user membership for an organization'),
         id: 'members',
       },
-      {
-        path: `${pathPrefix}/incident-rules/`,
-        title: t('Incident Rules'),
-        show: ({features}) => features.has('incidents'),
-        description: t('Manage Incident Rules'),
-        id: 'incident-rules',
-      },
       {
         path: `${pathPrefix}/auth/`,
         title: t('Auth'),

+ 6 - 0
src/sentry/static/sentry/app/views/settings/project/navigationConfiguration.jsx

@@ -24,6 +24,12 @@ export default function getConfiguration({project}) {
           title: t('Alerts'),
           description: t('Manage alerts and alert rules for a project'),
         },
+        {
+          path: `${pathPrefix}/alerts-v2/`,
+          title: t('Alerts (New)'),
+          description: t('Manage alerts and alert rules for a project'),
+          show: ({features}) => features.has('incidents'),
+        },
         {
           path: `${pathPrefix}/tags/`,
           title: t('Tags'),

+ 20 - 0
src/sentry/static/sentry/app/views/settings/projectAlerts/index.tsx

@@ -0,0 +1,20 @@
+import {Params} from 'react-router/lib/Router';
+import React from 'react';
+
+import ProjectAlertHeader from './projectAlertHeader';
+
+type Props = {
+  params: Params;
+  children: React.ReactNode;
+};
+
+function ProjectAlerts({params, children}: Props) {
+  return (
+    <React.Fragment>
+      <ProjectAlertHeader projectId={params.projectId} />
+      {children}
+    </React.Fragment>
+  );
+}
+
+export default ProjectAlerts;

Some files were not shown because too many files changed in this diff