Skip to content

Commit 0ffd317

Browse files
authored
feat(ui): Add basic templates for Incident Rules in settings (#14112)
This just adds some placeholder views, routes, and tests for the new Incident Rules feature. These will change as we develop the feature.
1 parent b484542 commit 0ffd317

File tree

11 files changed

+303
-3
lines changed

11 files changed

+303
-3
lines changed

src/sentry/static/sentry/app/components/charts/baseChart.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class BaseChart extends React.Component {
108108
onFinished: PropTypes.func,
109109

110110
// Forwarded Ref
111-
forwardedRef: PropTypes.object,
111+
forwardedRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
112112

113113
// Custom chart props that are implemented by us (and not a feature of eCharts)
114114
/**
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'echarts/lib/component/graphic';
2+
3+
/**
4+
* eCharts graphic
5+
*
6+
* See https://echarts.apache.org/en/option.html#graphic
7+
*/
8+
export default function Graphic(props) {
9+
return props;
10+
}

src/sentry/static/sentry/app/routes.jsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ function routes() {
603603
component={errorHandler(LazyLoad)}
604604
/>
605605
</Route>
606+
606607
<Route name="Developer Settings" path="developer-settings/">
607608
<IndexRoute
608609
componentPromise={() =>
@@ -627,6 +628,38 @@ function routes() {
627628
component={errorHandler(LazyLoad)}
628629
/>
629630
</Route>
631+
632+
<Route
633+
name="Incident Rules"
634+
path="incident-rules/"
635+
componentPromise={() =>
636+
import(/* webpackChunkName: "OrganizationIncidentRules" */ 'app/views/settings/organizationIncidentRules')
637+
}
638+
component={errorHandler(LazyLoad)}
639+
>
640+
<IndexRoute
641+
componentPromise={() =>
642+
import(/* webpackChunkName: "IncidentRulesList" */ 'app/views/settings/organizationIncidentRules/list')
643+
}
644+
component={errorHandler(LazyLoad)}
645+
/>
646+
<Route
647+
name="New Incident Rule"
648+
path="new/"
649+
componentPromise={() =>
650+
import(/* webpackChunkName: "IncidentRulesCreate" */ 'app/views/settings/organizationIncidentRules/create')
651+
}
652+
component={errorHandler(LazyLoad)}
653+
/>
654+
<Route
655+
name="Edit Incident Rule"
656+
path=":incidentRuleId/"
657+
componentPromise={() =>
658+
import(/* webpackChunkName: "IncidentRulesDetails" */ 'app/views/settings/organizationIncidentRules/details')
659+
}
660+
component={errorHandler(LazyLoad)}
661+
/>
662+
</Route>
630663
</React.Fragment>
631664
);
632665

src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const organizationNavigation = [
2929
path: `${pathPrefix}/members/`,
3030
title: t('Members'),
3131
// eslint-disable-next-line no-shadow
32-
badge: ({organization, access, features}) => {
32+
badge: ({organization, access}) => {
3333
if (!access.has('org:write')) {
3434
return null;
3535
}
@@ -76,6 +76,13 @@ const organizationNavigation = [
7676
description: t('Manage repositories connected to the organization'),
7777
id: 'repos',
7878
},
79+
{
80+
path: `${pathPrefix}/incident-rules/`,
81+
title: t('Incident Rules'),
82+
show: ({features}) => features.has('incidents'),
83+
description: t('Manage Incident Rules'),
84+
id: 'incident-rules',
85+
},
7986
{
8087
path: `${pathPrefix}/integrations/`,
8188
title: t('Integrations'),
@@ -87,7 +94,7 @@ const organizationNavigation = [
8794
{
8895
path: `${pathPrefix}/developer-settings/`,
8996
title: t('Developer Settings'),
90-
show: ({access, features}) => features.has('sentry-apps'),
97+
show: ({features}) => features.has('sentry-apps'),
9198
description: t('Manage developer applications'),
9299
id: 'developer-settings',
93100
},
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
4+
import {t} from 'app/locale';
5+
import Form from 'app/views/settings/components/forms/form';
6+
import Graphic from 'app/components/charts/components/graphic';
7+
import JsonForm from 'app/views/settings/components/forms/jsonForm';
8+
import LineChart from 'app/components/charts/lineChart';
9+
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
10+
11+
class IncidentRulesCreate extends React.Component {
12+
static propTypes = {
13+
data: PropTypes.array,
14+
};
15+
16+
static defaultProps = {
17+
data: [],
18+
};
19+
20+
state = {
21+
width: null,
22+
};
23+
24+
render() {
25+
const {orgId} = this.props.params;
26+
27+
return (
28+
<div>
29+
<SettingsPageHeader title={t('New Incident Rule')} />
30+
<LineChart
31+
isGroupedByDate
32+
forwardedRef={e => {
33+
if (e && typeof e.getEchartsInstance === 'function') {
34+
const width = e.getEchartsInstance().getWidth();
35+
if (width !== this.state.width) {
36+
this.setState({width});
37+
}
38+
}
39+
}}
40+
graphic={Graphic({
41+
elements: [
42+
{
43+
type: 'line',
44+
draggable: true,
45+
shape: {y1: 1, y2: 1, x1: 0, x2: this.state.width},
46+
ondrag: () => {},
47+
},
48+
],
49+
})}
50+
series={this.props.data}
51+
/>
52+
<Form
53+
apiMethod="POST"
54+
apiEndpoint={`/organizations/${orgId}/incident-rules/`}
55+
initialData={{}}
56+
saveOnBlur={false}
57+
>
58+
<JsonForm
59+
forms={[
60+
{
61+
title: t('Metric'),
62+
fields: [
63+
{
64+
label: t('Metric'),
65+
name: 'metric',
66+
type: 'select',
67+
help: t('Choose which metric to display on the Y-axis'),
68+
choices: [['users', 'Users Affected']],
69+
},
70+
{
71+
label: t('Upper Bound'),
72+
name: 'upper',
73+
type: 'range',
74+
help: t(
75+
'Anything trending above this limit will trigger an incident'
76+
),
77+
},
78+
{
79+
label: t('Lower Bound'),
80+
name: 'lower',
81+
type: 'range',
82+
help: t(
83+
'Anything trending below this limit will trigger an incident'
84+
),
85+
},
86+
],
87+
required: true,
88+
},
89+
]}
90+
/>
91+
</Form>
92+
</div>
93+
);
94+
}
95+
}
96+
97+
export default IncidentRulesCreate;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
3+
import AsyncView from 'app/views/asyncView';
4+
import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
5+
import SentryTypes from 'app/sentryTypes';
6+
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
7+
import {t} from 'app/locale';
8+
9+
class IncidentRulesDetails extends AsyncView {
10+
static propTypes = {
11+
organization: SentryTypes.Organization.isRequired,
12+
};
13+
14+
getEndpoints() {
15+
return [];
16+
// const {orgId, incidentRuleId} = this.props.params;
17+
18+
// return [['rule', `/organizations/${orgId}/incident-rules/${ incidentRuleId }/`]];
19+
}
20+
21+
renderBody() {
22+
return (
23+
<div>
24+
<SettingsPageHeader title={t('Incident Rule')} />
25+
<Panel>
26+
<PanelHeader>{t('Rule')}</PanelHeader>
27+
<PanelBody>TODO</PanelBody>
28+
</Panel>
29+
</div>
30+
);
31+
}
32+
}
33+
34+
export default IncidentRulesDetails;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
import Feature from 'app/components/acl/feature';
4+
5+
export default function OrganizationIncidentRules({children}) {
6+
return (
7+
<Feature features={['incidents']} renderDisabled>
8+
{children}
9+
</Feature>
10+
);
11+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
3+
import AsyncView from 'app/views/asyncView';
4+
import Button from 'app/components/button';
5+
import EmptyMessage from 'app/views/settings/components/emptyMessage';
6+
import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
7+
import SentryTypes from 'app/sentryTypes';
8+
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
9+
import {t} from 'app/locale';
10+
11+
class IncidentRulesList extends AsyncView {
12+
static propTypes = {
13+
organization: SentryTypes.Organization.isRequired,
14+
};
15+
16+
getEndpoints() {
17+
return [];
18+
// const {orgId} = this.props.params;
19+
20+
// return [['rules', `/organizations/${orgId}/incident-rules/`]];
21+
}
22+
23+
renderBody() {
24+
const {orgId} = this.props.params;
25+
const action = (
26+
<Button
27+
priority="primary"
28+
size="small"
29+
to={`/settings/${orgId}/incident-rules/new/`}
30+
icon="icon-circle-add"
31+
>
32+
{t('Create New Rule')}
33+
</Button>
34+
);
35+
36+
const isEmpty = true;
37+
38+
return (
39+
<div>
40+
<SettingsPageHeader title={t('Incident Rules')} action={action} />
41+
<Panel>
42+
<PanelHeader>{t('Rules')}</PanelHeader>
43+
<PanelBody>
44+
{!isEmpty ? null : (
45+
<EmptyMessage>{t('No Incident rules have been created yet.')}</EmptyMessage>
46+
)}
47+
</PanelBody>
48+
</Panel>
49+
</div>
50+
);
51+
}
52+
}
53+
54+
export default IncidentRulesList;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {mount} from 'enzyme';
2+
import React from 'react';
3+
4+
import {initializeOrg} from 'app-test/helpers/initializeOrg';
5+
import IncidentRulesCreate from 'app/views/settings/organizationIncidentRules/create';
6+
7+
describe('Incident Rules Create', function() {
8+
it('renders', function() {
9+
const {organization, routerContext} = initializeOrg();
10+
mount(
11+
<IncidentRulesCreate
12+
params={{orgId: organization.slug}}
13+
organization={organization}
14+
/>,
15+
routerContext
16+
);
17+
});
18+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {mount} from 'enzyme';
2+
import React from 'react';
3+
4+
import {initializeOrg} from 'app-test/helpers/initializeOrg';
5+
import IncidentRulesDetails from 'app/views/settings/organizationIncidentRules/details';
6+
7+
describe('Incident Rules Details', function() {
8+
it('renders', function() {
9+
const {organization, routerContext} = initializeOrg();
10+
mount(
11+
<IncidentRulesDetails
12+
params={{orgId: organization.slug}}
13+
organization={organization}
14+
/>,
15+
routerContext
16+
);
17+
});
18+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {mount} from 'enzyme';
2+
import React from 'react';
3+
4+
import {initializeOrg} from 'app-test/helpers/initializeOrg';
5+
import IncidentRulesList from 'app/views/settings/organizationIncidentRules/list';
6+
7+
describe('Incident Rules List', function() {
8+
it('renders', function() {
9+
const {organization, routerContext} = initializeOrg();
10+
mount(
11+
<IncidentRulesList
12+
params={{orgId: organization.slug}}
13+
organization={organization}
14+
/>,
15+
routerContext
16+
);
17+
});
18+
});

0 commit comments

Comments
 (0)