|
@@ -1,4 +1,5 @@
|
|
import {Fragment} from 'react';
|
|
import {Fragment} from 'react';
|
|
|
|
+import {css} from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import styled from '@emotion/styled';
|
|
import * as Sentry from '@sentry/react';
|
|
import * as Sentry from '@sentry/react';
|
|
import isEqual from 'lodash/isEqual';
|
|
import isEqual from 'lodash/isEqual';
|
|
@@ -6,6 +7,7 @@ import isEqual from 'lodash/isEqual';
|
|
import AsyncComponent from 'sentry/components/asyncComponent';
|
|
import AsyncComponent from 'sentry/components/asyncComponent';
|
|
import Input from 'sentry/components/forms/controls/input';
|
|
import Input from 'sentry/components/forms/controls/input';
|
|
import RadioGroup from 'sentry/components/forms/controls/radioGroup';
|
|
import RadioGroup from 'sentry/components/forms/controls/radioGroup';
|
|
|
|
+import MultipleCheckboxField from 'sentry/components/forms/MultipleCheckboxField';
|
|
import SelectControl from 'sentry/components/forms/selectControl';
|
|
import SelectControl from 'sentry/components/forms/selectControl';
|
|
import PageHeading from 'sentry/components/pageHeading';
|
|
import PageHeading from 'sentry/components/pageHeading';
|
|
import {t} from 'sentry/locale';
|
|
import {t} from 'sentry/locale';
|
|
@@ -13,6 +15,8 @@ import space from 'sentry/styles/space';
|
|
import {Organization} from 'sentry/types';
|
|
import {Organization} from 'sentry/types';
|
|
import withOrganization from 'sentry/utils/withOrganization';
|
|
import withOrganization from 'sentry/utils/withOrganization';
|
|
|
|
|
|
|
|
+import {PRESET_AGGREGATES} from '../alerts/rules/metric/presets';
|
|
|
|
+
|
|
enum MetricValues {
|
|
enum MetricValues {
|
|
ERRORS,
|
|
ERRORS,
|
|
USERS,
|
|
USERS,
|
|
@@ -51,6 +55,8 @@ type State = AsyncComponent['state'] & {
|
|
interval: string;
|
|
interval: string;
|
|
intervalChoices: [string, string][] | undefined;
|
|
intervalChoices: [string, string][] | undefined;
|
|
metric: MetricValues;
|
|
metric: MetricValues;
|
|
|
|
+ metricAlertPresets: Set<string>;
|
|
|
|
+
|
|
threshold: string;
|
|
threshold: string;
|
|
};
|
|
};
|
|
|
|
|
|
@@ -60,6 +66,7 @@ type RequestDataFragment = {
|
|
conditions: {id: string; interval: string; value: string}[] | undefined;
|
|
conditions: {id: string; interval: string; value: string}[] | undefined;
|
|
defaultRules: boolean;
|
|
defaultRules: boolean;
|
|
frequency: number;
|
|
frequency: number;
|
|
|
|
+ metricAlertPresets: string[];
|
|
name: string;
|
|
name: string;
|
|
shouldCreateCustomRule: boolean;
|
|
shouldCreateCustomRule: boolean;
|
|
};
|
|
};
|
|
@@ -111,6 +118,7 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
|
|
metric: MetricValues.ERRORS,
|
|
metric: MetricValues.ERRORS,
|
|
interval: '',
|
|
interval: '',
|
|
threshold: '',
|
|
threshold: '',
|
|
|
|
+ metricAlertPresets: new Set(),
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
@@ -224,6 +232,7 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
|
|
actions: [{id: NOTIFY_EVENT_ACTION}],
|
|
actions: [{id: NOTIFY_EVENT_ACTION}],
|
|
actionMatch: 'all',
|
|
actionMatch: 'all',
|
|
frequency: 5,
|
|
frequency: 5,
|
|
|
|
+ metricAlertPresets: Array.from(this.state.metricAlertPresets),
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
@@ -286,17 +295,47 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
|
|
const issueAlertOptionsChoices = this.getIssueAlertsChoices(
|
|
const issueAlertOptionsChoices = this.getIssueAlertsChoices(
|
|
this.state.conditions?.length > 0
|
|
this.state.conditions?.length > 0
|
|
);
|
|
);
|
|
|
|
+ const showMetricAlertSelections =
|
|
|
|
+ !!this.props.organization.experiments.MetricAlertOnProjectCreationExperiment;
|
|
return (
|
|
return (
|
|
<Fragment>
|
|
<Fragment>
|
|
<PageHeadingWithTopMargins withMargins>
|
|
<PageHeadingWithTopMargins withMargins>
|
|
{t('Set your default alert settings')}
|
|
{t('Set your default alert settings')}
|
|
</PageHeadingWithTopMargins>
|
|
</PageHeadingWithTopMargins>
|
|
- <RadioGroupWithPadding
|
|
|
|
- choices={issueAlertOptionsChoices}
|
|
|
|
- label={t('Options for creating an alert')}
|
|
|
|
- onChange={alertSetting => this.setStateAndUpdateParents({alertSetting})}
|
|
|
|
- value={this.state.alertSetting}
|
|
|
|
- />
|
|
|
|
|
|
+ <Content>
|
|
|
|
+ {showMetricAlertSelections && <Subheading>{t('Issue Alerts')}</Subheading>}
|
|
|
|
+ <RadioGroupWithPadding
|
|
|
|
+ choices={issueAlertOptionsChoices}
|
|
|
|
+ label={t('Options for creating an alert')}
|
|
|
|
+ onChange={alertSetting => this.setStateAndUpdateParents({alertSetting})}
|
|
|
|
+ value={this.state.alertSetting}
|
|
|
|
+ />
|
|
|
|
+ {showMetricAlertSelections && (
|
|
|
|
+ <Fragment>
|
|
|
|
+ <Subheading>{t('Performance Alerts')}</Subheading>
|
|
|
|
+ <MultipleCheckboxField
|
|
|
|
+ size="24px"
|
|
|
|
+ choices={PRESET_AGGREGATES.map(agg => ({
|
|
|
|
+ title: agg.description,
|
|
|
|
+ value: agg.id,
|
|
|
|
+ checked: this.state.metricAlertPresets.has(agg.id),
|
|
|
|
+ }))}
|
|
|
|
+ css={CheckboxFieldStyles}
|
|
|
|
+ onClick={selectedItem => {
|
|
|
|
+ const next = new Set(this.state.metricAlertPresets);
|
|
|
|
+ if (next.has(selectedItem)) {
|
|
|
|
+ next.delete(selectedItem);
|
|
|
|
+ } else {
|
|
|
|
+ next.add(selectedItem);
|
|
|
|
+ }
|
|
|
|
+ this.setStateAndUpdateParents({
|
|
|
|
+ metricAlertPresets: next,
|
|
|
|
+ });
|
|
|
|
+ }}
|
|
|
|
+ />
|
|
|
|
+ </Fragment>
|
|
|
|
+ )}
|
|
|
|
+ </Content>
|
|
</Fragment>
|
|
</Fragment>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -304,6 +343,15 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
|
|
|
|
|
|
export default withOrganization(IssueAlertOptions);
|
|
export default withOrganization(IssueAlertOptions);
|
|
|
|
|
|
|
|
+const CheckboxFieldStyles = css`
|
|
|
|
+ margin-top: ${space(1)};
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const Content = styled('div')`
|
|
|
|
+ padding-top: ${space(2)};
|
|
|
|
+ padding-bottom: ${space(4)};
|
|
|
|
+`;
|
|
|
|
+
|
|
const CustomizeAlertsGrid = styled('div')`
|
|
const CustomizeAlertsGrid = styled('div')`
|
|
display: grid;
|
|
display: grid;
|
|
grid-template-columns: repeat(5, max-content);
|
|
grid-template-columns: repeat(5, max-content);
|
|
@@ -317,12 +365,13 @@ const InlineSelectControl = styled(SelectControl)`
|
|
width: 160px;
|
|
width: 160px;
|
|
`;
|
|
`;
|
|
const RadioGroupWithPadding = styled(RadioGroup)`
|
|
const RadioGroupWithPadding = styled(RadioGroup)`
|
|
- padding: ${space(3)} 0;
|
|
|
|
- margin-bottom: 50px;
|
|
|
|
- box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
+ margin-bottom: ${space(2)};
|
|
`;
|
|
`;
|
|
const PageHeadingWithTopMargins = styled(PageHeading)`
|
|
const PageHeadingWithTopMargins = styled(PageHeading)`
|
|
margin-top: 65px;
|
|
margin-top: 65px;
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
+ padding-bottom: ${space(3)};
|
|
|
|
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
`;
|
|
`;
|
|
const RadioItemWrapper = styled('div')`
|
|
const RadioItemWrapper = styled('div')`
|
|
min-height: 35px;
|
|
min-height: 35px;
|
|
@@ -330,3 +379,6 @@ const RadioItemWrapper = styled('div')`
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
justify-content: center;
|
|
`;
|
|
`;
|
|
|
|
+const Subheading = styled('b')`
|
|
|
|
+ display: block;
|
|
|
|
+`;
|