Skip to content

Commit 7ff56fb

Browse files
committed
More form prototyping
1 parent 47c7d0f commit 7ff56fb

File tree

9 files changed

+152
-40
lines changed

9 files changed

+152
-40
lines changed

src/sentry/rules.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Rule(object):
5555

5656
form_cls = None
5757
action_label = None
58-
condition_label = None
58+
trigger_label = None
5959

6060
@classmethod
6161
def from_params(cls, project, data=None):
@@ -76,12 +76,12 @@ def after(self, event, is_new, is_regression, **kwargs):
7676
pass
7777

7878
def render_label(self):
79-
return ('%s %s' % (self.action_label, self.condition_label)).format(
79+
return ('%s %s' % (self.action_label, self.trigger_label)).format(
8080
**self.instance.data)
8181

8282
def render_form(self, form_data):
8383
if not self.form_cls:
84-
return self.condition_label
84+
return self.trigger_label
8585

8686
form = self.form_cls(
8787
form_data,
@@ -93,7 +93,7 @@ def replace_field(match):
9393
field = match.group(1)
9494
return unicode(form[field])
9595

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

9898
def save(self, form_data):
9999
instance = self.instance
@@ -112,7 +112,7 @@ def save(self, form_data):
112112

113113

114114
class NotifyRule(Rule):
115-
action_label = 'I want to send notifications when'
115+
action_label = 'Send a notification'
116116

117117
def notify(self, event):
118118
# TODO: fire off plugin notifications
@@ -127,14 +127,14 @@ def should_notify(self, event):
127127

128128

129129
class NotifyOnFirstSeenRule(NotifyRule):
130-
condition_label = 'an event is first seen'
130+
trigger_label = 'An event is first seen'
131131

132132
def should_notify(self, event, is_new, **kwargs):
133133
return is_new
134134

135135

136136
class NotifyOnRegressionRule(NotifyRule):
137-
condition_label = 'an event changes state from resolved to unresolved'
137+
trigger_label = 'An event changes state from resolved to unresolved'
138138

139139
def should_notify(self, event, is_regression, **kwargs):
140140
return is_regression
@@ -146,7 +146,7 @@ class NotifyOnTimesSeenForm(forms.Form):
146146

147147
class NotifyOnTimesSeenRule(NotifyRule):
148148
form_cls = NotifyOnTimesSeenForm
149-
condition_label = 'an event is seen more than {num} times'
149+
trigger_label = 'An event is seen more than {num} times'
150150

151151
def should_notify(self, event):
152152
return event.times_seen == self.get_option('num')

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

+71
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,77 @@
619619

620620
});
621621

622+
623+
app.NewProjectRulePage = BasePage.extend({
624+
625+
initialize: function(data){
626+
BasePage.prototype.initialize.apply(this, arguments);
627+
628+
_.bindAll(this, 'populateTriggers', 'addRule');
629+
630+
this.rules_by_action = {}
631+
this.triggers_by_id = {}
632+
this.el = $(data.el);
633+
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');
637+
638+
this.action_sel.empty();
639+
$.each(data.rules, _.bind(function(_, action) {
640+
var opt = $('<option></option>');
641+
opt.attr({
642+
value: action.label,
643+
});
644+
opt.text(action.label);
645+
opt.appendTo(this.action_sel);
646+
647+
this.rules_by_action[action.label] = action;
648+
}, this));
649+
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;
664+
var opt = $('<option></option>');
665+
opt.attr({
666+
value: trigger.id,
667+
});
668+
opt.text(trigger.label);
669+
opt.appendTo(this.trigger_sel);
670+
}, this));
671+
},
672+
673+
addRule: function() {
674+
var trigger = this.triggers_by_id[this.trigger_sel.val()];
675+
var row = $('<tr></tr>');
676+
var remove_btn = $('<button class="btn btn-small" data-action="remove-rule">Remove</button>');
677+
678+
row.append($('<td>' + trigger.html + '</td>'));
679+
row.append($('<td></td>').append(remove_btn));
680+
row.appendTo(this.trigger_table_body);
681+
682+
remove_btn.click(function(){
683+
row.remove();
684+
return false;
685+
});
686+
687+
this.trigger_sel.data("select2").clear();
688+
this.trigger_table.show();
689+
}
690+
691+
});
692+
622693
Backbone.sync = function(method, model, success, error){
623694
success();
624695
};

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/static/sentry/scripts/utils.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
if (href.indexOf('?') == -1)
3636
return vars;
37-
37+
3838
hashes = href.slice(href.indexOf('?') + 1, (href.indexOf('#') != -1 ? href.indexOf('#') : href.length)).split('&');
3939
$.each(hashes, function(_, chunk){
4040
hash = chunk.split('=');
@@ -56,7 +56,7 @@
5656
var b, x, y, o, p;
5757

5858
number = parseInt(number, 10);
59-
59+
6060
for (var i=0; (b=number_formats[i]); i++){
6161
x = b[0];
6262
y = b[1];
@@ -107,7 +107,7 @@
107107
slugify: function(str) {
108108
str = str.replace(/^\s+|\s+$/g, ''); // trim
109109
str = str.toLowerCase();
110-
110+
111111
// remove accents, swap ñ for n, etc
112112
var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
113113
var to = "aaaaeeeeiiiioooouuuunc------";
@@ -278,7 +278,8 @@
278278
var $this = $(this),
279279
options = {
280280
width: 'element',
281-
allowClear: false
281+
allowClear: false,
282+
minimumResultsForSearch: 10,
282283
};
283284

284285
if ($this.attr('data-allowClear')) {

src/sentry/static/sentry/styles/global.min.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -7551,7 +7551,7 @@ a.icon-share:hover {
75517551
padding: 0 5px 0 45px;
75527552
line-height: 20px;
75537553
height: 20px;
7554-
min-width: 20px;
7554+
width: 20px;
75557555
text-align: right;
75567556
float: left;
75577557
display: block;

src/sentry/static/sentry/styles/sentry.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -7551,7 +7551,7 @@ a.icon-share:hover {
75517551
padding: 0 5px 0 45px;
75527552
line-height: 20px;
75537553
height: 20px;
7554-
min-width: 20px;
7554+
width: 20px;
75557555
text-align: right;
75567556
float: left;
75577557
display: block;

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

+36-13
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,45 @@
77
<div class="page-header">
88
<h2>{% trans "New Rule" %}</h2>
99
</div>
10-
<form method="POST" action="">
10+
<form method="POST" action="" id="new-rule-form">
1111
{% csrf_token %}
12-
<ul class="rule-list">
13-
{% for action_label, rule_choices in rules_by_action %}
14-
<li class="rule-action">
15-
<label><input type="radio" name="action" value="{{ action_label }}"> {{ action_label }}</label>
16-
<ul class="rule-choices">
17-
{% for rule_id, rule in rule_choices %}
18-
<li><label><input type="radio" name="rule" value="{{ rule_id }}"{% if rule_id == selected_rule %} checked="checked"{% endif %}> {{ rule }}</label></li>
19-
{% endfor %}
20-
</ul>
21-
</li>
22-
{% endfor %}
23-
</ul>
12+
13+
<p>A rule consists of a single action and one or more triggers.</p>
14+
<fieldset>
15+
<div><legend>Details</legend></div>
16+
{% include "sentry/partial/form_base.html" %}
17+
</fieldset>
18+
19+
<fieldset>
20+
<div><legend>Action</legend></div>
21+
<div class="controls">
22+
<select name="action" class="span6" placeholder="select an action"></select>
23+
</div>
24+
</fieldset>
25+
26+
<fieldset>
27+
<div><legend>Triggers</legend></div>
28+
<table class="trigger-list table table-striped">
29+
<colgroup>
30+
<col>
31+
<col style="width:50px">
32+
</colgroup>
33+
<tbody></tbody>
34+
</table>
35+
<div class="controls">
36+
<select name="trigger" class="span6" placeholder="add a trigger"></select>
37+
</div>
38+
</fieldset>
39+
2440
<div class="actions">
2541
<button type="submit" class="btn btn-primary">{% trans "Save Rule" %}</button>
2642
</div>
2743
</form>
44+
45+
<script>
46+
new app.NewProjectRulePage({
47+
el: '#new-rule-form',
48+
rules: {{ rules_by_action|safe }}
49+
});
50+
</script>
2851
{% endblock %}

src/sentry/web/forms/projects.py

+7
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,10 @@ def save(self):
209209
ProjectOption.objects.set_value(
210210
self.project, 'quotas:per_minute', self.cleaned_data['per_minute'] or ''
211211
)
212+
213+
214+
class NewRuleForm(forms.Form):
215+
label = forms.CharField(
216+
label=_('Label'),
217+
widget=forms.TextInput(attrs={'placeholder': 'e.g. My Custom Rule'}),
218+
)

src/sentry/web/frontend/projects.py

+22-12
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
from sentry.permissions import (
2323
can_remove_project, can_add_project_key, can_remove_project_key)
2424
from sentry.plugins import plugins
25+
from sentry.utils import json
2526
from sentry.web.decorators import login_required, has_access
2627
from sentry.web.forms.projects import (
2728
ProjectTagsForm, RemoveProjectForm, EditProjectForm,
28-
NotificationTagValuesForm, AlertSettingsForm, ProjectQuotasForm)
29+
NotificationTagValuesForm, AlertSettingsForm, ProjectQuotasForm,
30+
NewRuleForm)
2931
from sentry.web.helpers import render_to_response, plugin_config
3032

3133

@@ -56,7 +58,8 @@ def remove_project(request, team, project):
5658
delete_project.delay(object_id=project.id)
5759
project.update(status=STATUS_HIDDEN)
5860

59-
messages.add_message(request, messages.SUCCESS,
61+
messages.add_message(
62+
request, messages.SUCCESS,
6063
_('Deletion has been queued and should occur shortly.'))
6164
elif removal_type == '2':
6265
new_project = form.cleaned_data['project']
@@ -454,12 +457,12 @@ def list_rules(request, team, project):
454457
def new_rule(request, team, project):
455458
from sentry.rules import RULES
456459

457-
selected_rule = request.POST.get('rule')
460+
form = NewRuleForm(request.POST or None)
458461

459-
if request.POST:
460-
rule_cls = RULES[selected_rule]
461-
rule = rule_cls.from_params(project)
462-
rule.save(request.POST)
462+
if request.POST and form.is_valid():
463+
# rule_cls = RULES[selected_rule]
464+
# rule = rule_cls.from_params(project)
465+
# rule.save(request.POST)
463466

464467
messages.add_message(
465468
request, messages.SUCCESS,
@@ -471,16 +474,23 @@ def new_rule(request, team, project):
471474
rules_by_action = defaultdict(list)
472475
for rule_id, rule_cls in RULES.iteritems():
473476
rule = rule_cls.from_params(project)
474-
rules_by_action[rule.action_label].append(
475-
(rule_id, rule.render_form(request.POST))
476-
)
477+
rules_by_action[rule.action_label].append({
478+
'id': rule_id,
479+
'label': rule.trigger_label,
480+
'html': rule.render_form(request.POST),
481+
})
482+
483+
rule_list = [{
484+
'label': k,
485+
'triggers': v,
486+
} for k, v in rules_by_action.iteritems()]
477487

478488
context = csrf(request)
479489
context.update({
480490
'team': team,
481491
'page': 'rules',
482-
'rules_by_action': rules_by_action.items(),
483-
'selected_rule': selected_rule,
492+
'rules_by_action': json.dumps(rule_list),
493+
'form': form,
484494
'project': project,
485495
})
486496

0 commit comments

Comments
 (0)