Skip to content

Commit 62ea924

Browse files
committed
Change to action + condition based rules
1 parent 7ff56fb commit 62ea924

File tree

6 files changed

+167
-112
lines changed

6 files changed

+167
-112
lines changed

src/sentry/rules.py

+75-55
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
from django import forms
3434
from django.utils.html import escape
35-
from django.utils.datastructures import SortedDict
3635
from django.utils.safestring import mark_safe
3736

3837
from sentry.models import Rule as RuleModel
@@ -43,19 +42,21 @@
4342
NOTIFY_ON_RATE_CHANGE = 3
4443

4544

46-
class RuleBase(type):
45+
class RuleDescriptor(type):
4746
def __new__(cls, *args, **kwargs):
48-
new_cls = super(RuleBase, cls).__new__(cls, *args, **kwargs)
47+
new_cls = super(RuleDescriptor, cls).__new__(cls, *args, **kwargs)
4948
new_cls.id = '%s.%s' % (new_cls.__module__, new_cls.__name__)
5049
return new_cls
5150

5251

53-
class Rule(object):
54-
__metaclass__ = RuleBase
55-
52+
class RuleBase(object):
53+
label = None
5654
form_cls = None
57-
action_label = None
58-
trigger_label = None
55+
56+
__metaclass__ = RuleDescriptor
57+
58+
def __init__(self, instance):
59+
self.instance = instance
5960

6061
@classmethod
6162
def from_params(cls, project, data=None):
@@ -65,23 +66,12 @@ def from_params(cls, project, data=None):
6566
)
6667
return cls(rule)
6768

68-
def __init__(self, instance):
69-
self.instance = instance
70-
71-
def before(self, event):
72-
# should this pass event or the data?
73-
return event
74-
75-
def after(self, event, is_new, is_regression, **kwargs):
76-
pass
77-
7869
def render_label(self):
79-
return ('%s %s' % (self.action_label, self.trigger_label)).format(
80-
**self.instance.data)
70+
return self.label.format(**self.instance.data)
8171

8272
def render_form(self, form_data):
8373
if not self.form_cls:
84-
return self.trigger_label
74+
return self.label
8575

8676
form = self.form_cls(
8777
form_data,
@@ -93,26 +83,51 @@ def replace_field(match):
9383
field = match.group(1)
9484
return unicode(form[field])
9585

96-
return mark_safe(re.sub(r'{([^}]+)}', replace_field, escape(self.trigger_label)))
86+
return mark_safe(re.sub(r'{([^}]+)}', replace_field, escape(self.label)))
9787

98-
def save(self, form_data):
99-
instance = self.instance
10088

101-
if self.form_cls:
102-
form = self.form_cls(
103-
form_data,
104-
initial=self.instance.data,
105-
prefix=self.id,
106-
)
107-
assert form.is_valid(), 'Form was not valid: %r' % (form.errors,)
108-
instance.data = form.cleaned_data
89+
# class Rule(RuleMixin):
90+
# __metaclass__ = RuleBase
10991

110-
instance.rule_id = self.id
111-
instance.save()
92+
# def before(self, event):
93+
# # should this pass event or the data?
94+
# return event
95+
96+
# def after(self, event, is_new, is_regression, **kwargs):
97+
# pass
98+
99+
# def save(self, form_data):
100+
# instance = self.instance
101+
102+
# if self.form_cls:
103+
# form = self.form_cls(
104+
# form_data,
105+
# initial=self.instance.data,
106+
# prefix=self.id,
107+
# )
108+
# assert form.is_valid(), 'Form was not valid: %r' % (form.errors,)
109+
# instance.data = form.cleaned_data
110+
111+
# instance.rule_id = self.id
112+
# instance.save()
113+
114+
115+
class EventAction(RuleBase):
116+
def before(self, event):
117+
# should this pass event or the data?
118+
return event
119+
120+
def after(self, event, is_new, is_regression, **kwargs):
121+
pass
122+
123+
124+
class EventCondition(RuleBase):
125+
def passes(self, event, is_new, is_regression, **kwargs):
126+
raise NotImplementedError
112127

113128

114-
class NotifyRule(Rule):
115-
action_label = 'Send a notification'
129+
class NotifyEventAction(EventAction):
130+
label = 'Send a notification'
116131

117132
def notify(self, event):
118133
# TODO: fire off plugin notifications
@@ -122,40 +137,45 @@ def after(self, event, **kwargs):
122137
if self.should_notify(event):
123138
self.notify(event)
124139

125-
def should_notify(self, event):
140+
def passes(self, event, **kwargs):
126141
raise NotImplementedError
127142

128143

129-
class NotifyOnFirstSeenRule(NotifyRule):
130-
trigger_label = 'An event is first seen'
144+
class FirstSeenEventCondition(EventCondition):
145+
label = 'An event is first seen'
131146

132-
def should_notify(self, event, is_new, **kwargs):
147+
def passes(self, event, is_new, **kwargs):
133148
return is_new
134149

135150

136-
class NotifyOnRegressionRule(NotifyRule):
137-
trigger_label = 'An event changes state from resolved to unresolved'
151+
class RegressionEventCondition(EventCondition):
152+
label = 'An event changes state from resolved to unresolved'
138153

139-
def should_notify(self, event, is_regression, **kwargs):
154+
def passes(self, event, is_regression, **kwargs):
140155
return is_regression
141156

142157

143-
class NotifyOnTimesSeenForm(forms.Form):
158+
class TimesSeenEventForm(forms.Form):
144159
num = forms.IntegerField(widget=forms.TextInput(attrs={'type': 'number'}))
145160

146161

147-
class NotifyOnTimesSeenRule(NotifyRule):
148-
form_cls = NotifyOnTimesSeenForm
149-
trigger_label = 'An event is seen more than {num} times'
162+
class TimesSeenEventCondition(EventCondition):
163+
form_cls = TimesSeenEventForm
164+
label = 'An event is seen more than {num} times'
150165

151-
def should_notify(self, event):
166+
def passes(self, event):
152167
return event.times_seen == self.get_option('num')
153168

154169

155-
RULES = SortedDict(
156-
(k.id, k) for k in [
157-
NotifyOnFirstSeenRule,
158-
NotifyOnRegressionRule,
159-
NotifyOnTimesSeenRule,
160-
]
161-
)
170+
RULES = {
171+
'events': {
172+
'actions': [
173+
NotifyEventAction,
174+
],
175+
'conditions': [
176+
FirstSeenEventCondition,
177+
RegressionEventCondition,
178+
TimesSeenEventCondition,
179+
],
180+
}
181+
}

src/sentry/static/sentry/scripts/app.js

+48-33
Original file line numberDiff line numberDiff line change
@@ -625,67 +625,82 @@
625625
initialize: function(data){
626626
BasePage.prototype.initialize.apply(this, arguments);
627627

628-
_.bindAll(this, 'populateTriggers', 'addRule');
628+
_.bindAll(this, 'addAction', 'addCondition');
629629

630-
this.rules_by_action = {}
631-
this.triggers_by_id = {}
630+
this.actions_by_id = {};
631+
this.conditions_by_id = {};
632632
this.el = $(data.el);
633633
this.action_sel = this.el.find('select[name="action"]');
634-
this.trigger_sel = this.el.find('select[name="trigger"]');
635-
this.trigger_table = this.el.find('table.trigger-list');
636-
this.trigger_table_body = this.trigger_table.find('tbody');
634+
this.action_table = this.el.find('table.action-list');
635+
this.action_table_body = this.action_table.find('tbody');
636+
this.condition_sel = this.el.find('select[name="condition"]');
637+
this.condition_table = this.el.find('table.condition-list');
638+
this.condition_table_body = this.condition_table.find('tbody');
637639

638640
this.action_sel.empty();
639-
$.each(data.rules, _.bind(function(_, action) {
641+
this.action_sel.append($('<option></option>'));
642+
$.each(data.actions, _.bind(function(_, action) {
640643
var opt = $('<option></option>');
641644
opt.attr({
642-
value: action.label,
645+
value: action.id,
643646
});
644647
opt.text(action.label);
645648
opt.appendTo(this.action_sel);
646649

647-
this.rules_by_action[action.label] = action;
650+
this.actions_by_id[action.id] = action;
648651
}, this));
649652

650-
this.action_sel.change(this.populateTriggers).change();
651-
this.trigger_sel.change(this.addRule);
652-
},
653-
654-
populateTriggers: function(){
655-
var rule = this.rules_by_action[this.action_sel.val()];
656-
657-
this.trigger_table.hide();
658-
this.trigger_table_body.empty();
659-
660-
this.trigger_sel.empty();
661-
this.trigger_sel.append($('<option></option>'));
662-
$.each(rule.triggers, _.bind(function(_, trigger) {
663-
this.triggers_by_id[trigger.id] = trigger;
653+
this.condition_sel.empty();
654+
this.condition_sel.append($('<option></option>'));
655+
$.each(data.conditions, _.bind(function(_, condition) {
664656
var opt = $('<option></option>');
665657
opt.attr({
666-
value: trigger.id,
658+
value: condition.id,
667659
});
668-
opt.text(trigger.label);
669-
opt.appendTo(this.trigger_sel);
660+
opt.text(condition.label);
661+
opt.appendTo(this.condition_sel);
662+
663+
this.conditions_by_id[condition.id] = condition;
670664
}, this));
665+
666+
this.action_sel.change(this.addAction);
667+
this.condition_sel.change(this.addCondition);
668+
},
669+
670+
addCondition: function() {
671+
var condition = this.conditions_by_id[this.condition_sel.val()];
672+
var row = $('<tr></tr>');
673+
var remove_btn = $('<button class="btn btn-small">Remove</button>');
674+
675+
row.append($('<td>' + condition.html + '</td>'));
676+
row.append($('<td></td>').append(remove_btn));
677+
row.appendTo(this.condition_table_body);
678+
679+
remove_btn.click(function(){
680+
row.remove();
681+
return false;
682+
});
683+
684+
this.condition_sel.data("select2").clear();
685+
this.condition_table.show();
671686
},
672687

673-
addRule: function() {
674-
var trigger = this.triggers_by_id[this.trigger_sel.val()];
688+
addAction: function() {
689+
var action = this.actions_by_id[this.action_sel.val()];
675690
var row = $('<tr></tr>');
676-
var remove_btn = $('<button class="btn btn-small" data-action="remove-rule">Remove</button>');
691+
var remove_btn = $('<button class="btn btn-small">Remove</button>');
677692

678-
row.append($('<td>' + trigger.html + '</td>'));
693+
row.append($('<td>' + action.html + '</td>'));
679694
row.append($('<td></td>').append(remove_btn));
680-
row.appendTo(this.trigger_table_body);
695+
row.appendTo(this.action_table_body);
681696

682697
remove_btn.click(function(){
683698
row.remove();
684699
return false;
685700
});
686701

687-
this.trigger_sel.data("select2").clear();
688-
this.trigger_table.show();
702+
this.action_sel.data("select2").clear();
703+
this.action_table.show();
689704
}
690705

691706
});

src/sentry/static/sentry/scripts/global.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/sentry/templates/sentry/projects/rules/list.html

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
<a href="{% url 'sentry-new-project-rule' project.team.slug project.slug %}" class="btn pull-right btn-primary">{% trans "New Rule" %}</a>
88
<h2>{% trans "Rules" %}</h2>
99
</div>
10+
<p>Below you'll find each available action along with the triggers specified for it. Each action can have any number of triggers.</p>
11+
<h4>Send a notification</h4>
1012
<ul>
11-
{% for rule, label in rule_list %}
12-
<li>{{ label }}</li>
13-
{% endfor %}
13+
<li>When an event is first seen</li>
14+
<li>When an event changes from resolved to unresolved</li>
1415
</ul>
1516
{% endblock %}

src/sentry/templates/sentry/projects/rules/new.html

+20-6
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,36 @@ <h2>{% trans "New Rule" %}</h2>
1717
</fieldset>
1818

1919
<fieldset>
20-
<div><legend>Action</legend></div>
20+
<div><legend>Conditions</legend></div>
21+
<p>When <select name="match" style="width:100px" class="select2-small">
22+
<option value="all">all of</option>
23+
<option value="any">any of</option>
24+
<option value="none">none of</option>
25+
</select> these conditions are met:</p>
26+
<table class="condition-list table table-striped">
27+
<colgroup>
28+
<col>
29+
<col style="width:50px">
30+
</colgroup>
31+
<tbody></tbody>
32+
</table>
2133
<div class="controls">
22-
<select name="action" class="span6" placeholder="select an action"></select>
34+
<select name="condition" class="span6" placeholder="add a condition"></select>
2335
</div>
2436
</fieldset>
2537

2638
<fieldset>
27-
<div><legend>Triggers</legend></div>
28-
<table class="trigger-list table table-striped">
39+
<div><legend>Actions</legend></div>
40+
<p>Take these actions every time this rules matches:</p>
41+
<table class="action-list table table-striped">
2942
<colgroup>
3043
<col>
3144
<col style="width:50px">
3245
</colgroup>
3346
<tbody></tbody>
3447
</table>
3548
<div class="controls">
36-
<select name="trigger" class="span6" placeholder="add a trigger"></select>
49+
<select name="action" class="span6" placeholder="add an action"></select>
3750
</div>
3851
</fieldset>
3952

@@ -45,7 +58,8 @@ <h2>{% trans "New Rule" %}</h2>
4558
<script>
4659
new app.NewProjectRulePage({
4760
el: '#new-rule-form',
48-
rules: {{ rules_by_action|safe }}
61+
actions: {{ action_list|safe }},
62+
conditions: {{ condition_list|safe }}
4963
});
5064
</script>
5165
{% endblock %}

0 commit comments

Comments
 (0)