Skip to content

Commit

Permalink
feat: contact noisiness (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko authored Feb 18, 2025
1 parent ea541d7 commit 33b181f
Show file tree
Hide file tree
Showing 16 changed files with 685 additions and 22 deletions.
80 changes: 80 additions & 0 deletions api/controller/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strings"
"time"

"github.com/go-graphite/carbonapi/date"
Expand Down Expand Up @@ -307,3 +309,81 @@ func validateContact(contactsTemplate []api.WebContact, contact moira.ContactDat

return nil
}

// GetContactNoisiness get contacts with amount of notification events (within time range [from, to])
// and sorts by events_count according to sortOrder.
func GetContactNoisiness(
database moira.Database,
page, size int64,
from, to string,
sortOrder api.SortOrder,
) (*dto.ContactNoisinessList, *api.ErrorResponse) {
contacts, err := database.GetAllContacts()
if err != nil {
return nil, api.ErrorInternalServer(err)
}

idsWithEventsCount, err := database.CountEventsInNotificationHistory(getOnlyIDs(contacts), from, to)
if err != nil {
return nil, api.ErrorInternalServer(err)
}

noisinessSlice := makeContactNoisinessSlice(contacts, idsWithEventsCount)

sortContactNoisinessByEventsCount(noisinessSlice, sortOrder)
total := int64(len(noisinessSlice))

return &dto.ContactNoisinessList{
Page: page,
Size: size,
Total: total,
List: applyPagination[*dto.ContactNoisiness](page, size, total, noisinessSlice),
}, nil
}

func getOnlyIDs(contactsData []*moira.ContactData) []string {
ids := make([]string, 0, len(contactsData))

for _, data := range contactsData {
ids = append(ids, data.ID)
}

return ids
}

func makeContactNoisinessSlice(contacts []*moira.ContactData, idsWithEventsCount []*moira.ContactIDWithNotificationCount) []*dto.ContactNoisiness {
noisiness := make([]*dto.ContactNoisiness, 0, len(contacts))

for i, contact := range contacts {
noisiness = append(noisiness,
&dto.ContactNoisiness{
Contact: dto.NewContact(*contact),
EventsCount: idsWithEventsCount[i].Count,
})
}

return noisiness
}

func sortContactNoisinessByEventsCount(noisiness []*dto.ContactNoisiness, sortOrder api.SortOrder) {
if sortOrder == api.AscSortOrder || sortOrder == api.DescSortOrder {
slices.SortFunc(noisiness, func(first, second *dto.ContactNoisiness) int {
cmpRes := 0
if first.EventsCount > second.EventsCount {
cmpRes = 1
} else if second.EventsCount > first.EventsCount {
cmpRes = -1
}

if cmpRes == 0 {
return strings.Compare(first.ID, second.ID)
}

if sortOrder == api.DescSortOrder {
cmpRes *= -1
}

return cmpRes
})
}
}
124 changes: 124 additions & 0 deletions api/controller/contact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1094,3 +1094,127 @@ func TestValidateContact(t *testing.T) {
})
})
}

func TestGetContactNoisiness(t *testing.T) {
Convey("Test get contact noisiness", t, func() {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
dataBase := mock_moira_alert.NewMockDatabase(mockCtrl)

const (
allTimeFrom = "-inf"
allTimeTo = "+inf"
zeroPage = int64(0)
allEventsSize = int64(-1)
defaultSortOrder = api.DescSortOrder
)

contacts := []*moira.ContactData{
{
ID: "contactID1",
},
{
ID: "contactID2",
},
{
ID: "contactID3",
},
}

Convey("On error, when getting all contacts, return 500", func() {
someErr := errors.New("error from db")

dataBase.EXPECT().GetAllContacts().Return(nil, someErr).Times(1)

gotDTO, gotErrRsp := GetContactNoisiness(dataBase, zeroPage, allEventsSize, allTimeFrom, allTimeTo, defaultSortOrder)
So(gotDTO, ShouldBeNil)
So(gotErrRsp, ShouldResemble, api.ErrorInternalServer(someErr))
})

Convey("On error, when getting events count for contacts, return 500", func() {
someErr := errors.New("error from db")

dataBase.EXPECT().GetAllContacts().Return(contacts, nil).Times(1)
dataBase.EXPECT().
CountEventsInNotificationHistory([]string{"contactID1", "contactID2", "contactID3"}, allTimeFrom, allTimeTo).
Return(nil, someErr).
Times(1)

gotDTO, gotErrRsp := GetContactNoisiness(dataBase, zeroPage, allEventsSize, allTimeFrom, allTimeTo, defaultSortOrder)
So(gotDTO, ShouldBeNil)
So(gotErrRsp, ShouldResemble, api.ErrorInternalServer(someErr))
})

Convey("No errors from db, noisiness got and sorted", func() {
Convey("with desc sort order", func() {
dataBase.EXPECT().GetAllContacts().Return(contacts, nil).Times(1)
dataBase.EXPECT().CountEventsInNotificationHistory(
[]string{"contactID1", "contactID2", "contactID3"},
allTimeFrom,
allTimeTo).
Return([]*moira.ContactIDWithNotificationCount{
{ID: "contactID1", Count: 2},
{ID: "contactID2", Count: 3},
{ID: "contactID3", Count: 1},
}, nil).Times(1)

gotDTO, gotErrRsp := GetContactNoisiness(dataBase, zeroPage, allEventsSize, allTimeFrom, allTimeTo, api.DescSortOrder)
So(gotDTO, ShouldResemble, &dto.ContactNoisinessList{
Page: zeroPage,
Size: allEventsSize,
Total: 3,
List: []*dto.ContactNoisiness{
{
Contact: dto.NewContact(*contacts[1]),
EventsCount: 3,
},
{
Contact: dto.NewContact(*contacts[0]),
EventsCount: 2,
},
{
Contact: dto.NewContact(*contacts[2]),
EventsCount: 1,
},
},
})
So(gotErrRsp, ShouldBeNil)
})

Convey("with asc sort order", func() {
dataBase.EXPECT().GetAllContacts().Return(contacts, nil).Times(1)
dataBase.EXPECT().CountEventsInNotificationHistory(
[]string{"contactID1", "contactID2", "contactID3"},
allTimeFrom,
allTimeTo).
Return([]*moira.ContactIDWithNotificationCount{
{ID: "contactID1", Count: 2},
{ID: "contactID2", Count: 3},
{ID: "contactID3", Count: 1},
}, nil).Times(1)

gotDTO, gotErrRsp := GetContactNoisiness(dataBase, zeroPage, allEventsSize, allTimeFrom, allTimeTo, api.AscSortOrder)
So(gotDTO, ShouldResemble, &dto.ContactNoisinessList{
Page: zeroPage,
Size: allEventsSize,
Total: 3,
List: []*dto.ContactNoisiness{
{
Contact: dto.NewContact(*contacts[2]),
EventsCount: 1,
},
{
Contact: dto.NewContact(*contacts[0]),
EventsCount: 2,
},
{
Contact: dto.NewContact(*contacts[1]),
EventsCount: 3,
},
},
})
So(gotErrRsp, ShouldBeNil)
})
})
})
}
6 changes: 3 additions & 3 deletions api/controller/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func GetTriggerNoisiness(
total := int64(len(triggerIDsWithEventsCount))

resDto := dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: page,
Size: size,
Total: total,
Expand All @@ -256,9 +256,9 @@ func GetTriggerNoisiness(
return nil, api.ErrorInternalServer(fmt.Errorf("failed to fetch triggers for such range"))
}

resDto.List = make([]dto.TriggerNoisiness, 0, len(triggers))
resDto.List = make([]*dto.TriggerNoisiness, 0, len(triggers))
for i := range triggers {
resDto.List = append(resDto.List, dto.TriggerNoisiness{
resDto.List = append(resDto.List, &dto.TriggerNoisiness{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggers[i].Trigger),
Throttling: triggers[i].Throttling,
Expand Down
20 changes: 10 additions & 10 deletions api/controller/triggers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, zeroPage, allEventsSize, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: zeroPage,
Size: allEventsSize,
Total: 0,
Expand Down Expand Up @@ -1257,7 +1257,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, zeroPage, allEventsSize, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{
List: []*dto.TriggerNoisiness{
{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggerCheck1.Trigger),
Expand Down Expand Up @@ -1288,7 +1288,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, zeroPage, allEventsSize, defaultFrom, defaultTo, api.AscSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{
List: []*dto.TriggerNoisiness{
{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggerCheck1.Trigger),
Expand Down Expand Up @@ -1318,7 +1318,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, zeroPage, allEventsSize, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{
List: []*dto.TriggerNoisiness{
{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggerCheck2.Trigger),
Expand Down Expand Up @@ -1348,7 +1348,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, zeroPage, 0, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: zeroPage,
Size: 0,
Total: 2,
Expand All @@ -1363,7 +1363,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err = GetTriggerNoisiness(dataBase, zeroPage, 1, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{
List: []*dto.TriggerNoisiness{
{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggerCheck2.Trigger),
Expand All @@ -1385,7 +1385,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err = GetTriggerNoisiness(dataBase, 1, 1, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{
List: []*dto.TriggerNoisiness{
{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggerCheck1.Trigger),
Expand All @@ -1407,7 +1407,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, 1, -1, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: 1,
Size: -1,
Total: 2,
Expand All @@ -1422,7 +1422,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, -1, -1, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: -1,
Size: -1,
Total: 2,
Expand All @@ -1437,7 +1437,7 @@ func TestGetTriggerNoisiness(t *testing.T) {
triggerNoisinessList, err := GetTriggerNoisiness(dataBase, -1, 1, defaultFrom, defaultTo, api.DescSortOrder)
So(err, ShouldBeNil)
So(triggerNoisinessList, ShouldResemble, &dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
List: []*dto.TriggerNoisiness{},
Page: -1,
Size: 1,
Total: 2,
Expand Down
32 changes: 32 additions & 0 deletions api/dto/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package dto
import (
"fmt"
"net/http"

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

type ContactList struct {
Expand All @@ -23,6 +25,18 @@ type Contact struct {
TeamID string `json:"team_id,omitempty"`
}

// NewContact init Contact with data from moira.ContactData.
func NewContact(data moira.ContactData) Contact {
return Contact{
Type: data.Type,
Name: data.Name,
Value: data.Value,
ID: data.ID,
User: data.User,
TeamID: data.Team,
}
}

func (*Contact) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
Expand All @@ -39,3 +53,21 @@ func (contact *Contact) Bind(r *http.Request) error {
}
return nil
}

// ContactNoisiness represents Contact with amount of events for this contact.
type ContactNoisiness struct {
Contact
// EventsCount for the contact.
EventsCount uint64 `json:"events_count"`
}

func (*ContactNoisiness) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}

// ContactNoisinessList represents list of ContactNoisiness.
type ContactNoisinessList ListDTO[*ContactNoisiness]

func (*ContactNoisinessList) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
Loading

0 comments on commit 33b181f

Please sign in to comment.