Skip to content

Commit

Permalink
feat(api): trigger noisiness (#1131)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko authored Jan 27, 2025
1 parent 3e5ad92 commit ab16e55
Show file tree
Hide file tree
Showing 16 changed files with 799 additions and 51 deletions.
26 changes: 26 additions & 0 deletions api/controller/paginate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controller

// applyPagination returns entities[page*size:(page+1)*size] if possible.
// If bad page and size are given or out of range than empty slice []T is returned.
func applyPagination[T any](page, size, total int64, entities []T) []T {
if page < 0 || (page > 0 && size < 0) {
return make([]T, 0)
}

if page >= 0 && size >= 0 {
start := page * size
end := start + size

if start >= total {
return make([]T, 0)
} else {
if end > total {
end = total
}

return entities[start:end]
}
}

return entities
}
122 changes: 122 additions & 0 deletions api/controller/paginate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package controller

import (
"fmt"
"testing"

. "github.com/smartystreets/goconvey/convey"
)

func Test_applyPaginate(t *testing.T) {
Convey("Test paginating", t, func() {
entities := make([]int, 0, 'z'-'a'+1)
for i := 0; i < 40; i++ {
entities = append(entities, i)
}

type testcase struct {
page int64
size int64
total int64
givenEntities []int
expectedEntities []int
desc string
}

cases := []testcase{
{
page: -1,
givenEntities: entities,
expectedEntities: []int{},
desc: "with negative page",
},
{
page: 1,
size: -1,
givenEntities: entities,
expectedEntities: []int{},
desc: "with positive page and negative size",
},
{
page: 1,
size: 10,
total: 7,
givenEntities: entities,
expectedEntities: []int{},
desc: "out of range",
},
{
page: 0,
size: -1,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: entities,
desc: "fetch all entities",
},
{
page: 0,
size: -2,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: entities,
desc: "again fetch all entities",
},
{
page: 0,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{0, 1, 2, 3, 4, 5, 6},
desc: "first page with size 7 (page = 0)",
},
{
page: 1,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{7, 8, 9, 10, 11, 12, 13},
desc: "second page with size 7 (page = 1)",
},
{
page: 2,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{14, 15, 16, 17, 18, 19, 20},
desc: "third page with size 7 (page = 2)",
},
{
page: 3,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{21, 22, 23, 24, 25, 26, 27},
desc: "forth page with size 7 (page = 3)",
},
{
page: 4,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{28, 29, 30, 31, 32, 33, 34},
desc: "fifth page with size 7 (page = 4)",
},
{
page: 5,
size: 7,
total: int64(len(entities)),
givenEntities: entities,
expectedEntities: []int{35, 36, 37, 38, 39},
desc: "last page with size 7 (page = 5)",
},
}

for i, tcase := range cases {
Convey(fmt.Sprintf("Case %v: %s", i+1, tcase.desc), func() {
gotEntities := applyPagination[int](tcase.page, tcase.size, tcase.total, tcase.givenEntities)

So(gotEntities, ShouldResemble, tcase.expectedEntities)
})
}
})
}
107 changes: 107 additions & 0 deletions api/controller/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"math"
"regexp"
"slices"
"strings"

"github.com/gofrs/uuid"

Expand Down Expand Up @@ -213,3 +215,108 @@ func triggerExists(database moira.Database, triggerID string) (bool, error) {
}
return true, nil
}

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

triggerIDsWithEventsCount := getTriggerIDsWithEventsCount(database, triggerIDs, from, to)

sortTriggerIDsByEventsCount(triggerIDsWithEventsCount, sortOrder)

total := int64(len(triggerIDsWithEventsCount))

resDto := dto.TriggerNoisinessList{
List: []dto.TriggerNoisiness{},
Page: page,
Size: size,
Total: total,
}

triggerIDsWithEventsCount = applyPagination[triggerIDWithEventsCount](page, size, total, triggerIDsWithEventsCount)
if len(triggerIDsWithEventsCount) == 0 {
return &resDto, nil
}

triggers, err := getTriggerChecks(database, onlyTriggerIDs(triggerIDsWithEventsCount))
if err != nil {
return nil, api.ErrorInternalServer(err)
}

if len(triggers) != len(triggerIDsWithEventsCount) {
return nil, api.ErrorInternalServer(fmt.Errorf("failed to fetch triggers for such range"))
}

resDto.List = make([]dto.TriggerNoisiness, 0, len(triggers))
for i := range triggers {
resDto.List = append(resDto.List, dto.TriggerNoisiness{
Trigger: dto.Trigger{
TriggerModel: dto.CreateTriggerModel(&triggers[i].Trigger),
Throttling: triggers[i].Throttling,
},
EventsCount: triggerIDsWithEventsCount[i].eventsCount,
})
}

return &resDto, nil
}

type triggerIDWithEventsCount struct {
triggerID string
eventsCount int64
}

func getTriggerIDsWithEventsCount(
database moira.Database,
triggerIDs []string,
from, to string,
) []triggerIDWithEventsCount {
resultTriggerIDs := make([]triggerIDWithEventsCount, 0, len(triggerIDs))

for _, triggerID := range triggerIDs {
eventsCount := database.GetNotificationEventCount(triggerID, from, to)
resultTriggerIDs = append(resultTriggerIDs, triggerIDWithEventsCount{
triggerID: triggerID,
eventsCount: eventsCount,
})
}

return resultTriggerIDs
}

func sortTriggerIDsByEventsCount(idsWithCount []triggerIDWithEventsCount, sortOrder api.SortOrder) {
if sortOrder == api.AscSortOrder || sortOrder == api.DescSortOrder {
slices.SortFunc(idsWithCount, func(first, second triggerIDWithEventsCount) int {
cmpRes := first.eventsCount - second.eventsCount

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

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

return int(cmpRes)
})
}
}

func onlyTriggerIDs(idsWithCount []triggerIDWithEventsCount) []string {
triggerIDs := make([]string, 0, len(idsWithCount))

for _, idWithCount := range idsWithCount {
triggerIDs = append(triggerIDs, idWithCount.triggerID)
}

return triggerIDs
}
Loading

0 comments on commit ab16e55

Please sign in to comment.