Skip to content

Commit

Permalink
feat(api): query params to event history (#1072)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko authored Aug 15, 2024
1 parent 64f2f54 commit 0197720
Show file tree
Hide file tree
Showing 17 changed files with 532 additions and 62 deletions.
6 changes: 3 additions & 3 deletions api/controller/contact_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestGetContactEventsByIdWithLimit(t *testing.T) {

Convey("Ensure that request with default parameters would return both event items (no url params specified)", t, func() {
dataBase.EXPECT().GetContact(contact.ID).Return(contactExpect, nil).AnyTimes()
dataBase.EXPECT().GetNotificationsHistoryByContactId(contact.ID, defaultFromParameter, defaultToParameter, defaultPage, defaultSize).Return(items, nil)
dataBase.EXPECT().GetNotificationsHistoryByContactID(contact.ID, defaultFromParameter, defaultToParameter, defaultPage, defaultSize).Return(items, nil)

actualEvents, err := GetContactEventsHistoryByID(dataBase, contact.ID, defaultFromParameter, defaultToParameter, defaultPage, defaultSize)

Expand All @@ -91,7 +91,7 @@ func TestGetContactEventsByIdWithLimit(t *testing.T) {

Convey("Ensure that request with only 'from' parameter given and 'to' default will return only one (newest) event", t, func() {
dataBase.EXPECT().GetContact(contact.ID).Return(contactExpect, nil).AnyTimes()
dataBase.EXPECT().GetNotificationsHistoryByContactId(contact.ID, defaultFromParameter-20, defaultToParameter, defaultPage, defaultSize).Return(items[:1], nil)
dataBase.EXPECT().GetNotificationsHistoryByContactID(contact.ID, defaultFromParameter-20, defaultToParameter, defaultPage, defaultSize).Return(items[:1], nil)

actualEvents, err := GetContactEventsHistoryByID(dataBase, contact.ID, defaultFromParameter-20, defaultToParameter, defaultPage, defaultSize)
So(err, ShouldBeNil)
Expand All @@ -104,7 +104,7 @@ func TestGetContactEventsByIdWithLimit(t *testing.T) {

Convey("Ensure that request with only 'to' parameter given and 'from' default will return only one (oldest) event", t, func() {
dataBase.EXPECT().GetContact(contact.ID).Return(contactExpect, nil).AnyTimes()
dataBase.EXPECT().GetNotificationsHistoryByContactId(contact.ID, defaultFromParameter, defaultToParameter-30, defaultPage, defaultSize).Return(items[1:], nil)
dataBase.EXPECT().GetNotificationsHistoryByContactID(contact.ID, defaultFromParameter, defaultToParameter-30, defaultPage, defaultSize).Return(items[1:], nil)

actualEvents, err := GetContactEventsHistoryByID(dataBase, contact.ID, defaultFromParameter, defaultToParameter-30, defaultPage, defaultSize)
So(err, ShouldBeNil)
Expand Down
81 changes: 77 additions & 4 deletions api/controller/events.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package controller

import (
"regexp"

"github.com/moira-alert/moira"
"github.com/moira-alert/moira/api"
"github.com/moira-alert/moira/api/dto"
)

// GetTriggerEvents gets trigger event from current page and all trigger event count.
func GetTriggerEvents(database moira.Database, triggerID string, page int64, size int64) (*dto.EventsList, *api.ErrorResponse) {
events, err := database.GetNotificationEvents(triggerID, page*size, size-1)
// GetTriggerEvents gets trigger event from current page and all trigger event count. Events list is filtered by time range
// with `from` and `to` params (`from` and `to` should be "+inf", "-inf" or int64 converted to string),
// by metric (regular expression) and by states. If `states` map is empty or nil then all states are accepted.
func GetTriggerEvents(
database moira.Database,
triggerID string,
page, size int64,
from, to string,
metricRegexp *regexp.Regexp,
states map[string]struct{},
) (*dto.EventsList, *api.ErrorResponse) {
events, err := getFilteredNotificationEvents(database, triggerID, page, size, from, to, metricRegexp, states)
if err != nil {
return nil, api.ErrorInternalServer(err)
}
Expand All @@ -18,7 +29,7 @@ func GetTriggerEvents(database moira.Database, triggerID string, page int64, siz
Size: size,
Page: page,
Total: eventCount,
List: make([]moira.NotificationEvent, 0),
List: make([]moira.NotificationEvent, 0, len(events)),
}
for _, event := range events {
if event != nil {
Expand All @@ -28,6 +39,68 @@ func GetTriggerEvents(database moira.Database, triggerID string, page int64, siz
return eventsList, nil
}

func getFilteredNotificationEvents(
database moira.Database,
triggerID string,
page, size int64,
from, to string,
metricRegexp *regexp.Regexp,
states map[string]struct{},
) ([]*moira.NotificationEvent, error) {
// fetch all events
if size < 0 {
events, err := database.GetNotificationEvents(triggerID, page, size, from, to)
if err != nil {
return nil, err
}

return filterNotificationEvents(events, metricRegexp, states), nil
}

// fetch at most `size` events
filtered := make([]*moira.NotificationEvent, 0, size)
var count int64

for int64(len(filtered)) < size {
eventsData, err := database.GetNotificationEvents(triggerID, page+count, size, from, to)
if err != nil {
return nil, err
}

if len(eventsData) == 0 {
break
}

filtered = append(filtered, filterNotificationEvents(eventsData, metricRegexp, states)...)
count += 1

if int64(len(eventsData)) < size {
break
}
}

return filtered, nil
}

func filterNotificationEvents(
notificationEvents []*moira.NotificationEvent,
metricRegexp *regexp.Regexp,
states map[string]struct{},
) []*moira.NotificationEvent {
filteredNotificationEvents := make([]*moira.NotificationEvent, 0)

for _, event := range notificationEvents {
if metricRegexp.MatchString(event.Metric) {
_, ok := states[string(event.State)]
if len(states) == 0 || ok {
filteredNotificationEvents = append(filteredNotificationEvents, event)
}
}
}

return filteredNotificationEvents
}

// DeleteAllEvents deletes all notification events.
func DeleteAllEvents(database moira.Database) *api.ErrorResponse {
if err := database.RemoveAllNotificationEvents(); err != nil {
Expand Down
122 changes: 116 additions & 6 deletions api/controller/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"fmt"
"regexp"
"testing"

"github.com/gofrs/uuid"
Expand All @@ -13,19 +14,38 @@ import (
"go.uber.org/mock/gomock"
)

var (
allMetrics = regexp.MustCompile(``)
allStates map[string]struct{}
)

func TestGetEvents(t *testing.T) {
mockCtrl := gomock.NewController(t)
dataBase := mock_moira_alert.NewMockDatabase(mockCtrl)
defer mockCtrl.Finish()
triggerID := uuid.Must(uuid.NewV4()).String()

var page int64 = 10
var size int64 = 100
from := "-inf"
to := "+inf"

Convey("Test has events", t, func() {
var total int64 = 6000000
dataBase.EXPECT().GetNotificationEvents(triggerID, page*size, size-1).Return([]*moira.NotificationEvent{{State: moira.StateNODATA, OldState: moira.StateOK}, {State: moira.StateOK, OldState: moira.StateNODATA}}, nil)
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).
Return([]*moira.NotificationEvent{
{
State: moira.StateNODATA,
OldState: moira.StateOK,
},
{
State: moira.StateOK,
OldState: moira.StateNODATA,
},
}, nil)
dataBase.EXPECT().GetNotificationEventCount(triggerID, int64(-1)).Return(total)
list, err := GetTriggerEvents(dataBase, triggerID, page, size)

list, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, allMetrics, allStates)
So(err, ShouldBeNil)
So(list, ShouldResemble, &dto.EventsList{
List: []moira.NotificationEvent{{State: moira.StateNODATA, OldState: moira.StateOK}, {State: moira.StateOK, OldState: moira.StateNODATA}},
Expand All @@ -37,9 +57,9 @@ func TestGetEvents(t *testing.T) {

Convey("Test no events", t, func() {
var total int64
dataBase.EXPECT().GetNotificationEvents(triggerID, page*size, size-1).Return(make([]*moira.NotificationEvent, 0), nil)
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).Return(make([]*moira.NotificationEvent, 0), nil)
dataBase.EXPECT().GetNotificationEventCount(triggerID, int64(-1)).Return(total)
list, err := GetTriggerEvents(dataBase, triggerID, page, size)
list, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, allMetrics, allStates)
So(err, ShouldBeNil)
So(list, ShouldResemble, &dto.EventsList{
List: make([]moira.NotificationEvent, 0),
Expand All @@ -51,11 +71,101 @@ func TestGetEvents(t *testing.T) {

Convey("Test error", t, func() {
expected := fmt.Errorf("oooops! Can not get all contacts")
dataBase.EXPECT().GetNotificationEvents(triggerID, page*size, size-1).Return(nil, expected)
list, err := GetTriggerEvents(dataBase, triggerID, page, size)
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).Return(nil, expected)
list, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, allMetrics, allStates)
So(err, ShouldResemble, api.ErrorInternalServer(expected))
So(list, ShouldBeNil)
})

Convey("Test filtering", t, func() {
Convey("by metric regex", func() {
page = 0
size = 2
Convey("with same pattern", func() {
filtered := []*moira.NotificationEvent{
{Metric: "metric.test.event1"},
{Metric: "a.metric.test.event2"},
}
notFiltered := []*moira.NotificationEvent{
{Metric: "another.mEtric.test.event"},
{Metric: "metric.test"},
}
firstPortion := append(make([]*moira.NotificationEvent, 0), notFiltered[0], filtered[0])
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).Return(firstPortion, nil)

secondPortion := append(make([]*moira.NotificationEvent, 0), filtered[1], notFiltered[1])
dataBase.EXPECT().GetNotificationEvents(triggerID, page+1, size, from, to).Return(secondPortion, nil)

total := int64(len(firstPortion) + len(secondPortion))
dataBase.EXPECT().GetNotificationEventCount(triggerID, int64(-1)).Return(total)

actual, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, regexp.MustCompile(`metric\.test\.event`), allStates)
So(err, ShouldBeNil)
So(actual, ShouldResemble, &dto.EventsList{
Page: page,
Size: size,
Total: total,
List: toDTOList(filtered),
})
})
})
page = 0
size = -1

Convey("by state", func() {
filtered := []*moira.NotificationEvent{
{State: moira.StateOK},
{State: moira.StateTEST},
{State: moira.StateEXCEPTION},
}
notFiltered := []*moira.NotificationEvent{
{State: moira.StateWARN},
{State: moira.StateNODATA},
{State: moira.StateERROR},
}
Convey("with empty map all allowed", func() {
total := int64(len(filtered) + len(notFiltered))
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).Return(append(filtered, notFiltered...), nil)
dataBase.EXPECT().GetNotificationEventCount(triggerID, int64(-1)).Return(total)

actual, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, allMetrics, allStates)
So(err, ShouldBeNil)
So(actual, ShouldResemble, &dto.EventsList{
Page: page,
Size: size,
Total: total,
List: toDTOList(append(filtered, notFiltered...)),
})
})

Convey("with given states", func() {
total := int64(len(filtered) + len(notFiltered))
dataBase.EXPECT().GetNotificationEvents(triggerID, page, size, from, to).Return(append(filtered, notFiltered...), nil)
dataBase.EXPECT().GetNotificationEventCount(triggerID, int64(-1)).Return(total)

actual, err := GetTriggerEvents(dataBase, triggerID, page, size, from, to, allMetrics, map[string]struct{}{
string(moira.StateOK): {},
string(moira.StateEXCEPTION): {},
string(moira.StateTEST): {},
})
So(err, ShouldBeNil)
So(actual, ShouldResemble, &dto.EventsList{
Page: page,
Size: size,
Total: total,
List: toDTOList(filtered),
})
})
})
})
}

func toDTOList(eventPtrs []*moira.NotificationEvent) []moira.NotificationEvent {
events := make([]moira.NotificationEvent, 0, len(eventPtrs))
for _, ptr := range eventPtrs {
events = append(events, *ptr)
}
return events
}

func TestDeleteAllNotificationEvents(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions api/handler/constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package handler

const allMetricsPattern = ".*"

const (
eventDefaultPage = 0
eventDefaultSize = 100
eventDefaultFrom = "-inf"
eventDefaultTo = "+inf"
eventDefaultMetric = allMetricsPattern
)

const (
contactEventsDefaultFrom = "-3hour"
contactEventsDefaultTo = "now"
Expand Down
10 changes: 5 additions & 5 deletions api/handler/contact_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func contactEvents(router chi.Router) {
// @id get-contact-events-by-id
// @tags contact
// @produce json
// @param contactID path string true "Contact ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c)
// @param from query string false "Start time of the time range" default(-3hour)
// @param to query string false "End time of the time range" default(now)
// @param size query int false "Number of items to return or all items if size == -1 (if size == -1 p should be zero for correct work)" default(100)
// @param p query int false "Defines the index of data portion (combined with size). E.g, p=2, size=100 will return records from 200 (including), to 300 (not including)" default(0)
// @param contactID path string true "Contact ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c)
// @param from query string false "Start time of the time range" default(-3hour)
// @param to query string false "End time of the time range" default(now)
// @param size query int false "Number of items to return or all items if size == -1 (if size == -1 p should be zero for correct work)" default(100)
// @param p query int false "Defines the index of data portion (combined with size). E.g, p=2, size=100 will return records from 200 (including), to 300 (not including)" default(0)
// @success 200 {object} dto.ContactEventItemList "Successfully received contact events"
// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client"
// @failure 403 {object} api.ErrorForbiddenExample "Forbidden"
Expand Down
Loading

0 comments on commit 0197720

Please sign in to comment.