Skip to content

Commit

Permalink
支持创建规则时展示消息样例和匹配测试
Browse files Browse the repository at this point in the history
  • Loading branch information
mylxsw committed Sep 5, 2020
1 parent ea7936a commit ba0c4d2
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 35 deletions.
78 changes: 59 additions & 19 deletions api/controller/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package controller

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -13,6 +12,7 @@ import (
"github.com/mylxsw/adanos-alert/internal/matcher"
"github.com/mylxsw/adanos-alert/internal/repository"
"github.com/mylxsw/adanos-alert/pkg/array"
"github.com/mylxsw/adanos-alert/pkg/misc"
"github.com/mylxsw/adanos-alert/pkg/template"
"github.com/mylxsw/adanos-alert/pubsub"
"github.com/mylxsw/asteria/log"
Expand Down Expand Up @@ -45,7 +45,6 @@ func (r RuleController) Register(router *web.Router) {
})

router.Group("/rules-test/", func(router *web.Router) {
router.Post("/rule-message/", r.TestMessageMatch).Name("rules:test:rule-message")
router.Post("/rule-check/{type}/", r.Check).Name("rules:test:check")
})
}
Expand Down Expand Up @@ -205,27 +204,64 @@ func (r RuleForm) Validate(req web.Request) error {
}

// Check validate the rule
func (r RuleController) Check(ctx web.Context) web.Response {
func (r RuleController) Check(ctx web.Context, msgRepo repository.MessageRepo) web.Response {
content := ctx.Input("content")
msgID := ctx.Input("msg_id")

var err error
switch repository.TemplateType(ctx.PathVar("type")) {
case repository.TemplateTypeMatchRule:
_, err = matcher.NewMessageMatcher(repository.Rule{Rule: content})
if msgID != "" {
matched, err := r.testMessageMatchRule(content, msgID, msgRepo)
if err != nil {
return ctx.JSON(web.M{
"error": err,
"msg": "",
})
}

return ctx.JSON(web.M{
"error": nil,
"msg": misc.IfElse(matched, "与当前 message 匹配", "与当前 message 不匹配"),
})
} else {
_, err = matcher.NewMessageMatcher(repository.Rule{Rule: content})
}
case repository.TemplateTypeTriggerRule:
_, err = matcher.NewTriggerMatcher(repository.Trigger{PreCondition: content})
case repository.TemplateTypeTemplate:
_, err = template.CreateParser(content)
case "aggregate_rule":
_, err = matcher.NewMessageFinger(content)
finger, err1 := matcher.NewMessageFinger(content)
if err1 == nil {
if msgID != "" {
msg, err1 := r.getMessageByID(msgID, msgRepo)
if err1 == nil {
res, err1 := finger.Run(msg)
if err1 == nil {
return ctx.JSON(web.M{
"error": nil,
"msg": fmt.Sprintf("当前 message 聚合 Key 为 %s", res),
})
} else {
err = err1
}
} else {
err = err1
}
}
} else {
err = err1
}
}

if err != nil {
return ctx.JSON(web.M{"error": err.Error()})
return ctx.JSON(web.M{"error": err.Error(), "msg": ""})
}

return ctx.JSON(web.M{
"error": nil,
"msg": "",
})
}

Expand Down Expand Up @@ -483,29 +519,33 @@ func (r RuleController) Delete(ctx web.Context, em event.Manager, repo repositor
return repo.DeleteID(id)
}

// TestMessageMatch test if the message and rule can be matched
func (r RuleController) TestMessageMatch(ctx web.Context) web.Response {
rule := ctx.Input("rule")
message := ctx.Input("message")
func (r RuleController) getMessageByID(messageID string, msgRepo repository.MessageRepo) (repository.Message, error) {
msgID, err := primitive.ObjectIDFromHex(messageID)
if err != nil {
return repository.Message{}, fmt.Errorf("invalid message_id: %v", err)
}

var msg repository.Message
if err := json.Unmarshal([]byte(message), &msg); err != nil {
return ctx.JSONError(fmt.Sprintf("invalid message: %v", err), http.StatusUnprocessableEntity)
return msgRepo.Get(msgID)
}

// testMessageMatchRule test if the message and rule can be matched
func (r RuleController) testMessageMatchRule(rule string, messageID string, msgRepo repository.MessageRepo) (bool, error) {
message, err := r.getMessageByID(messageID, msgRepo)
if err != nil {
return false, err
}

m, err := matcher.NewMessageMatcher(repository.Rule{Rule: rule})
if err != nil {
return ctx.JSONError(fmt.Sprintf("invalid rule: %v", err), http.StatusUnprocessableEntity)
return false, fmt.Errorf("invalid rule: %v", err)
}

rs, err := m.Match(msg)
rs, err := m.Match(message)
if err != nil {
return ctx.JSONError(fmt.Sprintf("rule match with errors: %v", err), http.StatusUnprocessableEntity)
return false, fmt.Errorf("rule match with errors: %v", err)
}

return ctx.JSON(bson.M{
"matched": rs,
})
return rs, nil
}

// Tags return all tags existed
Expand Down
43 changes: 35 additions & 8 deletions dashboard/src/components/MessageCard.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,57 @@
<template>
<b-card :header-bg-variant="message.status === 'canceled' ? 'warning': ''">
<template v-slot:header>
<b title="创建时间">
<b title="创建时间" v-if="!title">
<b-badge v-if="message_index != null" class="mr-2" variant="primary"># {{ message.seq_num }}</b-badge>
<date-time :value="message.created_at"></date-time>
</b>
<span v-if="title">{{ title }}</span>
<div class="float-right" title="状态">
<b-badge v-if="message.status === 'pending'" variant="dark">准备中</b-badge>
<b-badge v-if="message.status === 'grouped'" variant="success">已分组</b-badge>
<b-badge v-if="message.status === 'canceled'" variant="danger">无规则,已取消</b-badge>
<b-badge v-if="message.status === 'expired'" variant="warning">匹配规则,已过期</b-badge>

<b-link class="ml-2" @click="isFold = !isFold">
<b-icon icon="arrows-collapse" v-if="isFold"></b-icon>
<b-icon icon="arrows-expand" v-if="!isFold"></b-icon>
</b-link>
</div>

</template>
<b-card-text v-if="isFold">...</b-card-text>

<template v-slot:footer v-if="!onlyShow">
<div class="float-right">
<b-button size="sm" class="ml-2" variant="warning" @click="testMatchedRules(message.id)" v-if="testMatchedRules">测试</b-button>
<b-button :to="{path:'/rules/add', query: {test_message_id: message.id}}" target="_blank" class="ml-2" size="sm" variant="primary">
创建规则
</b-button>
</div>
</template>

<b-card-text>
<b-card-text v-if="!isFold">
<b-row style="max-width: 100rem;" class="adanos-meta-line" v-if="message.group_ids != null && message.group_ids.length > 0">
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">分组ID</b></b-col>
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">分组 ID</b></b-col>
<b-col sm="9" style="text-align: left">
<b-link v-for="(g, index) in message.group_ids" :key="index" class="mr-1" :to="{path:'/messages', query: {group_id: g}}">{{ g }}</b-link>
</b-col>
</b-row>
<b-row style="max-width: 100rem;" class="adanos-meta-line">
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">标签</b></b-col>
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">Tags</b></b-col>
<b-col sm="9" style="text-align: left"><b-badge v-for="(tag, index) in message.tags" :key="index" class="mr-1">{{ tag }}</b-badge></b-col>
</b-row>
<b-row style="max-width: 100rem;" class="mb-2 adanos-meta-line">
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">来源</b></b-col>
<b-col sm="3"><b class="text-black-50" style="border-bottom: 1px dashed black">Origin</b></b-col>
<b-col sm="9"><b-badge variant="light">{{ message.origin }}</b-badge></b-col>
</b-row>
<b-row v-for="(val, key) in message.meta" :key="key" style="max-width: 100rem;" class="adanos-meta-line">
<b-row v-for="(val, key) in message.meta" :key="key" style="max-width: 100rem;" class="adanos-meta-line" title="Meta" v-b-tooltip>
<b-col sm="3"><b class="text-dark" style="border-bottom: 1px dashed black">{{ key }}</b></b-col>
<b-col sm="9"><pre class="adanos-code"><code>{{ val }}</code></pre></b-col>
</b-row>
</b-card-text>
<b-card-text>
<code><pre class="adanos-code">{{ message.content }}</pre></code>
<b-card-text v-if="!isFold">
<code><pre class="adanos-code" v-b-tooltip title="Content">{{ message.content }}</pre></code>
</b-card-text>
</b-card>
</template>
Expand All @@ -47,8 +63,19 @@
message: Object,
message_index: Number,
testMatchedRules: Function,
onlyShow: Boolean,
title: String,
fold: Boolean,
},
data() {
return {
isFold: false,
}
},
methods: {
},
mounted() {
this.isFold = this.fold;
}
}
</script>
Expand Down
8 changes: 6 additions & 2 deletions dashboard/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import App from './App.vue'
import router from './router'
import store from './store'

import { BootstrapVueIcons } from 'bootstrap-vue'

import { library } from '@fortawesome/fontawesome-svg-core'
import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

import DateTime from "./components/DateTime";
Expand All @@ -17,8 +19,11 @@ import Paginator from "./components/Paginator";
import MessageCard from "./components/MessageCard";

library.add(faExternalLinkAlt);
library.add(faPlus)
Vue.component('font-awesome-icon', FontAwesomeIcon);

Vue.use(BootstrapVueIcons);

Vue.component('DateTime', DateTime);
Vue.component('HumanTime', HumanTime);
Vue.component('MessageCard', MessageCard)
Expand Down Expand Up @@ -47,7 +52,6 @@ Vue.prototype.ToastSuccess = function (message) {
};

Vue.prototype.ToastError = function (message) {
console.log("Error: ", message);
this.$bvToast.toast(this.ParseError(message), {
title: 'ERROR',
variant: 'danger'
Expand Down
5 changes: 4 additions & 1 deletion dashboard/src/views/Messages.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
</b-form>
</b-card-text>
</b-card>
<MessageCard v-for="(message, index) in messages" :key="index" class="mb-3" :message="message" :message_index="index" :test-matched-rules="testMatchedRules"></MessageCard>
<MessageCard v-for="(message, index) in messages" :key="index" class="mb-3"
:message="message"
:message_index="index"
:test-matched-rules="testMatchedRules"></MessageCard>
<b-card v-if="messages.length === 0">
<b-card-body>There are no records to show</b-card-body>
</b-card>
Expand Down
16 changes: 14 additions & 2 deletions dashboard/src/views/RuleEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
</b-card>
</b-card-group>

<MessageCard class="mb-3" title="待匹配消息示例" :fold="true" v-if="test_message_id !== null" :message="test_message" :message_index="0" :onlyShow="true"></MessageCard>

<b-card-group class="mb-3">
<b-card header="规则">
<p class="text-muted">分组匹配规则,作用于单条 message,用于判断该 message 是否与当前规则匹配。
Expand Down Expand Up @@ -632,6 +634,8 @@ export default {
templateRules: helpers.templates,
dingdingTemplateRules: helpers.templates,
},
test_message_id: this.$route.query.test_message_id !== undefined ? this.$route.query.test_message_id : null,
test_message: {},
};
},
computed: {
Expand Down Expand Up @@ -703,9 +707,9 @@ export default {
* 发送规则检查请求
*/
sendCheckRequest(type, content) {
axios.post('/api/rules-test/rule-check/' + type + '/', {content: content}).then(resp => {
axios.post('/api/rules-test/rule-check/' + type + '/', {content: content, msg_id: this.test_message_id}).then(resp => {
if (resp.data.error === null || resp.data.error === "") {
this.SuccessBox('检查通过');
this.SuccessBox('检查通过' + (resp.data.msg !== '' ? '' + resp.data.msg : ''));
} else {
this.ErrorBox('检查不通过:' + resp.data.error);
}
Expand Down Expand Up @@ -975,6 +979,14 @@ export default {
})).catch((error) => {
this.ToastError(error)
});
if (this.test_message_id !== null && this.test_message_id !== '') {
axios.get('/api/messages/' + this.test_message_id + '/').then(resp => {
this.test_message = resp.data;
}).catch((error) => {
this.ToastError(error);
})
}
}
}
</script>
Expand Down
6 changes: 3 additions & 3 deletions internal/job/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (a *AggregationJob) groupingMessages(msgRepo repository.MessageRepo, groupR

// if the message matched a rule, update message's group_id and skip to next message
if matched {
aggregateKey := buildMessageFinger(m.Rule().AggregateRule, msg)
aggregateKey := BuildMessageFinger(m.Rule().AggregateRule, msg)
key := fmt.Sprintf("%s:%s", m.Rule().ID.Hex(), aggregateKey)
if _, ok := collectingGroups[key]; !ok {
grp, err := groupRepo.CollectingGroup(m.Rule().ToGroupRule(aggregateKey))
Expand Down Expand Up @@ -170,7 +170,7 @@ func (a *AggregationJob) pendingMessageGroup(groupRepo repository.MessageGroupRe
})
}

func buildMessageFinger(groupRule string, msg repository.Message) string {
func BuildMessageFinger(groupRule string, msg repository.Message) string {
finger, err := matcher.NewMessageFinger(groupRule)
if err != nil {
log.WithFields(log.Fields{
Expand Down Expand Up @@ -215,7 +215,7 @@ func BuildMessageMatchTest(ruleRepo repository.RuleRepo) func(msg repository.Mes
if matched {
matchedRules = append(matchedRules, MatchedRule{
Rule: m.Rule(),
AggregateKey: buildMessageFinger(m.Rule().AggregateRule, msg),
AggregateKey: BuildMessageFinger(m.Rule().AggregateRule, msg),
})
}
}
Expand Down

0 comments on commit ba0c4d2

Please sign in to comment.