diff --git a/src/sentry/static/sentry/app/components/charts/baseChart.jsx b/src/sentry/static/sentry/app/components/charts/baseChart.jsx index 0482e8ea123f..0c57d2747fb2 100644 --- a/src/sentry/static/sentry/app/components/charts/baseChart.jsx +++ b/src/sentry/static/sentry/app/components/charts/baseChart.jsx @@ -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) /** diff --git a/src/sentry/static/sentry/app/components/charts/components/graphic.jsx b/src/sentry/static/sentry/app/components/charts/components/graphic.jsx new file mode 100644 index 000000000000..02ff5b03a188 --- /dev/null +++ b/src/sentry/static/sentry/app/components/charts/components/graphic.jsx @@ -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; +} diff --git a/src/sentry/static/sentry/app/routes.jsx b/src/sentry/static/sentry/app/routes.jsx index ca47744b1788..c040f4b9c874 100644 --- a/src/sentry/static/sentry/app/routes.jsx +++ b/src/sentry/static/sentry/app/routes.jsx @@ -603,6 +603,7 @@ function routes() { component={errorHandler(LazyLoad)} /> + @@ -627,6 +628,38 @@ function routes() { component={errorHandler(LazyLoad)} /> + + + import(/* webpackChunkName: "OrganizationIncidentRules" */ 'app/views/settings/organizationIncidentRules') + } + component={errorHandler(LazyLoad)} + > + + import(/* webpackChunkName: "IncidentRulesList" */ 'app/views/settings/organizationIncidentRules/list') + } + component={errorHandler(LazyLoad)} + /> + + import(/* webpackChunkName: "IncidentRulesCreate" */ 'app/views/settings/organizationIncidentRules/create') + } + component={errorHandler(LazyLoad)} + /> + + import(/* webpackChunkName: "IncidentRulesDetails" */ 'app/views/settings/organizationIncidentRules/details') + } + component={errorHandler(LazyLoad)} + /> + ); diff --git a/src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx b/src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx index 4dbe977b621c..34d654111557 100644 --- a/src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx +++ b/src/sentry/static/sentry/app/views/settings/organization/navigationConfiguration.jsx @@ -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; } @@ -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'), @@ -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', }, diff --git a/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/create.jsx b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/create.jsx new file mode 100644 index 000000000000..7fcec04c077e --- /dev/null +++ b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/create.jsx @@ -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 ( +
+ + { + if (e && typeof e.getEchartsInstance === 'function') { + 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} + /> +
+ + +
+ ); + } +} + +export default IncidentRulesCreate; diff --git a/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/details.jsx b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/details.jsx new file mode 100644 index 000000000000..6d1d171c5679 --- /dev/null +++ b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/details.jsx @@ -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 ( +
+ + + {t('Rule')} + TODO + +
+ ); + } +} + +export default IncidentRulesDetails; diff --git a/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/index.jsx b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/index.jsx new file mode 100644 index 000000000000..674eff4836d8 --- /dev/null +++ b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/index.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +import Feature from 'app/components/acl/feature'; + +export default function OrganizationIncidentRules({children}) { + return ( + + {children} + + ); +} diff --git a/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/list.jsx b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/list.jsx new file mode 100644 index 000000000000..86e3afe134c1 --- /dev/null +++ b/src/sentry/static/sentry/app/views/settings/organizationIncidentRules/list.jsx @@ -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 = ( + + ); + + const isEmpty = true; + + return ( +
+ + + {t('Rules')} + + {!isEmpty ? null : ( + {t('No Incident rules have been created yet.')} + )} + + +
+ ); + } +} + +export default IncidentRulesList; diff --git a/tests/js/spec/views/settings/organizationIncidentRules/create.spec.jsx b/tests/js/spec/views/settings/organizationIncidentRules/create.spec.jsx new file mode 100644 index 000000000000..0b2ae0118ec3 --- /dev/null +++ b/tests/js/spec/views/settings/organizationIncidentRules/create.spec.jsx @@ -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( + , + routerContext + ); + }); +}); diff --git a/tests/js/spec/views/settings/organizationIncidentRules/details.spec.jsx b/tests/js/spec/views/settings/organizationIncidentRules/details.spec.jsx new file mode 100644 index 000000000000..563be4af61f1 --- /dev/null +++ b/tests/js/spec/views/settings/organizationIncidentRules/details.spec.jsx @@ -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( + , + routerContext + ); + }); +}); diff --git a/tests/js/spec/views/settings/organizationIncidentRules/list.spec.jsx b/tests/js/spec/views/settings/organizationIncidentRules/list.spec.jsx new file mode 100644 index 000000000000..b337643bf574 --- /dev/null +++ b/tests/js/spec/views/settings/organizationIncidentRules/list.spec.jsx @@ -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( + , + routerContext + ); + }); +});