feat(http): add notification rule handler
parent
de7f5dd8e0
commit
dcda49d1d0
|
@ -18,25 +18,26 @@ import (
|
|||
// APIHandler is a collection of all the service handlers.
|
||||
type APIHandler struct {
|
||||
influxdb.HTTPErrorHandler
|
||||
BucketHandler *BucketHandler
|
||||
UserHandler *UserHandler
|
||||
OrgHandler *OrgHandler
|
||||
AuthorizationHandler *AuthorizationHandler
|
||||
DashboardHandler *DashboardHandler
|
||||
LabelHandler *LabelHandler
|
||||
AssetHandler *AssetHandler
|
||||
ChronografHandler *ChronografHandler
|
||||
ScraperHandler *ScraperHandler
|
||||
SourceHandler *SourceHandler
|
||||
VariableHandler *VariableHandler
|
||||
TaskHandler *TaskHandler
|
||||
TelegrafHandler *TelegrafHandler
|
||||
QueryHandler *FluxHandler
|
||||
WriteHandler *WriteHandler
|
||||
DocumentHandler *DocumentHandler
|
||||
SetupHandler *SetupHandler
|
||||
SessionHandler *SessionHandler
|
||||
SwaggerHandler http.Handler
|
||||
BucketHandler *BucketHandler
|
||||
UserHandler *UserHandler
|
||||
OrgHandler *OrgHandler
|
||||
AuthorizationHandler *AuthorizationHandler
|
||||
DashboardHandler *DashboardHandler
|
||||
LabelHandler *LabelHandler
|
||||
AssetHandler *AssetHandler
|
||||
ChronografHandler *ChronografHandler
|
||||
ScraperHandler *ScraperHandler
|
||||
SourceHandler *SourceHandler
|
||||
VariableHandler *VariableHandler
|
||||
TaskHandler *TaskHandler
|
||||
TelegrafHandler *TelegrafHandler
|
||||
QueryHandler *FluxHandler
|
||||
WriteHandler *WriteHandler
|
||||
DocumentHandler *DocumentHandler
|
||||
SetupHandler *SetupHandler
|
||||
SessionHandler *SessionHandler
|
||||
SwaggerHandler http.Handler
|
||||
NotificationRuleHandler *NotificationRuleHandler
|
||||
}
|
||||
|
||||
// APIBackend is all services and associated parameters required to construct
|
||||
|
@ -80,6 +81,7 @@ type APIBackend struct {
|
|||
ChronografService *server.Service
|
||||
OrgLookupService authorizer.OrganizationService
|
||||
DocumentService influxdb.DocumentService
|
||||
NotificationRuleStore influxdb.NotificationRuleStore
|
||||
}
|
||||
|
||||
// PrometheusCollectors exposes the prometheus collectors associated with an APIBackend.
|
||||
|
@ -158,6 +160,11 @@ func NewAPIHandler(b *APIBackend) *APIHandler {
|
|||
telegrafBackend.TelegrafService = authorizer.NewTelegrafConfigService(b.TelegrafService, b.UserResourceMappingService)
|
||||
h.TelegrafHandler = NewTelegrafHandler(telegrafBackend)
|
||||
|
||||
notificationRuleBackend := NewNotificationRuleBackend(b)
|
||||
notificationRuleBackend.NotificationRuleStore = authorizer.NewNotificationRuleStore(b.NotificationRuleStore,
|
||||
b.UserResourceMappingService, b.OrganizationService)
|
||||
h.NotificationRuleHandler = NewNotificationRuleHandler(notificationRuleBackend)
|
||||
|
||||
writeBackend := NewWriteBackend(b)
|
||||
h.WriteHandler = NewWriteHandler(writeBackend)
|
||||
|
||||
|
@ -180,10 +187,11 @@ var apiLinks = map[string]interface{}{
|
|||
"external": map[string]string{
|
||||
"statusFeed": "https://www.influxdata.com/feed/json",
|
||||
},
|
||||
"labels": "/api/v2/labels",
|
||||
"variables": "/api/v2/variables",
|
||||
"me": "/api/v2/me",
|
||||
"orgs": "/api/v2/orgs",
|
||||
"labels": "/api/v2/labels",
|
||||
"variables": "/api/v2/variables",
|
||||
"me": "/api/v2/me",
|
||||
"notificationRules": "/api/v2/notificationRules",
|
||||
"orgs": "/api/v2/orgs",
|
||||
"query": map[string]string{
|
||||
"self": "/api/v2/query",
|
||||
"ast": "/api/v2/query/ast",
|
||||
|
@ -302,6 +310,11 @@ func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/v2/notificationRules") {
|
||||
h.NotificationRuleHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/v2/variables") {
|
||||
h.VariableHandler.ServeHTTP(w, r)
|
||||
return
|
||||
|
|
|
@ -0,0 +1,442 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
pctx "github.com/influxdata/influxdb/context"
|
||||
"github.com/influxdata/influxdb/notification/rule"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NotificationRuleBackend is all services and associated parameters required to construct
|
||||
// the NotificationRuleBackendHandler.
|
||||
type NotificationRuleBackend struct {
|
||||
influxdb.HTTPErrorHandler
|
||||
Logger *zap.Logger
|
||||
|
||||
NotificationRuleStore influxdb.NotificationRuleStore
|
||||
UserResourceMappingService influxdb.UserResourceMappingService
|
||||
LabelService influxdb.LabelService
|
||||
UserService influxdb.UserService
|
||||
OrganizationService influxdb.OrganizationService
|
||||
}
|
||||
|
||||
// NewNotificationRuleBackend returns a new instance of NotificationRuleBackend.
|
||||
func NewNotificationRuleBackend(b *APIBackend) *NotificationRuleBackend {
|
||||
return &NotificationRuleBackend{
|
||||
HTTPErrorHandler: b.HTTPErrorHandler,
|
||||
Logger: b.Logger.With(zap.String("handler", "notification_rule")),
|
||||
|
||||
NotificationRuleStore: b.NotificationRuleStore,
|
||||
UserResourceMappingService: b.UserResourceMappingService,
|
||||
LabelService: b.LabelService,
|
||||
UserService: b.UserService,
|
||||
OrganizationService: b.OrganizationService,
|
||||
}
|
||||
}
|
||||
|
||||
// NotificationRuleHandler is the handler for the notification rule service
|
||||
type NotificationRuleHandler struct {
|
||||
*httprouter.Router
|
||||
influxdb.HTTPErrorHandler
|
||||
Logger *zap.Logger
|
||||
|
||||
NotificationRuleStore influxdb.NotificationRuleStore
|
||||
UserResourceMappingService influxdb.UserResourceMappingService
|
||||
LabelService influxdb.LabelService
|
||||
UserService influxdb.UserService
|
||||
OrganizationService influxdb.OrganizationService
|
||||
}
|
||||
|
||||
const (
|
||||
notificationRulesPath = "/api/v2/notificationRules"
|
||||
notificationRulesIDPath = "/api/v2/notificationRules/:id"
|
||||
notificationRulesIDMembersPath = "/api/v2/notificationRules/:id/members"
|
||||
notificationRulesIDMembersIDPath = "/api/v2/notificationRules/:id/members/:userID"
|
||||
notificationRulesIDOwnersPath = "/api/v2/notificationRules/:id/owners"
|
||||
notificationRulesIDOwnersIDPath = "/api/v2/notificationRules/:id/owners/:userID"
|
||||
notificationRulesIDLabelsPath = "/api/v2/notificationRules/:id/labels"
|
||||
notificationRulesIDLabelsIDPath = "/api/v2/notificationRules/:id/labels/:lid"
|
||||
)
|
||||
|
||||
// NewNotificationRuleHandler returns a new instance of NotificationRuleHandler.
|
||||
func NewNotificationRuleHandler(b *NotificationRuleBackend) *NotificationRuleHandler {
|
||||
h := &NotificationRuleHandler{
|
||||
Router: NewRouter(b.HTTPErrorHandler),
|
||||
HTTPErrorHandler: b.HTTPErrorHandler,
|
||||
Logger: b.Logger,
|
||||
|
||||
NotificationRuleStore: b.NotificationRuleStore,
|
||||
UserResourceMappingService: b.UserResourceMappingService,
|
||||
LabelService: b.LabelService,
|
||||
UserService: b.UserService,
|
||||
OrganizationService: b.OrganizationService,
|
||||
}
|
||||
h.HandlerFunc("POST", notificationRulesPath, h.handlePostNotificationRule)
|
||||
h.HandlerFunc("GET", notificationRulesPath, h.handleGetNotificationRules)
|
||||
h.HandlerFunc("GET", notificationRulesIDPath, h.handleGetNotificationRule)
|
||||
h.HandlerFunc("DELETE", notificationRulesIDPath, h.handleDeleteNotificationRule)
|
||||
h.HandlerFunc("PUT", notificationRulesIDPath, h.handlePutNotificationRule)
|
||||
|
||||
memberBackend := MemberBackend{
|
||||
HTTPErrorHandler: b.HTTPErrorHandler,
|
||||
Logger: b.Logger.With(zap.String("handler", "member")),
|
||||
ResourceType: influxdb.NotificationRuleResourceType,
|
||||
UserType: influxdb.Member,
|
||||
UserResourceMappingService: b.UserResourceMappingService,
|
||||
UserService: b.UserService,
|
||||
}
|
||||
h.HandlerFunc("POST", notificationRulesIDMembersPath, newPostMemberHandler(memberBackend))
|
||||
h.HandlerFunc("GET", notificationRulesIDMembersPath, newGetMembersHandler(memberBackend))
|
||||
h.HandlerFunc("DELETE", notificationRulesIDMembersIDPath, newDeleteMemberHandler(memberBackend))
|
||||
|
||||
ownerBackend := MemberBackend{
|
||||
HTTPErrorHandler: b.HTTPErrorHandler,
|
||||
Logger: b.Logger.With(zap.String("handler", "member")),
|
||||
ResourceType: influxdb.NotificationRuleResourceType,
|
||||
UserType: influxdb.Owner,
|
||||
UserResourceMappingService: b.UserResourceMappingService,
|
||||
UserService: b.UserService,
|
||||
}
|
||||
h.HandlerFunc("POST", notificationRulesIDOwnersPath, newPostMemberHandler(ownerBackend))
|
||||
h.HandlerFunc("GET", notificationRulesIDOwnersPath, newGetMembersHandler(ownerBackend))
|
||||
h.HandlerFunc("DELETE", notificationRulesIDOwnersIDPath, newDeleteMemberHandler(ownerBackend))
|
||||
|
||||
labelBackend := &LabelBackend{
|
||||
HTTPErrorHandler: b.HTTPErrorHandler,
|
||||
Logger: b.Logger.With(zap.String("handler", "label")),
|
||||
LabelService: b.LabelService,
|
||||
ResourceType: influxdb.TelegrafsResourceType,
|
||||
}
|
||||
h.HandlerFunc("GET", notificationRulesIDLabelsIDPath, newGetLabelsHandler(labelBackend))
|
||||
h.HandlerFunc("POST", notificationRulesIDLabelsPath, newPostLabelHandler(labelBackend))
|
||||
h.HandlerFunc("DELETE", notificationRulesIDLabelsIDPath, newDeleteLabelHandler(labelBackend))
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type notificationRuleLinks struct {
|
||||
Self string `json:"self"`
|
||||
Labels string `json:"labels"`
|
||||
Members string `json:"members"`
|
||||
Owners string `json:"owners"`
|
||||
}
|
||||
|
||||
type notificationRuleResponse struct {
|
||||
influxdb.NotificationRule
|
||||
Labels []influxdb.Label `json:"labels"`
|
||||
Links notificationRuleLinks `json:"links"`
|
||||
}
|
||||
|
||||
func (resp notificationRuleResponse) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(resp.NotificationRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b2, err := json.Marshal(struct {
|
||||
Labels []influxdb.Label `json:"labels"`
|
||||
Links notificationRuleLinks `json:"links"`
|
||||
}{
|
||||
Links: resp.Links,
|
||||
Labels: resp.Labels,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(string(b1[:len(b1)-1]) + ", " + string(b2[1:])), nil
|
||||
}
|
||||
|
||||
type notificationRulesResponse struct {
|
||||
NotificationRules []*notificationRuleResponse `json:"notificationRules"`
|
||||
Links *influxdb.PagingLinks `json:"links"`
|
||||
}
|
||||
|
||||
func newNotificationRuleResponse(nr influxdb.NotificationRule, labels []*influxdb.Label) *notificationRuleResponse {
|
||||
res := ¬ificationRuleResponse{
|
||||
NotificationRule: nr,
|
||||
Links: notificationRuleLinks{
|
||||
Self: fmt.Sprintf("/api/v2/notificationRules/%s", nr.GetID()),
|
||||
Labels: fmt.Sprintf("/api/v2/notificationRules/%s/labels", nr.GetID()),
|
||||
Members: fmt.Sprintf("/api/v2/notificationRules/%s/members", nr.GetID()),
|
||||
Owners: fmt.Sprintf("/api/v2/notificationRules/%s/owners", nr.GetID()),
|
||||
},
|
||||
Labels: []influxdb.Label{},
|
||||
}
|
||||
|
||||
for _, l := range labels {
|
||||
res.Labels = append(res.Labels, *l)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func newNotificationRulesResponse(ctx context.Context, nrs []influxdb.NotificationRule, labelService influxdb.LabelService, f influxdb.PagingFilter, opts influxdb.FindOptions) *notificationRulesResponse {
|
||||
resp := ¬ificationRulesResponse{
|
||||
NotificationRules: make([]*notificationRuleResponse, len(nrs)),
|
||||
Links: newPagingLinks(notificationRulesPath, opts, f, len(nrs)),
|
||||
}
|
||||
for i, nr := range nrs {
|
||||
labels, _ := labelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: nr.GetID()})
|
||||
resp.NotificationRules[i] = newNotificationRuleResponse(nr, labels)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func decodeGetNotificationRuleRequest(ctx context.Context, r *http.Request) (i influxdb.ID, err error) {
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return i, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "url missing id",
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return i, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (h *NotificationRuleHandler) handleGetNotificationRules(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
h.Logger.Debug("notification rules retrieve request", zap.String("r", fmt.Sprint(r)))
|
||||
filter, opts, err := decodeNotificationRuleFilter(ctx, r)
|
||||
if err != nil {
|
||||
h.Logger.Debug("failed to decode request", zap.Error(err))
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
nrs, _, err := h.NotificationRuleStore.FindNotificationRules(ctx, *filter, *opts)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
h.Logger.Debug("notification rules retrieved", zap.String("notificationRules", fmt.Sprint(nrs)))
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newNotificationRulesResponse(ctx, nrs, h.LabelService, filter, *opts)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationRuleHandler) handleGetNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
h.Logger.Debug("notification rule retrieve request", zap.String("r", fmt.Sprint(r)))
|
||||
id, err := decodeGetNotificationRuleRequest(ctx, r)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
nr, err := h.NotificationRuleStore.FindNotificationRuleByID(ctx, id)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
h.Logger.Debug("notification rule retrieved", zap.String("notificationRule", fmt.Sprint(nr)))
|
||||
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: nr.GetID()})
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newNotificationRuleResponse(nr, labels)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func decodeNotificationRuleFilter(ctx context.Context, r *http.Request) (*influxdb.NotificationRuleFilter, *influxdb.FindOptions, error) {
|
||||
f := &influxdb.NotificationRuleFilter{}
|
||||
urm, err := decodeUserResourceMappingFilter(ctx, r, influxdb.NotificationRuleResourceType)
|
||||
if err == nil {
|
||||
f.UserResourceMappingFilter = *urm
|
||||
}
|
||||
|
||||
opts, err := decodeFindOptions(ctx, r)
|
||||
if err != nil {
|
||||
return f, nil, err
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
if orgIDStr := q.Get("orgID"); orgIDStr != "" {
|
||||
orgID, err := influxdb.IDFromString(orgIDStr)
|
||||
if err != nil {
|
||||
return f, opts, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "orgID is invalid",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
f.OrgID = orgID
|
||||
} else if orgNameStr := q.Get("org"); orgNameStr != "" {
|
||||
*f.Organization = orgNameStr
|
||||
}
|
||||
return f, opts, err
|
||||
}
|
||||
|
||||
func decodeUserResourceMappingFilter(ctx context.Context, r *http.Request, typ influxdb.ResourceType) (*influxdb.UserResourceMappingFilter, error) {
|
||||
q := r.URL.Query()
|
||||
f := &influxdb.UserResourceMappingFilter{
|
||||
ResourceType: typ,
|
||||
}
|
||||
if idStr := q.Get("resourceID"); idStr != "" {
|
||||
id, err := influxdb.IDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.ResourceID = *id
|
||||
}
|
||||
|
||||
if idStr := q.Get("userID"); idStr != "" {
|
||||
id, err := influxdb.IDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.UserID = *id
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func decodePostNotificationRuleRequest(ctx context.Context, r *http.Request) (influxdb.NotificationRule, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
defer r.Body.Close()
|
||||
nr, err := rule.UnmarshalJSON(buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nr, nil
|
||||
}
|
||||
|
||||
func decodePutNotificationRuleRequest(ctx context.Context, r *http.Request) (influxdb.NotificationRule, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
defer r.Body.Close()
|
||||
nr, err := rule.UnmarshalJSON(buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
params := httprouter.ParamsFromContext(ctx)
|
||||
id := params.ByName("id")
|
||||
if id == "" {
|
||||
return nil, &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "url missing id",
|
||||
}
|
||||
}
|
||||
i := new(influxdb.ID)
|
||||
if err := i.DecodeFromString(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nr.SetID(*i)
|
||||
return nr, nil
|
||||
}
|
||||
|
||||
// handlePostNotificationRule is the HTTP handler for the POST /api/v2/notificationRules route.
|
||||
func (h *NotificationRuleHandler) handlePostNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
h.Logger.Debug("notification rule create request", zap.String("r", fmt.Sprint(r)))
|
||||
nr, err := decodePostNotificationRuleRequest(ctx, r)
|
||||
if err != nil {
|
||||
h.Logger.Debug("failed to decode request", zap.Error(err))
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
auth, err := pctx.GetAuthorizer(ctx)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.NotificationRuleStore.CreateNotificationRule(ctx, nr, auth.GetUserID()); err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
h.Logger.Debug("notification rule created", zap.String("notificationRule", fmt.Sprint(nr)))
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusCreated, newNotificationRuleResponse(nr, []*influxdb.Label{})); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handlePutNotificationRule is the HTTP handler for the PUT /api/v2/notificationRule route.
|
||||
func (h *NotificationRuleHandler) handlePutNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
h.Logger.Debug("notification rule update request", zap.String("r", fmt.Sprint(r)))
|
||||
nr, err := decodePutNotificationRuleRequest(ctx, r)
|
||||
if err != nil {
|
||||
h.Logger.Debug("failed to decode request", zap.Error(err))
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
auth, err := pctx.GetAuthorizer(ctx)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
nr, err = h.NotificationRuleStore.UpdateNotificationRule(ctx, nr.GetID(), nr, auth.GetUserID())
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: nr.GetID()})
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
h.Logger.Debug("notification rule updated", zap.String("notificationRule", fmt.Sprint(nr)))
|
||||
|
||||
if err := encodeResponse(ctx, w, http.StatusOK, newNotificationRuleResponse(nr, labels)); err != nil {
|
||||
logEncodingError(h.Logger, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationRuleHandler) handleDeleteNotificationRule(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
h.Logger.Debug("notification rule delete request", zap.String("r", fmt.Sprint(r)))
|
||||
i, err := decodeGetNotificationRuleRequest(ctx, r)
|
||||
if err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.NotificationRuleStore.DeleteNotificationRule(ctx, i); err != nil {
|
||||
h.HandleHTTPError(ctx, err, w)
|
||||
return
|
||||
}
|
||||
h.Logger.Debug("notification rule deleted", zap.String("notificationRuleID", fmt.Sprint(i)))
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/notification"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
"github.com/influxdata/influxdb/notification/rule"
|
||||
influxTesting "github.com/influxdata/influxdb/testing"
|
||||
)
|
||||
|
||||
func Test_newNotificationRuleResponses(t *testing.T) {
|
||||
type args struct {
|
||||
opt influxdb.FindOptions
|
||||
filter influxdb.NotificationRuleFilter
|
||||
nrs []influxdb.NotificationRule
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
args: args{
|
||||
opt: influxdb.FindOptions{
|
||||
Limit: 50,
|
||||
Offset: 0,
|
||||
Descending: true,
|
||||
},
|
||||
filter: influxdb.NotificationRuleFilter{
|
||||
OrgID: influxTesting.IDPtr(influxdb.ID(2)),
|
||||
},
|
||||
nrs: []influxdb.NotificationRule{
|
||||
&rule.Slack{
|
||||
Channel: "ch1",
|
||||
MessageTemplate: "message 1{var1}",
|
||||
Base: rule.Base{
|
||||
ID: influxdb.ID(1),
|
||||
OrgID: influxdb.ID(2),
|
||||
AuthorizationID: influxdb.ID(3),
|
||||
EndpointID: influxTesting.IDPtr(influxdb.ID(4)),
|
||||
Name: "name1",
|
||||
Description: "desc1",
|
||||
Status: influxdb.Active,
|
||||
Every: influxdb.Duration{Duration: time.Minute * 5},
|
||||
Offset: influxdb.Duration{Duration: time.Second * 15},
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: notification.Tag{Key: "k1", Value: "v1"},
|
||||
Operator: notification.Equal,
|
||||
},
|
||||
{
|
||||
Tag: notification.Tag{Key: "k2", Value: "v2"},
|
||||
Operator: notification.NotRegexEqual,
|
||||
},
|
||||
},
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.LevelRule{CheckLevel: notification.Critical, Operation: true},
|
||||
Count: 3,
|
||||
Period: influxdb.Duration{Duration: time.Hour},
|
||||
},
|
||||
{
|
||||
CurrentLevel: notification.LevelRule{CheckLevel: notification.Warn, Operation: false},
|
||||
Count: 30,
|
||||
Period: influxdb.Duration{Duration: time.Minute * 30},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&rule.SMTP{
|
||||
To: "example@domain1.com, example@domain2.com",
|
||||
SubjectTemp: "subject 2{var2}",
|
||||
BodyTemp: "body 2{var2}",
|
||||
Base: rule.Base{
|
||||
ID: influxdb.ID(11),
|
||||
OrgID: influxdb.ID(2),
|
||||
AuthorizationID: influxdb.ID(33),
|
||||
EndpointID: influxTesting.IDPtr(influxdb.ID(44)),
|
||||
Name: "name2",
|
||||
Description: "desc2",
|
||||
Status: influxdb.Inactive,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: `{
|
||||
"links": {
|
||||
"self": "/api/v2/notificationRules?descending=true&limit=50&offset=0&orgID=0000000000000002"
|
||||
},
|
||||
"notificationRules": [
|
||||
{
|
||||
"authorizationID": "0000000000000003",
|
||||
"channel": "ch1",
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"description": "desc1",
|
||||
"endpointID": "0000000000000004",
|
||||
"every": "5m0s",
|
||||
"id": "0000000000000001",
|
||||
"messageTemplate": "message 1{var1}",
|
||||
"name": "name1",
|
||||
"offset": "15s",
|
||||
"orgID": "0000000000000002",
|
||||
"runbookLink": "",
|
||||
"status": "active",
|
||||
"statusRules": [
|
||||
{
|
||||
"count": 3,
|
||||
"currentLevel": {
|
||||
"level": "CRIT",
|
||||
"operation": "equal"
|
||||
},
|
||||
"period": "1h0m0s",
|
||||
"previousLevel": null
|
||||
},
|
||||
{
|
||||
"count": 30,
|
||||
"currentLevel": {
|
||||
"level": "WARN",
|
||||
"operation": "notequal"
|
||||
},
|
||||
"period": "30m0s",
|
||||
"previousLevel": null
|
||||
}
|
||||
],
|
||||
"tagRules": [
|
||||
{
|
||||
"key": "k1",
|
||||
"operator": "equal",
|
||||
"value": "v1"
|
||||
},
|
||||
{
|
||||
"key": "k2",
|
||||
"operator": "notequalregex",
|
||||
"value": "v2"
|
||||
}
|
||||
],
|
||||
"type": "slack",
|
||||
"updatedAt": "0001-01-01T00:00:00Z",
|
||||
"labels": [],
|
||||
"links": {
|
||||
"labels": "/api/v2/notificationRules/0000000000000001/labels",
|
||||
"members": "/api/v2/notificationRules/0000000000000001/members",
|
||||
"owners": "/api/v2/notificationRules/0000000000000001/owners",
|
||||
"self": "/api/v2/notificationRules/0000000000000001"
|
||||
}
|
||||
},
|
||||
{
|
||||
"authorizationID": "0000000000000021",
|
||||
"bodyTemplate": "body 2{var2}",
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"description": "desc2",
|
||||
"endpointID": "000000000000002c",
|
||||
"every": "0s",
|
||||
"id": "000000000000000b",
|
||||
"name": "name2",
|
||||
"offset": "0s",
|
||||
"orgID": "0000000000000002",
|
||||
"runbookLink": "",
|
||||
"status": "inactive",
|
||||
"subjectTemplate": "subject 2{var2}",
|
||||
"to": "example@domain1.com, example@domain2.com",
|
||||
"type": "smtp",
|
||||
"updatedAt": "0001-01-01T00:00:00Z",
|
||||
"labels": [],
|
||||
"links": {
|
||||
"labels": "/api/v2/notificationRules/000000000000000b/labels",
|
||||
"members": "/api/v2/notificationRules/000000000000000b/members",
|
||||
"owners": "/api/v2/notificationRules/000000000000000b/owners",
|
||||
"self": "/api/v2/notificationRules/000000000000000b"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
res := newNotificationRulesResponse(ctx, tt.args.nrs, mock.NewLabelService(), tt.args.filter, tt.args.opt)
|
||||
got, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
t.Fatalf("newNotificationRulesResponse() JSON marshal %v", err)
|
||||
}
|
||||
if eq, diff, _ := jsonEqual(string(got), tt.want); tt.want != "" && !eq {
|
||||
t.Errorf("%q. newNotificationRulesResponse() = ***%s***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newNotificationRuleResponse(t *testing.T) {
|
||||
type args struct {
|
||||
nr influxdb.NotificationRule
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
args: args{
|
||||
nr: &rule.Slack{
|
||||
Channel: "ch1",
|
||||
MessageTemplate: "message 1{var1}",
|
||||
Base: rule.Base{
|
||||
ID: influxdb.ID(1),
|
||||
OrgID: influxdb.ID(2),
|
||||
AuthorizationID: influxdb.ID(3),
|
||||
EndpointID: influxTesting.IDPtr(influxdb.ID(4)),
|
||||
Name: "name1",
|
||||
Description: "desc1",
|
||||
Status: influxdb.Active,
|
||||
Every: influxdb.Duration{Duration: time.Minute * 5},
|
||||
Offset: influxdb.Duration{Duration: time.Second * 15},
|
||||
TagRules: []notification.TagRule{
|
||||
{
|
||||
Tag: notification.Tag{Key: "k1", Value: "v1"},
|
||||
Operator: notification.Equal,
|
||||
},
|
||||
{
|
||||
Tag: notification.Tag{Key: "k2", Value: "v2"},
|
||||
Operator: notification.NotRegexEqual,
|
||||
},
|
||||
},
|
||||
StatusRules: []notification.StatusRule{
|
||||
{
|
||||
CurrentLevel: notification.LevelRule{CheckLevel: notification.Critical, Operation: true},
|
||||
Count: 3,
|
||||
Period: influxdb.Duration{Duration: time.Hour},
|
||||
},
|
||||
{
|
||||
CurrentLevel: notification.LevelRule{CheckLevel: notification.Warn, Operation: true},
|
||||
Count: 30,
|
||||
Period: influxdb.Duration{Duration: time.Minute * 30},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: `{
|
||||
"channel": "ch1",
|
||||
"messageTemplate": "message 1{var1}",
|
||||
"id": "0000000000000001",
|
||||
"orgID": "0000000000000002",
|
||||
"authorizationID": "0000000000000003",
|
||||
"endpointID": "0000000000000004",
|
||||
"name": "name1",
|
||||
"description": "desc1",
|
||||
"every": "5m0s",
|
||||
"offset": "15s",
|
||||
"type": "slack",
|
||||
"runbookLink": "",
|
||||
"status": "active",
|
||||
"statusRules": [
|
||||
{
|
||||
"count": 3,
|
||||
"currentLevel": {
|
||||
"level": "CRIT",
|
||||
"operation": "equal"
|
||||
},
|
||||
"period": "1h0m0s",
|
||||
"previousLevel": null
|
||||
},
|
||||
{
|
||||
"count": 30,
|
||||
"currentLevel": {
|
||||
"level": "WARN",
|
||||
"operation": "equal"
|
||||
},
|
||||
"period": "30m0s",
|
||||
"previousLevel": null
|
||||
}
|
||||
],
|
||||
"tagRules": [
|
||||
{
|
||||
"key": "k1",
|
||||
"operator": "equal",
|
||||
"value": "v1"
|
||||
},
|
||||
{
|
||||
"key": "k2",
|
||||
"operator": "notequalregex",
|
||||
"value": "v2"
|
||||
}
|
||||
],
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"updatedAt": "0001-01-01T00:00:00Z",
|
||||
"labels": [
|
||||
],
|
||||
"links": {
|
||||
"labels": "/api/v2/notificationRules/0000000000000001/labels",
|
||||
"members": "/api/v2/notificationRules/0000000000000001/members",
|
||||
"owners": "/api/v2/notificationRules/0000000000000001/owners",
|
||||
"self": "/api/v2/notificationRules/0000000000000001"
|
||||
}
|
||||
|
||||
}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res := newNotificationRuleResponse(tt.args.nr, []*influxdb.Label{})
|
||||
got, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
t.Fatalf("newNotificationRuleResponse() JSON marshal %v", err)
|
||||
}
|
||||
if eq, diff, _ := jsonEqual(string(got), tt.want); tt.want != "" && !eq {
|
||||
t.Errorf("%q. newNotificationRuleResponse() = ***%s***", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -18,7 +18,10 @@ func decodeFindOptions(ctx context.Context, r *http.Request) (*platform.FindOpti
|
|||
if offset := qp.Get("offset"); offset != "" {
|
||||
o, err := strconv.Atoi(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "offset is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
opts.Offset = o
|
||||
|
@ -27,7 +30,10 @@ func decodeFindOptions(ctx context.Context, r *http.Request) (*platform.FindOpti
|
|||
if limit := qp.Get("limit"); limit != "" {
|
||||
l, err := strconv.Atoi(limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "limit is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
if l < 1 || l > platform.MaxPageSize {
|
||||
|
@ -49,7 +55,10 @@ func decodeFindOptions(ctx context.Context, r *http.Request) (*platform.FindOpti
|
|||
if descending := qp.Get("descending"); descending != "" {
|
||||
desc, err := strconv.ParseBool(descending)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &platform.Error{
|
||||
Code: platform.EInvalid,
|
||||
Msg: "descending is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
opts.Descending = desc
|
||||
|
|
|
@ -300,7 +300,7 @@ func (h *TelegrafHandler) handleGetTelegraf(w http.ResponseWriter, r *http.Reque
|
|||
|
||||
func decodeTelegrafConfigFilter(ctx context.Context, r *http.Request) (*platform.TelegrafConfigFilter, error) {
|
||||
f := &platform.TelegrafConfigFilter{}
|
||||
urm, err := decodeUserResourceMappingFilter(ctx, r)
|
||||
urm, err := decodeUserResourceMappingFilter(ctx, r, platform.TelegrafsResourceType)
|
||||
if err == nil {
|
||||
f.UserResourceMappingFilter = *urm
|
||||
}
|
||||
|
@ -322,29 +322,6 @@ func decodeTelegrafConfigFilter(ctx context.Context, r *http.Request) (*platform
|
|||
return f, err
|
||||
}
|
||||
|
||||
func decodeUserResourceMappingFilter(ctx context.Context, r *http.Request) (*platform.UserResourceMappingFilter, error) {
|
||||
q := r.URL.Query()
|
||||
f := &platform.UserResourceMappingFilter{
|
||||
ResourceType: platform.TelegrafsResourceType,
|
||||
}
|
||||
if idStr := q.Get("resourceID"); idStr != "" {
|
||||
id, err := platform.IDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.ResourceID = *id
|
||||
}
|
||||
|
||||
if idStr := q.Get("userID"); idStr != "" {
|
||||
id, err := platform.IDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.UserID = *id
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func decodePostTelegrafRequest(ctx context.Context, r *http.Request) (*platform.TelegrafConfig, error) {
|
||||
tc := new(platform.TelegrafConfig)
|
||||
err := json.NewDecoder(r.Body).Decode(tc)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationRuleStore = &NotificationRuleStore{}
|
||||
|
||||
// NotificationRuleStore represents a service for managing notification rule data.
|
||||
type NotificationRuleStore struct {
|
||||
OrganizationService
|
||||
UserResourceMappingService
|
||||
FindNotificationRuleByIDF func(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error)
|
||||
FindNotificationRulesF func(ctx context.Context, filter influxdb.NotificationRuleFilter, opt ...influxdb.FindOptions) ([]influxdb.NotificationRule, int, error)
|
||||
CreateNotificationRuleF func(ctx context.Context, nr influxdb.NotificationRule, userID influxdb.ID) error
|
||||
UpdateNotificationRuleF func(ctx context.Context, id influxdb.ID, nr influxdb.NotificationRule, userID influxdb.ID) (influxdb.NotificationRule, error)
|
||||
DeleteNotificationRuleF func(ctx context.Context, id influxdb.ID) error
|
||||
}
|
||||
|
||||
// FindNotificationRuleByID returns a single telegraf config by ID.
|
||||
func (s *NotificationRuleStore) FindNotificationRuleByID(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error) {
|
||||
return s.FindNotificationRuleByIDF(ctx, id)
|
||||
}
|
||||
|
||||
// FindNotificationRules returns a list of notification rules that match filter and the total count of matching notification rules.
|
||||
// Additional options provide pagination & sorting.
|
||||
func (s *NotificationRuleStore) FindNotificationRules(ctx context.Context, filter influxdb.NotificationRuleFilter, opt ...influxdb.FindOptions) ([]influxdb.NotificationRule, int, error) {
|
||||
return s.FindNotificationRulesF(ctx, filter, opt...)
|
||||
}
|
||||
|
||||
// CreateNotificationRule creates a new notification rule and sets ID with the new identifier.
|
||||
func (s *NotificationRuleStore) CreateNotificationRule(ctx context.Context, nr influxdb.NotificationRule, userID influxdb.ID) error {
|
||||
return s.CreateNotificationRuleF(ctx, nr, userID)
|
||||
}
|
||||
|
||||
// UpdateNotificationRule updates a single notification rule.
|
||||
// Returns the new notification rule after update.
|
||||
func (s *NotificationRuleStore) UpdateNotificationRule(ctx context.Context, id influxdb.ID, nr influxdb.NotificationRule, userID influxdb.ID) (influxdb.NotificationRule, error) {
|
||||
return s.UpdateNotificationRuleF(ctx, id, nr, userID)
|
||||
}
|
||||
|
||||
// DeleteNotificationRule removes a notification rule by ID.
|
||||
func (s *NotificationRuleStore) DeleteNotificationRule(ctx context.Context, id influxdb.ID) error {
|
||||
return s.DeleteNotificationRuleF(ctx, id)
|
||||
}
|
Loading…
Reference in New Issue