Merge pull request #90 from cloudflare/status-filter

Replace @silenced & @inhibited filters with @status
This commit is contained in:
Łukasz Mierzwa
2017-05-05 16:53:04 +01:00
committed by GitHub
14 changed files with 124 additions and 284 deletions

View File

@@ -52,7 +52,7 @@ run: $(NAME)
COLOR_LABELS_UNIQUE="instance cluster" \
COLOR_LABELS_STATIC="job" \
DEBUG="$(GIN_DEBUG)" \
FILTER_DEFAULT="@inhibited=false" \
FILTER_DEFAULT="@status=active" \
PORT=$(PORT) \
./$(NAME)

View File

@@ -30,14 +30,14 @@ var Autocomplete = (function() {
// this is used to generate quick filters for label modal
var generateHints = function(label_key, label_val) {
var hints = [];
if (label_key == "@silenced") {
if (label_key == "@status") {
// static list of hints for @silenced label
hints.push("@silenced=true");
hints.push("@silenced=false");
} else if (label_key == "@inhibited") {
// static list of hints for @inhibited label
hints.push("@inhibited=true");
hints.push("@inhibited=false");
hints.push("@status=active");
hints.push("@status=suppressed");
hints.push("@status=unprocessed");
hints.push("@status!=active");
hints.push("@status!=suppressed");
hints.push("@status!=unprocessed");
} else {
// equal and non-equal hints for everything else
hints.push(label_key + "=" + label_val);

View File

@@ -223,12 +223,15 @@ input#filter:focus {
/* make silence description smaller */
.silence-comment {
font-size: smaller;
margin-top: 6px;
margin-bottom: 0;
border-left-color: #18bc9c;
padding-top: 5px;
padding-bottom: 5px;
}
.silence-comment-title {
margin-top: 6px;
display: block;
}
.incident .panel {
margin-bottom: 0;

View File

@@ -5,10 +5,9 @@ var Colors = (function() {
staticColorLabels;
var specialLabels = {
"@silenced: false": "label-danger",
"@silenced: true": "label-success",
"@inhibited: false": "label-danger",
"@inhibited: true": "label-success"
"@status: unprocessed": "label-default",
"@status: active": "label-danger",
"@status: suppressed": "label-success",
};
var update = function(colorData) {

View File

@@ -74,15 +74,9 @@
<%- text %>
</a>
<% }) %>
<% if (alert.inhibitedBy.length) { %>
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-success', label: {key: '@inhibited', value: 'true', text: '@inhibited: true'}}) %>
<% } else { %>
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-danger', label: {key: '@inhibited', value: 'false', text: '@inhibited: false'}}) %>
<% } %>
<% if (alert.silencedBy.length) { %>
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-success', label: {key: '@silenced', value: 'true', text: '@silenced: true'}}) %>
<% } else { %>
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-danger', label: {key: '@silenced', value: 'false', text: '@silenced: false'}}) %>
<% var attrs = Alerts.GetLabelAttrs("@status", alert.status) %>
<%= Templates.Render('buttonLabel', {elem: 'span', attrs: attrs, label: {key: '@status', value: alert.status, text: '@status: ' + alert.status}}) %>
<% if (alert.silencedBy.length == 0) { %>
<% var labels = [] %>
<% _.each(Alerts.SortMapByKey(alert.labels), function(label) { %>
<% labels.push(label.key + '=' + label.value) %>
@@ -110,6 +104,9 @@
</script>
<script type="application/json" id="alert-group-silence">
<small class="silence-comment-title text-muted">
Silenced by:
</small>
<% _.each(alert.silencedBy, function(silenceID) { %>
<div>
<% var silence = silences[silenceID] %>
@@ -217,24 +214,9 @@
<% labelMap[text].hits++ %>
<% } %>
<% }) %>
<% var silencedText = '@silenced: false' %>
<% var isSilenced = 'false' %>
<% if (alert.silencedBy.length) { %>
<% silencedText = '@silenced: true' %>
<% isSilenced = 'true' %>
<% } %>
<% if (labelMap[silencedText] == undefined) { labelMap[silencedText] = {key: '@silenced', value: isSilenced, hits: 0} } %>
<% labelMap[silencedText].hits++ %>
<% var inhibitedText = '@inhibited: false' %>
<% var isInhibited = 'false' %>
<% if (alert.inhibitedBy.length) { %>
<% inhibitedText = '@inhibited: true' %>
<% isInhibited = 'true' %>
<% } %>
<% if (labelMap[inhibitedText] == undefined) { labelMap[inhibitedText] = {key: '@inhibited', value: isInhibited, hits: 0} } %>
<% labelMap[inhibitedText].hits++ %>
<% var statusText = '@status: ' + alert.status %>
<% if (labelMap[statusText] == undefined) { labelMap[statusText] = {key: '@status', value: alert.status, hits: 0} } %>
<% labelMap[statusText].hits++ %>
<% } else { %>
<% var cls_body = '' %>
<% if (i < group.alerts.length - 1) { cls_body = 'incident-group-separator' } %>

View File

@@ -130,40 +130,24 @@
</thead>
<tbody>
<tr>
<td id="help-silenced">
<code>@silenced=(true false)</code>
<td id="help-status">
<code>@status=(active suppresed unprocessed)</code>
</td>
<td>
<p>Match alerts based on silence status.</p>
<p>Match alerts based on the status.</p>
<table class="table examples">
<tbody>
<tr>
<td><span class="label label-info">@silenced=true</span></td>
<td>Match only silenced alerts.</td>
<td><span class="label label-info">@status=active</span></td>
<td>Match only active alerts.</td>
</tr>
<tr>
<td><span class="label label-info">@silenced=false</span></td>
<td>Match only unsilenced alerts.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td id="help-inhibited">
<code>@inhibited=(true false)</code>
</td>
<td>
<p>Match alerts based on inhibition status.</p>
<table class="table examples">
<tbody>
<tr>
<td><span class="label label-info">@inhibited=true</span></td>
<td>Match only inhibited alerts.</td>
<td><span class="label label-info">@status=suppressed</span></td>
<td>Match only suppressed alerts.</td>
</tr>
<tr>
<td><span class="label label-info">@inhibited=false</span></td>
<td>Match only non-inhibited alerts.</td>
<td><span class="label label-info">@status=unprocessed</span></td>
<td>Match only unprocessed alerts.</td>
</tr>
</tbody>
</table>
@@ -179,15 +163,15 @@
<tbody>
<tr>
<td><span class="label label-info">@silence_author=me@domain1.com</span></td>
<td>Match alerts silenced by <em>me@domain1.com</em>.</td>
<td>Match alerts status by <em>me@domain1.com</em>.</td>
</tr>
<tr>
<td><span class="label label-info">@silence_author!=me@domain1.com</span></td>
<td>Match alerts not silenced by <em>me@domain1.com</em>.</td>
<td>Match alerts not status by <em>me@domain1.com</em>.</td>
</tr>
<tr>
<td><span class="label label-info">@silence_author=~@domain2.com</span></td>
<td>Match alerts silenced by username that match regular expression <code>/.*@domain2.com.*/</code>.</td>
<td>Match alerts status by username that match regular expression <code>/.*@domain2.com.*/</code>.</td>
</tr>
</tbody>
</table>
@@ -203,15 +187,15 @@
<tbody>
<tr>
<td><span class="label label-info">@silence_jira=PROJECT-123</span></td>
<td>Match silenced alerts where detected JIRA issue id is equal to <em>PROJECT-123</em>.</td>
<td>Match status alerts where detected JIRA issue id is equal to <em>PROJECT-123</em>.</td>
</tr>
<tr>
<td><span class="label label-info">@silence_jira!=PROJECT-123</span></td>
<td>Match silenced alerts where there was no JIRA issue id detected or it was not equal to <em>PROJECT-123</em>.</td>
<td>Match status alerts where there was no JIRA issue id detected or it was not equal to <em>PROJECT-123</em>.</td>
</tr>
<tr>
<td><span class="label label-info">@silence_jira=~PROJECT</span></td>
<td>Match silenced alerts where dectected JIRA issue id matches regular expression <code>/.*PROJECT.*/</code>.</td>
<td>Match status alerts where dectected JIRA issue id matches regular expression <code>/.*PROJECT.*/</code>.</td>
</tr>
</tbody>
</table>

File diff suppressed because one or more lines are too long

View File

@@ -1,72 +0,0 @@
package filters
import (
"fmt"
"strings"
"github.com/cloudflare/unsee/models"
)
type inhibitedFilter struct {
alertFilter
}
func (filter *inhibitedFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
filter.Matched = name
if matcher != nil {
filter.Matcher = *matcher
}
filter.RawText = rawText
filter.IsValid = isValid
switch value {
case "true":
filter.Value = true
case "false":
filter.Value = false
default:
filter.IsValid = false
}
}
func (filter *inhibitedFilter) Match(alert *models.Alert, matches int) bool {
if filter.IsValid {
isMatch := filter.Matcher.Compare(alert.IsInhibited(), filter.Value)
if isMatch {
filter.Hits++
}
return isMatch
}
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
panic(e)
}
func newInhibitedFilter() FilterT {
f := inhibitedFilter{}
return &f
}
func inhibitedAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete {
tokens := []models.Autocomplete{}
for _, operator := range operators {
switch operator {
case equalOperator:
tokens = append(tokens, makeAC(
fmt.Sprintf("%s%strue", name, operator),
[]string{
name,
strings.TrimPrefix(name, "@"),
fmt.Sprintf("%s%s", name, operator),
},
))
tokens = append(tokens, makeAC(
fmt.Sprintf("%s%sfalse", name, operator),
[]string{
name,
strings.TrimPrefix(name, "@"),
fmt.Sprintf("%s%s", name, operator),
},
))
}
}
return tokens
}

View File

@@ -1,72 +0,0 @@
package filters
import (
"fmt"
"strings"
"github.com/cloudflare/unsee/models"
)
type silencedFilter struct {
alertFilter
}
func (filter *silencedFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
filter.Matched = name
if matcher != nil {
filter.Matcher = *matcher
}
filter.RawText = rawText
filter.IsValid = isValid
switch value {
case "true":
filter.Value = true
case "false":
filter.Value = false
default:
filter.IsValid = false
}
}
func (filter *silencedFilter) Match(alert *models.Alert, matches int) bool {
if filter.IsValid {
isMatch := filter.Matcher.Compare(alert.IsSilenced(), filter.Value)
if isMatch {
filter.Hits++
}
return isMatch
}
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
panic(e)
}
func newSilencedFilter() FilterT {
f := silencedFilter{}
return &f
}
func silencedAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete {
tokens := []models.Autocomplete{}
for _, operator := range operators {
switch operator {
case equalOperator:
tokens = append(tokens, makeAC(
fmt.Sprintf("%s%strue", name, operator),
[]string{
name,
strings.TrimPrefix(name, "@"),
fmt.Sprintf("%s%s", name, operator),
},
))
tokens = append(tokens, makeAC(
fmt.Sprintf("%s%sfalse", name, operator),
[]string{
name,
strings.TrimPrefix(name, "@"),
fmt.Sprintf("%s%s", name, operator),
},
))
}
}
return tokens
}

59
filters/filter_status.go Normal file
View File

@@ -0,0 +1,59 @@
package filters
import (
"fmt"
"strings"
"github.com/cloudflare/unsee/models"
)
type statusFilter struct {
alertFilter
}
func (filter *statusFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
filter.Matched = name
if matcher != nil {
filter.Matcher = *matcher
}
filter.RawText = rawText
filter.IsValid = isValid
filter.Value = value
if !stringInSlice(models.AlertStateList, value) {
filter.IsValid = false
}
}
func (filter *statusFilter) Match(alert *models.Alert, matches int) bool {
if filter.IsValid {
isMatch := filter.Matcher.Compare(alert.Status, filter.Value)
if isMatch {
filter.Hits++
}
return isMatch
}
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
panic(e)
}
func newstatusFilter() FilterT {
f := statusFilter{}
return &f
}
func statusAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete {
tokens := []models.Autocomplete{}
for _, operator := range operators {
for _, alert := range alerts {
tokens = append(tokens, makeAC(
name+operator+alert.Status,
[]string{
name,
strings.TrimPrefix(name, "@"),
name + operator,
},
))
}
}
return tokens
}

View File

@@ -20,100 +20,67 @@ type filterTest struct {
var tests = []filterTest{
filterTest{
Expression: "@silenced=true",
Expression: "@status=active",
IsValid: true,
Alert: models.Alert{},
IsMatch: false,
},
filterTest{
Expression: "@silenced!=true",
Expression: "@status!=active",
IsValid: true,
Alert: models.Alert{},
IsMatch: true,
},
filterTest{
Expression: "@silenced=true",
Expression: "@status=suppressed",
IsValid: true,
Alert: models.Alert{Status: "suppressed", SilencedBy: []string{"1"}},
IsMatch: true,
},
filterTest{
Expression: "@silenced!=true",
Expression: "@status!=suppressed",
IsValid: true,
Alert: models.Alert{Status: "suppressed", SilencedBy: []string{"1"}},
IsMatch: false,
},
filterTest{
Expression: "@silenced=xx",
Expression: "@status=xx",
IsValid: false,
},
filterTest{
Expression: "@silenced=:xx",
Expression: "@status=:xx",
IsValid: false,
},
filterTest{
Expression: "@silenced==xx",
Expression: "@status==xx",
IsValid: false,
},
filterTest{
Expression: "@silenced=~true",
Expression: "@status=~true",
IsValid: false,
},
filterTest{
Expression: "@silenced=~false",
Expression: "@status=~false",
IsValid: false,
},
filterTest{
Expression: "@inhibited=true",
IsValid: true,
Alert: models.Alert{},
IsMatch: false,
},
filterTest{
Expression: "@inhibited!=true",
IsValid: true,
Alert: models.Alert{},
IsMatch: true,
},
filterTest{
Expression: "@inhibited=true",
Expression: "@status=suppressed",
IsValid: true,
Alert: models.Alert{Status: "suppressed", InhibitedBy: []string{"999"}},
IsMatch: true,
},
filterTest{
Expression: "@inhibited=true",
Expression: "@status=suppressed",
IsValid: true,
Alert: models.Alert{Status: "active"},
IsMatch: false,
},
filterTest{
Expression: "@inhibited!=true",
Expression: "@status!=suppressed",
IsValid: true,
Alert: models.Alert{Status: "suppressed", InhibitedBy: []string{"999"}},
IsMatch: false,
},
filterTest{
Expression: "@inhibited=xx",
IsValid: false,
},
filterTest{
Expression: "@inhibited=:xx",
IsValid: false,
},
filterTest{
Expression: "@inhibited==xx",
IsValid: false,
},
filterTest{
Expression: "@inhibited=~true",
IsValid: false,
},
filterTest{
Expression: "@inhibited=~false",
IsValid: false,
},
filterTest{
Expression: "@silence_jira=1",

View File

@@ -36,16 +36,10 @@ type filterConfig struct {
// support
var AllFilters = []filterConfig{
filterConfig{
Label: "@silenced",
Label: "@status",
SupportedOperators: []string{equalOperator, notEqualOperator},
Factory: newSilencedFilter,
Autocomplete: silencedAutocomplete,
},
filterConfig{
Label: "@inhibited",
SupportedOperators: []string{equalOperator, notEqualOperator},
Factory: newInhibitedFilter,
Autocomplete: inhibitedAutocomplete,
Factory: newstatusFilter,
Autocomplete: statusAutocomplete,
},
filterConfig{
Label: "@age",

View File

@@ -7,7 +7,6 @@ import (
"io"
"net/http"
"sort"
"strconv"
"strings"
"time"
@@ -163,17 +162,14 @@ func alerts(c *gin.Context) {
io.WriteString(h, string(aj))
if alert.IsSilenced() {
countLabel(counters, "@silenced", "true")
for _, silenceID := range alert.SilencedBy {
if silence := store.Store.GetSilence(silenceID); silence != nil {
silences[silenceID] = *silence
}
}
} else {
countLabel(counters, "@silenced", "false")
}
countLabel(counters, "@inhibited", strconv.FormatBool(alert.IsInhibited()))
countLabel(counters, "@status", alert.Status)
if alert.IsActive() {
agCopy.ActiveCount++

View File

@@ -149,7 +149,7 @@ func TestAlerts(t *testing.T) {
if ur.Status != "success" {
t.Errorf("[%s] Invalid status in response: %s", version, ur.Status)
}
if len(ur.Counters) != 6 {
if len(ur.Counters) != 5 {
t.Errorf("[%s] Invalid number of counters in response (%d): %v", version, len(ur.Counters), ur.Counters)
}
for _, ag := range ur.AlertGroups {
@@ -280,16 +280,16 @@ var acTests = []acTestCase{
"@age<1h",
"@age>10m",
"@age>1h",
"@inhibited=false",
"@inhibited=true",
"@limit=10",
"@limit=50",
"@silence_author!=john@example.com",
"@silence_author!~john@example.com",
"@silence_author=john@example.com",
"@silence_author=~john@example.com",
"@silenced=false",
"@silenced=true",
"@status!=active",
"@status!=suppressed",
"@status=active",
"@status=suppressed",
},
},
acTestCase{