Skip to content

Commit

Permalink
Merge pull request #2644 from target/msg-logs-paginate
Browse files Browse the repository at this point in the history
admin: add pagination to message logs query
  • Loading branch information
Forfold authored Nov 2, 2022
2 parents 59198a5 + 1deadac commit f5f5313
Show file tree
Hide file tree
Showing 20 changed files with 1,058 additions and 532 deletions.
385 changes: 385 additions & 0 deletions graphql2/generated.go

Large diffs are not rendered by default.

235 changes: 235 additions & 0 deletions graphql2/graphqlapp/messagelog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package graphqlapp

import (
"context"
"fmt"
"strings"

"github.com/google/uuid"
"github.com/target/goalert/graphql2"
"github.com/target/goalert/notification"
"github.com/target/goalert/notificationchannel"
"github.com/target/goalert/search"
"github.com/target/goalert/validation/validate"
)

type MessageLog App

func (a *App) formatNC(ctx context.Context, id string) (string, error) {
if id == "" {
return "", nil
}
uid, err := uuid.Parse(id)
if err != nil {
return "", err
}

n, err := a.FindOneNC(ctx, uid)
if err != nil {
return "", err
}
var typeName string
switch n.Type {
case notificationchannel.TypeSlack:
typeName = "Slack"
default:
typeName = string(n.Type)
}

return fmt.Sprintf("%s (%s)", n.Name, typeName), nil
}

func (q *Query) formatDest(ctx context.Context, dst notification.Dest) (string, error) {
if !dst.Type.IsUserCM() {
return (*App)(q).formatNC(ctx, dst.ID)
}

var str strings.Builder
str.WriteString((*App)(q).FormatDestFunc(ctx, dst.Type, dst.Value))
switch dst.Type {
case notification.DestTypeSMS:
str.WriteString(" (SMS)")
case notification.DestTypeUserEmail:
str.WriteString(" (Email)")
case notification.DestTypeVoice:
str.WriteString(" (Voice)")
case notification.DestTypeUserWebhook:
str.Reset()
str.WriteString("Webhook")
default:
str.Reset()
str.WriteString(dst.Type.String())
}

return str.String(), nil
}

func msgStatus(stat notification.Status) string {
var str strings.Builder
switch stat.State {
case notification.StateBundled:
str.WriteString("Bundled")
case notification.StateUnknown:
str.WriteString("Unknown")
case notification.StateSending:
str.WriteString("Sending")
case notification.StatePending:
str.WriteString("Pending")
case notification.StateSent:
str.WriteString("Sent")
case notification.StateDelivered:
str.WriteString("Delivered")
case notification.StateFailedTemp:
str.WriteString("Failed (temporary)")
case notification.StateFailedPerm:
str.WriteString("Failed (permanent)")
}
if stat.Details != "" {
str.WriteString(": ")
str.WriteString(stat.Details)
}
return str.String()
}

func (q *Query) MessageLogs(ctx context.Context, opts *graphql2.MessageLogSearchOptions) (conn *graphql2.MessageLogConnection, err error) {
if opts == nil {
opts = &graphql2.MessageLogSearchOptions{}
}
var searchOpts notification.SearchOptions
if opts.Search != nil {
searchOpts.Search = *opts.Search
}
searchOpts.Omit = opts.Omit
if opts.After != nil && *opts.After != "" {
err = search.ParseCursor(*opts.After, &searchOpts)
if err != nil {
return nil, err
}
}
if opts.First != nil {
err := validate.Range("First", *opts.First, 0, 100)
if err != nil {
return nil, err
}
searchOpts.Limit = *opts.First
}
if opts.CreatedAfter != nil {
searchOpts.CreatedAfter = *opts.CreatedAfter
}
if opts.CreatedBefore != nil {
searchOpts.CreatedBefore = *opts.CreatedBefore
}
if searchOpts.Limit == 0 {
searchOpts.Limit = 50
}

searchOpts.Limit++
logs, err := q.NotificationStore.Search(ctx, &searchOpts)
hasNextPage := len(logs) == searchOpts.Limit
searchOpts.Limit-- // prevent confusion later
if err != nil {
return nil, err
}

conn = new(graphql2.MessageLogConnection)
conn.PageInfo = &graphql2.PageInfo{
HasNextPage: hasNextPage,
}

if hasNextPage {
last := logs[len(logs)-1]
searchOpts.After.CreatedAt = last.CreatedAt
searchOpts.After.ID = last.ID

cur, err := search.Cursor(searchOpts)
if err != nil {
return conn, err
}
conn.PageInfo.EndCursor = &cur
}

if len(logs) > searchOpts.Limit {
// If we have next page, we've fetched MORE than one page, but we only want to return one page.
logs = logs[:searchOpts.Limit]
}

for _, _log := range logs {
log := _log
var dest notification.Dest
switch {
case log.ContactMethodID != "":
cm, err := (*App)(q).FindOneCM(ctx, log.ContactMethodID)
if err != nil {
return nil, fmt.Errorf("lookup contact method %s: %w", log.ContactMethodID, err)
}
dest = notification.DestFromPair(cm, nil)

case log.ChannelID != uuid.Nil:
nc, err := (*App)(q).FindOneNC(ctx, log.ChannelID)
if err != nil {
return nil, fmt.Errorf("lookup notification channel %s: %w", log.ChannelID, err)
}
dest = notification.DestFromPair(nil, nc)
}

dm := graphql2.DebugMessage{
ID: log.ID,
CreatedAt: log.CreatedAt,
UpdatedAt: log.LastStatusAt,
Type: strings.TrimPrefix(log.MessageType.String(), "MessageType"),
Status: msgStatus(notification.Status{State: log.LastStatus, Details: log.StatusDetails}),
AlertID: &log.AlertID,
}
if dest.ID != "" {
dm.Destination, err = q.formatDest(ctx, dest)
if err != nil {
return nil, fmt.Errorf("format dest: %w", err)
}
}
if log.UserID != "" {
dm.UserID = &log.UserID
}
if log.UserName != "" {
dm.UserName = &log.UserName
}
if log.SrcValue != "" {
src, err := q.formatDest(ctx, notification.Dest{Type: dest.Type, Value: log.SrcValue})
if err != nil {
return nil, fmt.Errorf("format src: %w", err)
}
dm.Source = &src
}
if log.ServiceID != "" {
dm.ServiceID = &log.ServiceID
}
if log.ServiceName != "" {
dm.ServiceName = &log.ServiceName
}
if log.AlertID != 0 {
dm.AlertID = &log.AlertID
}
if log.ProviderMsgID != nil {
dm.ProviderID = &log.ProviderMsgID.ExternalID
}

conn.Nodes = append(conn.Nodes, dm)
}

return conn, nil
}

func (q *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessagesInput) ([]graphql2.DebugMessage, error) {
if input.First != nil && *input.First > 100 {
*input.First = 100
}
conn, err := q.MessageLogs(ctx, &graphql2.MessageLogSearchOptions{
CreatedBefore: input.CreatedBefore,
CreatedAfter: input.CreatedAfter,
First: input.First,
})
if err != nil {
return nil, err
}

return conn.Nodes, nil
}
Loading

0 comments on commit f5f5313

Please sign in to comment.