Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class BaseChart extends React.Component {
onFinished: PropTypes.func,

// Forwarded Ref
forwardedRef: PropTypes.object,
forwardedRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),

// Custom chart props that are implemented by us (and not a feature of eCharts)
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'echarts/lib/component/graphic';

/**
* eCharts graphic
*
* See https://echarts.apache.org/en/option.html#graphic
*/
export default function Graphic(props) {
return props;
}
33 changes: 33 additions & 0 deletions src/sentry/static/sentry/app/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ function routes() {
component={errorHandler(LazyLoad)}
/>
</Route>

<Route name="Developer Settings" path="developer-settings/">
<IndexRoute
componentPromise={() =>
Expand All @@ -627,6 +628,38 @@ function routes() {
component={errorHandler(LazyLoad)}
/>
</Route>

<Route
name="Incident Rules"
path="incident-rules/"
componentPromise={() =>
import(/* webpackChunkName: "OrganizationIncidentRules" */ 'app/views/settings/organizationIncidentRules')
}
component={errorHandler(LazyLoad)}
>
<IndexRoute
componentPromise={() =>
import(/* webpackChunkName: "IncidentRulesList" */ 'app/views/settings/organizationIncidentRules/list')
}
component={errorHandler(LazyLoad)}
/>
<Route
name="New Incident Rule"
path="new/"
componentPromise={() =>
import(/* webpackChunkName: "IncidentRulesCreate" */ 'app/views/settings/organizationIncidentRules/create')
}
component={errorHandler(LazyLoad)}
/>
<Route
name="Edit Incident Rule"
path=":incidentRuleId/"
componentPromise={() =>
import(/* webpackChunkName: "IncidentRulesDetails" */ 'app/views/settings/organizationIncidentRules/details')
}
component={errorHandler(LazyLoad)}
/>
</Route>
</React.Fragment>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const organizationNavigation = [
path: `${pathPrefix}/members/`,
title: t('Members'),
// eslint-disable-next-line no-shadow
badge: ({organization, access, features}) => {
badge: ({organization, access}) => {
if (!access.has('org:write')) {
return null;
}
Expand Down Expand Up @@ -76,6 +76,13 @@ const organizationNavigation = [
description: t('Manage repositories connected to the organization'),
id: 'repos',
},
{
path: `${pathPrefix}/incident-rules/`,
title: t('Incident Rules'),
show: ({features}) => features.has('incidents'),
description: t('Manage Incident Rules'),
id: 'incident-rules',
},
{
path: `${pathPrefix}/integrations/`,
title: t('Integrations'),
Expand All @@ -87,7 +94,7 @@ const organizationNavigation = [
{
path: `${pathPrefix}/developer-settings/`,
title: t('Developer Settings'),
show: ({access, features}) => features.has('sentry-apps'),
show: ({features}) => features.has('sentry-apps'),
description: t('Manage developer applications'),
id: 'developer-settings',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import PropTypes from 'prop-types';
import React from 'react';

import {t} from 'app/locale';
import Form from 'app/views/settings/components/forms/form';
import Graphic from 'app/components/charts/components/graphic';
import JsonForm from 'app/views/settings/components/forms/jsonForm';
import LineChart from 'app/components/charts/lineChart';
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';

class IncidentRulesCreate extends React.Component {
static propTypes = {
data: PropTypes.array,
};

static defaultProps = {
data: [],
};

state = {
width: null,
};

render() {
const {orgId} = this.props.params;

return (
<div>
<SettingsPageHeader title={t('New Incident Rule')} />
<LineChart
isGroupedByDate
forwardedRef={e => {
if (e && typeof e.getEchartsInstance === 'function') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you never get an echarts object?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should only happen in tests

const width = e.getEchartsInstance().getWidth();
if (width !== this.state.width) {
this.setState({width});
}
}
}}
graphic={Graphic({
elements: [
{
type: 'line',
draggable: true,
shape: {y1: 1, y2: 1, x1: 0, x2: this.state.width},
ondrag: () => {},
},
],
})}
series={this.props.data}
/>
<Form
apiMethod="POST"
apiEndpoint={`/organizations/${orgId}/incident-rules/`}
initialData={{}}
saveOnBlur={false}
>
<JsonForm
forms={[
{
title: t('Metric'),
fields: [
{
label: t('Metric'),
name: 'metric',
type: 'select',
help: t('Choose which metric to display on the Y-axis'),
choices: [['users', 'Users Affected']],
},
{
label: t('Upper Bound'),
name: 'upper',
type: 'range',
help: t(
'Anything trending above this limit will trigger an incident'
),
},
{
label: t('Lower Bound'),
name: 'lower',
type: 'range',
help: t(
'Anything trending below this limit will trigger an incident'
),
},
],
required: true,
},
]}
/>
</Form>
</div>
);
}
}

export default IncidentRulesCreate;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import AsyncView from 'app/views/asyncView';
import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
import SentryTypes from 'app/sentryTypes';
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
import {t} from 'app/locale';

class IncidentRulesDetails extends AsyncView {
static propTypes = {
organization: SentryTypes.Organization.isRequired,
};

getEndpoints() {
return [];
// const {orgId, incidentRuleId} = this.props.params;

// return [['rule', `/organizations/${orgId}/incident-rules/${ incidentRuleId }/`]];
}

renderBody() {
return (
<div>
<SettingsPageHeader title={t('Incident Rule')} />
<Panel>
<PanelHeader>{t('Rule')}</PanelHeader>
<PanelBody>TODO</PanelBody>
</Panel>
</div>
);
}
}

export default IncidentRulesDetails;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

import Feature from 'app/components/acl/feature';

export default function OrganizationIncidentRules({children}) {
return (
<Feature features={['incidents']} renderDisabled>
{children}
</Feature>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';

import AsyncView from 'app/views/asyncView';
import Button from 'app/components/button';
import EmptyMessage from 'app/views/settings/components/emptyMessage';
import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
import SentryTypes from 'app/sentryTypes';
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
import {t} from 'app/locale';

class IncidentRulesList extends AsyncView {
static propTypes = {
organization: SentryTypes.Organization.isRequired,
};

getEndpoints() {
return [];
// const {orgId} = this.props.params;

// return [['rules', `/organizations/${orgId}/incident-rules/`]];
}

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 isEmpty = true;

return (
<div>
<SettingsPageHeader title={t('Incident Rules')} action={action} />
<Panel>
<PanelHeader>{t('Rules')}</PanelHeader>
<PanelBody>
{!isEmpty ? null : (
<EmptyMessage>{t('No Incident rules have been created yet.')}</EmptyMessage>
)}
</PanelBody>
</Panel>
</div>
);
}
}

export default IncidentRulesList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {mount} from 'enzyme';
import React from 'react';

import {initializeOrg} from 'app-test/helpers/initializeOrg';
import IncidentRulesCreate from 'app/views/settings/organizationIncidentRules/create';

describe('Incident Rules Create', function() {
it('renders', function() {
const {organization, routerContext} = initializeOrg();
mount(
<IncidentRulesCreate
params={{orgId: organization.slug}}
organization={organization}
/>,
routerContext
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {mount} from 'enzyme';
import React from 'react';

import {initializeOrg} from 'app-test/helpers/initializeOrg';
import IncidentRulesDetails from 'app/views/settings/organizationIncidentRules/details';

describe('Incident Rules Details', function() {
it('renders', function() {
const {organization, routerContext} = initializeOrg();
mount(
<IncidentRulesDetails
params={{orgId: organization.slug}}
organization={organization}
/>,
routerContext
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {mount} from 'enzyme';
import React from 'react';

import {initializeOrg} from 'app-test/helpers/initializeOrg';
import IncidentRulesList from 'app/views/settings/organizationIncidentRules/list';

describe('Incident Rules List', function() {
it('renders', function() {
const {organization, routerContext} = initializeOrg();
mount(
<IncidentRulesList
params={{orgId: organization.slug}}
organization={organization}
/>,
routerContext
);
});
});