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
41 changes: 26 additions & 15 deletions static/app/views/alerts/incidentRules/ruleConditionsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ type Props = {
onFilterSearch: (query: string) => void;
alertType: AlertType;
dataset: Dataset;
timeWindow: number;
comparisonType: AlertRuleComparisonType;
onComparisonTypeChange: (value: AlertRuleComparisonType) => void;
onComparisonDeltaChange: (value: number) => void;
onTimeWindowChange: (value: number) => void;
comparisonDelta?: number;
allowChangeEventTypes?: boolean;
};
Expand Down Expand Up @@ -107,7 +109,7 @@ class RuleConditionsForm extends React.PureComponent<Props, State> {
}

return Object.entries(options).map(([value, label]) => ({
value,
value: parseInt(value, 10),
label,
}));
}
Expand Down Expand Up @@ -144,8 +146,10 @@ class RuleConditionsForm extends React.PureComponent<Props, State> {
onFilterSearch,
allowChangeEventTypes,
alertType,
timeWindow,
comparisonType,
comparisonDelta,
onTimeWindowChange,
onComparisonDeltaChange,
onComparisonTypeChange,
dataset,
Expand Down Expand Up @@ -391,35 +395,42 @@ class RuleConditionsForm extends React.PureComponent<Props, State> {
/>
)}
{timeWindowText && <FormRowText>{timeWindowText}</FormRowText>}
<SelectField
<SelectControl
name="timeWindow"
style={{
...formElemBaseStyle,
flex: '0 150px 0',
minWidth: 130,
maxWidth: 300,
styles={{
control: (provided: {[x: string]: string | number | boolean}) => ({
...provided,
minWidth: 130,
maxWidth: 300,
}),
}}
options={this.timeWindowOptions}
required
isDisabled={disabled}
getValue={value => Number(value)}
setValue={value => `${value}`}
value={timeWindow}
onChange={({value}) => onTimeWindowChange(value)}
inline={false}
flexibleControlStateSize
/>
<Feature features={['organizations:change-alerts']} organization={organization}>
{comparisonType === AlertRuleComparisonType.CHANGE && (
<ComparisonContainer>
{t(' compared to ')}
<SelectField
<SelectControl
name="comparisonDelta"
style={{
...formElemBaseStyle,
minWidth: 500,
maxWidth: 1000,
styles={{
container: (provided: {[x: string]: string | number | boolean}) => ({
...provided,
marginLeft: space(1),
}),
control: (provided: {[x: string]: string | number | boolean}) => ({
...provided,
minWidth: 500,
maxWidth: 1000,
}),
}}
value={comparisonDelta}
onChange={onComparisonDeltaChange}
onChange={({value}) => onComparisonDeltaChange(value)}
options={COMPARISON_DELTA_OPTIONS}
required={comparisonType === AlertRuleComparisonType.CHANGE}
/>
Expand Down
26 changes: 21 additions & 5 deletions static/app/views/alerts/incidentRules/ruleForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,14 @@ class RuleFormContainer extends AsyncComponent<Props, State> {

handleFieldChange = (name: string, value: unknown) => {
if (
['dataset', 'eventTypes', 'timeWindow', 'environment', 'aggregate'].includes(name)
[
'dataset',
'eventTypes',
'timeWindow',
'environment',
'aggregate',
'comparisonDelta',
].includes(name)
) {
this.setState({[name]: value});
}
Expand Down Expand Up @@ -435,7 +442,8 @@ class RuleFormContainer extends AsyncComponent<Props, State> {

const {organization, params, rule, onSubmitSuccess, location, sessionId} = this.props;
const {ruleId} = this.props.params;
const {resolveThreshold, triggers, thresholdType, comparisonDelta, uuid} = this.state;
const {resolveThreshold, triggers, thresholdType, comparisonDelta, uuid, timeWindow} =
this.state;

// Remove empty warning trigger
const sanitizedTriggers = triggers.filter(
Expand Down Expand Up @@ -473,6 +481,7 @@ class RuleFormContainer extends AsyncComponent<Props, State> {
resolveThreshold: isEmpty(resolveThreshold) ? null : resolveThreshold,
thresholdType,
comparisonDelta,
timeWindow,
},
{
referrer: location?.query?.referrer,
Expand Down Expand Up @@ -562,8 +571,11 @@ class RuleFormContainer extends AsyncComponent<Props, State> {

handleComparisonTypeChange = (value: AlertRuleComparisonType) => {
const comparisonDelta =
value === AlertRuleComparisonType.COUNT ? undefined : this.state.comparisonDelta;
this.setState({comparisonType: value, comparisonDelta});
value === AlertRuleComparisonType.COUNT
? undefined
: this.state.comparisonDelta ?? 10080;
const timeWindow = this.state.comparisonDelta ? this.state.timeWindow : 60;
this.setState({comparisonType: value, comparisonDelta, timeWindow});
};

handleComparisonDeltaChange = (value: number) => {
Expand Down Expand Up @@ -741,10 +753,14 @@ class RuleFormContainer extends AsyncComponent<Props, State> {
allowChangeEventTypes={isCustomMetric || dataset === Dataset.ERRORS}
alertType={isCustomMetric ? 'custom' : alertType}
dataset={dataset}
timeWindow={timeWindow}
comparisonType={comparisonType}
comparisonDelta={comparisonDelta}
onComparisonTypeChange={this.handleComparisonTypeChange}
onComparisonDeltaChange={this.handleComparisonDeltaChange}
onComparisonDeltaChange={value =>
this.handleFieldChange('comparisonDelta', value)
}
onTimeWindowChange={value => this.handleFieldChange('timeWindow', value)}
/>
<AlertListItem>{t('Set thresholds to trigger alert')}</AlertListItem>
{triggerForm(hasAccess)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const CHANGE_ALERT_CONDITION_IDS = [

export const CHANGE_ALERT_PLACEHOLDERS_LABELS = {
'sentry.rules.conditions.event_frequency.EventFrequencyCondition':
'Number of errors in an issue is...',
'Number of events in an issue is...',
'sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition':
'Number of users affected by an issue is...',
'sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition':
Expand Down
10 changes: 9 additions & 1 deletion static/app/views/alerts/issueRuleEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,21 @@ class IssueRuleEditor extends AsyncView<Props, State> {

getInitialValue = (type: ConditionOrActionProperty, id: string) => {
const configuration = this.state.configs?.[type]?.find(c => c.id === id);

const hasChangeAlerts =
configuration?.id &&
this.props.organization.features.includes('change-alerts') &&
CHANGE_ALERT_CONDITION_IDS.includes(configuration.id);

return configuration?.formFields
? Object.fromEntries(
Object.entries(configuration.formFields)
// TODO(ts): Doesn't work if I cast formField as IssueAlertRuleFormField
.map(([key, formField]: [string, any]) => [
key,
formField?.initial ?? formField?.choices?.[0]?.[0],
hasChangeAlerts && key === 'interval'
? '1h'
: formField?.initial ?? formField?.choices?.[0]?.[0],
])
.filter(([, initial]) => !!initial)
)
Expand Down
12 changes: 9 additions & 3 deletions static/app/views/alerts/issueRuleEditor/ruleNodeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class RuleNodeList extends React.Component<Props> {
| IssueAlertRuleConditionTemplate
| null
| undefined => {
const {nodes, items, organization} = this.props;
const {nodes, items, organization, onPropertyChange} = this.props;
const node = nodes ? nodes.find(n => n.id === id) : null;

if (!node) {
Expand Down Expand Up @@ -95,15 +95,21 @@ class RuleNodeList extends React.Component<Props> {
};

if (item.comparisonType === 'percent') {
if (!item.comparisonInterval) {
// comparisonInterval value in IssueRuleEditor state
// is undefined even if initial value is defined
// can't directly call onPropertyChange, because
// getNode is called during render
setTimeout(() => onPropertyChange(itemIdx, 'comparisonInterval', '1w'));
}
changeAlertNode = {
...changeAlertNode,
formFields: {
...changeAlertNode.formFields,
comparisonInterval: {
type: 'choice',
choices: COMPARISON_INTERVAL_CHOICES,
// comparisonInterval initial value isn't on the item, so it needs to be selected by user
initial: 'select',
initial: '1w',
},
},
};
Expand Down