From 9caefd26d7f384624202eb9149488a0cb7a7e0ea Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 7 Dec 2021 11:51:57 -0600 Subject: [PATCH 1/9] add `debugMessages` query to return recent messages --- graphql2/generated.go | 928 +++++++++++++++++++++++++++++++++- graphql2/gqlgen.yml | 2 + graphql2/graphqlapp/query.go | 64 ++- graphql2/models_gen.go | 6 + graphql2/schema.graphql | 25 + notification/recentmessage.go | 152 ++++++ notification/status.go | 37 ++ notification/store.go | 2 + web/src/schema.d.ts | 23 + 9 files changed, 1226 insertions(+), 13 deletions(-) create mode 100644 notification/recentmessage.go diff --git a/graphql2/generated.go b/graphql2/generated.go index 9d05c2fc38..fa36683d71 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -24,6 +24,7 @@ import ( "github.com/target/goalert/label" "github.com/target/goalert/limit" "github.com/target/goalert/notice" + "github.com/target/goalert/notification" "github.com/target/goalert/notification/slack" "github.com/target/goalert/notification/twilio" "github.com/target/goalert/oncall" @@ -60,6 +61,7 @@ type Config struct { type ResolverRoot interface { Alert() AlertResolver AlertLogEntry() AlertLogEntryResolver + DebugMessage() DebugMessageResolver EscalationPolicy() EscalationPolicyResolver EscalationPolicyStep() EscalationPolicyStepResolver HeartbeatMonitor() HeartbeatMonitorResolver @@ -158,6 +160,22 @@ type ComplexityRoot struct { Type func(childComplexity int) int } + DebugMessage struct { + AlertID func(childComplexity int) int + CreatedAt func(childComplexity int) int + Destination func(childComplexity int) int + ID func(childComplexity int) int + ProviderID func(childComplexity int) int + ServiceID func(childComplexity int) int + ServiceName func(childComplexity int) int + Source func(childComplexity int) int + Status func(childComplexity int) int + Type func(childComplexity int) int + UpdatedAt func(childComplexity int) int + UserID func(childComplexity int) int + UserName func(childComplexity int) int + } + DebugMessageStatusInfo struct { State func(childComplexity int) int } @@ -315,6 +333,7 @@ type ComplexityRoot struct { Config func(childComplexity int, all *bool) int ConfigHints func(childComplexity int) int DebugMessageStatus func(childComplexity int, input DebugMessageStatusInput) int + DebugMessages func(childComplexity int, input *DebugMessagesInput) int EscalationPolicies func(childComplexity int, input *EscalationPolicySearchOptions) int EscalationPolicy func(childComplexity int, id string) int GenerateSlackAppManifest func(childComplexity int) int @@ -552,6 +571,15 @@ type AlertLogEntryResolver interface { Message(ctx context.Context, obj *alertlog.Entry) (string, error) State(ctx context.Context, obj *alertlog.Entry) (*NotificationState, error) } +type DebugMessageResolver interface { + Type(ctx context.Context, obj *notification.RecentMessage) (string, error) + Status(ctx context.Context, obj *notification.RecentMessage) (string, error) + + Source(ctx context.Context, obj *notification.RecentMessage) (string, error) + Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) + + ProviderID(ctx context.Context, obj *notification.RecentMessage) (string, error) +} type EscalationPolicyResolver interface { IsFavorite(ctx context.Context, obj *escalation.Policy) (bool, error) AssignedTo(ctx context.Context, obj *escalation.Policy) ([]assignment.RawTarget, error) @@ -625,6 +653,7 @@ type OnCallShiftResolver interface { } type QueryResolver interface { PhoneNumberInfo(ctx context.Context, number string) (*PhoneNumberInfo, error) + DebugMessages(ctx context.Context, input *DebugMessagesInput) ([]notification.RecentMessage, error) User(ctx context.Context, id *string) (*user.User, error) Users(ctx context.Context, input *UserSearchOptions, first *int, after *string, search *string) (*UserConnection, error) Alert(ctx context.Context, id int) (*alert.Alert, error) @@ -1025,6 +1054,97 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DebugCarrierInfo.Type(childComplexity), true + case "DebugMessage.alertID": + if e.complexity.DebugMessage.AlertID == nil { + break + } + + return e.complexity.DebugMessage.AlertID(childComplexity), true + + case "DebugMessage.createdAt": + if e.complexity.DebugMessage.CreatedAt == nil { + break + } + + return e.complexity.DebugMessage.CreatedAt(childComplexity), true + + case "DebugMessage.destination": + if e.complexity.DebugMessage.Destination == nil { + break + } + + return e.complexity.DebugMessage.Destination(childComplexity), true + + case "DebugMessage.id": + if e.complexity.DebugMessage.ID == nil { + break + } + + return e.complexity.DebugMessage.ID(childComplexity), true + + case "DebugMessage.providerID": + if e.complexity.DebugMessage.ProviderID == nil { + break + } + + return e.complexity.DebugMessage.ProviderID(childComplexity), true + + case "DebugMessage.serviceID": + if e.complexity.DebugMessage.ServiceID == nil { + break + } + + return e.complexity.DebugMessage.ServiceID(childComplexity), true + + case "DebugMessage.serviceName": + if e.complexity.DebugMessage.ServiceName == nil { + break + } + + return e.complexity.DebugMessage.ServiceName(childComplexity), true + + case "DebugMessage.source": + if e.complexity.DebugMessage.Source == nil { + break + } + + return e.complexity.DebugMessage.Source(childComplexity), true + + case "DebugMessage.status": + if e.complexity.DebugMessage.Status == nil { + break + } + + return e.complexity.DebugMessage.Status(childComplexity), true + + case "DebugMessage.type": + if e.complexity.DebugMessage.Type == nil { + break + } + + return e.complexity.DebugMessage.Type(childComplexity), true + + case "DebugMessage.updatedAt": + if e.complexity.DebugMessage.UpdatedAt == nil { + break + } + + return e.complexity.DebugMessage.UpdatedAt(childComplexity), true + + case "DebugMessage.userID": + if e.complexity.DebugMessage.UserID == nil { + break + } + + return e.complexity.DebugMessage.UserID(childComplexity), true + + case "DebugMessage.userName": + if e.complexity.DebugMessage.UserName == nil { + break + } + + return e.complexity.DebugMessage.UserName(childComplexity), true + case "DebugMessageStatusInfo.state": if e.complexity.DebugMessageStatusInfo.State == nil { break @@ -2021,6 +2141,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.DebugMessageStatus(childComplexity, args["input"].(DebugMessageStatusInput)), true + case "Query.debugMessages": + if e.complexity.Query.DebugMessages == nil { + break + } + + args, err := ec.field_Query_debugMessages_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.DebugMessages(childComplexity, args["input"].(*DebugMessagesInput)), true + case "Query.escalationPolicies": if e.complexity.Query.EscalationPolicies == nil { break @@ -3233,6 +3365,9 @@ var sources = []*ast.Source{ {Name: "./schema.graphql", Input: `type Query { phoneNumberInfo(number: String!): PhoneNumberInfo + # Returns the list of recent messages. + debugMessages(input: DebugMessagesInput): [DebugMessage!]! + # Returns the user with the given ID. If no ID is specified, # the current user is implied. user(id: ID): User @@ -3340,6 +3475,28 @@ var sources = []*ast.Source{ generateSlackAppManifest: String! } +input DebugMessagesInput { + first: Int = 15 + createdBefore: ISOTimestamp + createdAfter: ISOTimestamp +} + +type DebugMessage { + id: ID! + createdAt: ISOTimestamp! + updatedAt: ISOTimestamp! + type: String! + status: String! + userID: ID! + userName: String! + source: String! + destination: String! + serviceID: ID! + serviceName: String! + alertID: Int! + providerID: ID! +} + input SlackChannelSearchOptions { first: Int = 15 after: String = "" @@ -5192,6 +5349,21 @@ func (ec *executionContext) field_Query_debugMessageStatus_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Query_debugMessages_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *DebugMessagesInput + if tmp, ok := rawArgs["input"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + arg0, err = ec.unmarshalODebugMessagesInput2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessagesInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_escalationPolicies_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -6932,7 +7104,462 @@ func (ec *executionContext) _DebugCarrierInfo_type(ctx context.Context, field gr ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Type, nil + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugCarrierInfo_mobileNetworkCode(ctx context.Context, field graphql.CollectedField, obj *twilio.CarrierInfo) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugCarrierInfo", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.MobileNetworkCode, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugCarrierInfo_mobileCountryCode(ctx context.Context, field graphql.CollectedField, obj *twilio.CarrierInfo) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugCarrierInfo", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.MobileCountryCode, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_id(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_createdAt(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNISOTimestamp2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_updatedAt(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UpdatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNISOTimestamp2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_type(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DebugMessage().Type(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_status(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DebugMessage().Status(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_userID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UserID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_userName(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UserName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_source(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DebugMessage().Source(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_destination(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.DebugMessage().Destination(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_serviceID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ServiceID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _DebugMessage_serviceName(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DebugMessage", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ServiceName, nil }) if err != nil { ec.Error(ctx, err) @@ -6949,7 +7576,7 @@ func (ec *executionContext) _DebugCarrierInfo_type(ctx context.Context, field gr return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugCarrierInfo_mobileNetworkCode(ctx context.Context, field graphql.CollectedField, obj *twilio.CarrierInfo) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_alertID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6957,7 +7584,7 @@ func (ec *executionContext) _DebugCarrierInfo_mobileNetworkCode(ctx context.Cont } }() fc := &graphql.FieldContext{ - Object: "DebugCarrierInfo", + Object: "DebugMessage", Field: field, Args: nil, IsMethod: false, @@ -6967,7 +7594,7 @@ func (ec *executionContext) _DebugCarrierInfo_mobileNetworkCode(ctx context.Cont ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.MobileNetworkCode, nil + return obj.AlertID, nil }) if err != nil { ec.Error(ctx, err) @@ -6979,12 +7606,12 @@ func (ec *executionContext) _DebugCarrierInfo_mobileNetworkCode(ctx context.Cont } return graphql.Null } - res := resTmp.(string) + res := resTmp.(int) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNInt2int(ctx, field.Selections, res) } -func (ec *executionContext) _DebugCarrierInfo_mobileCountryCode(ctx context.Context, field graphql.CollectedField, obj *twilio.CarrierInfo) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_providerID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6992,17 +7619,17 @@ func (ec *executionContext) _DebugCarrierInfo_mobileCountryCode(ctx context.Cont } }() fc := &graphql.FieldContext{ - Object: "DebugCarrierInfo", + Object: "DebugMessage", Field: field, Args: nil, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.MobileCountryCode, nil + return ec.resolvers.DebugMessage().ProviderID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -7016,7 +7643,7 @@ func (ec *executionContext) _DebugCarrierInfo_mobileCountryCode(ctx context.Cont } res := resTmp.(string) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNID2string(ctx, field.Selections, res) } func (ec *executionContext) _DebugMessageStatusInfo_state(ctx context.Context, field graphql.CollectedField, obj *DebugMessageStatusInfo) (ret graphql.Marshaler) { @@ -10821,6 +11448,48 @@ func (ec *executionContext) _Query_phoneNumberInfo(ctx context.Context, field gr return ec.marshalOPhoneNumberInfo2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐPhoneNumberInfo(ctx, field.Selections, res) } +func (ec *executionContext) _Query_debugMessages(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_debugMessages_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DebugMessages(rctx, args["input"].(*DebugMessagesInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]notification.RecentMessage) + fc.Result = res + return ec.marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessageᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -18420,6 +19089,46 @@ func (ec *executionContext) unmarshalInputDebugMessageStatusInput(ctx context.Co return it, nil } +func (ec *executionContext) unmarshalInputDebugMessagesInput(ctx context.Context, obj interface{}) (DebugMessagesInput, error) { + var it DebugMessagesInput + var asMap = obj.(map[string]interface{}) + + if _, present := asMap["first"]; !present { + asMap["first"] = 15 + } + + for k, v := range asMap { + switch k { + case "first": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + it.First, err = ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + case "createdBefore": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("createdBefore")) + it.CreatedBefore, err = ec.unmarshalOISOTimestamp2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + case "createdAfter": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("createdAfter")) + it.CreatedAfter, err = ec.unmarshalOISOTimestamp2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDebugSendSMSInput(ctx context.Context, obj interface{}) (DebugSendSMSInput, error) { var it DebugSendSMSInput var asMap = obj.(map[string]interface{}) @@ -20613,6 +21322,138 @@ func (ec *executionContext) _DebugCarrierInfo(ctx context.Context, sel ast.Selec return out } +var debugMessageImplementors = []string{"DebugMessage"} + +func (ec *executionContext) _DebugMessage(ctx context.Context, sel ast.SelectionSet, obj *notification.RecentMessage) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, debugMessageImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DebugMessage") + case "id": + out.Values[i] = ec._DebugMessage_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "createdAt": + out.Values[i] = ec._DebugMessage_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "updatedAt": + out.Values[i] = ec._DebugMessage_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "type": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DebugMessage_type(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "status": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DebugMessage_status(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "userID": + out.Values[i] = ec._DebugMessage_userID(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "userName": + out.Values[i] = ec._DebugMessage_userName(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "source": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DebugMessage_source(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "destination": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DebugMessage_destination(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "serviceID": + out.Values[i] = ec._DebugMessage_serviceID(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "serviceName": + out.Values[i] = ec._DebugMessage_serviceName(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "alertID": + out.Values[i] = ec._DebugMessage_alertID(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "providerID": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DebugMessage_providerID(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var debugMessageStatusInfoImplementors = []string{"DebugMessageStatusInfo"} func (ec *executionContext) _DebugMessageStatusInfo(ctx context.Context, sel ast.SelectionSet, obj *DebugMessageStatusInfo) graphql.Marshaler { @@ -21542,6 +22383,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_phoneNumberInfo(ctx, field) return res }) + case "debugMessages": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_debugMessages(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "user": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -24148,6 +25003,47 @@ func (ec *executionContext) unmarshalNDebugCarrierInfoInput2githubᚗcomᚋtarge return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessage(ctx context.Context, sel ast.SelectionSet, v notification.RecentMessage) graphql.Marshaler { + return ec._DebugMessage(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessageᚄ(ctx context.Context, sel ast.SelectionSet, v []notification.RecentMessage) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessage(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalNDebugMessageStatusInfo2githubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessageStatusInfo(ctx context.Context, sel ast.SelectionSet, v DebugMessageStatusInfo) graphql.Marshaler { return ec._DebugMessageStatusInfo(ctx, sel, &v) } @@ -26471,6 +27367,14 @@ func (ec *executionContext) unmarshalOCreateUserOverrideInput2ᚕgithubᚗcomᚋ return res, nil } +func (ec *executionContext) unmarshalODebugMessagesInput2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessagesInput(ctx context.Context, v interface{}) (*DebugMessagesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputDebugMessagesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalODebugSendSMSInfo2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugSendSMSInfo(ctx context.Context, sel ast.SelectionSet, v *DebugSendSMSInfo) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/graphql2/gqlgen.yml b/graphql2/gqlgen.yml index 22f18618ee..2d6b06d65f 100644 --- a/graphql2/gqlgen.yml +++ b/graphql2/gqlgen.yml @@ -94,6 +94,8 @@ models: model: github.com/target/goalert/graphql2.OnCallNotificationRuleInput WeekdayFilter: model: github.com/target/goalert/util/timeutil.WeekdayFilter + DebugMessage: + model: github.com/target/goalert/notification.RecentMessage ID: model: - github.com/99designs/gqlgen/graphql.ID diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index 58aa9de6aa..ce741b8c0c 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -2,8 +2,10 @@ package graphqlapp import ( context "context" + "strings" "github.com/target/goalert/graphql2" + "github.com/target/goalert/notification" "github.com/target/goalert/search" "github.com/target/goalert/validation/validate" @@ -11,8 +13,68 @@ import ( ) type Query App +type DebugMessage App -func (a *App) Query() graphql2.QueryResolver { return (*Query)(a) } +func (a *App) Query() graphql2.QueryResolver { return (*Query)(a) } +func (a *App) DebugMessage() graphql2.DebugMessageResolver { return (*DebugMessage)(a) } + +func (a *DebugMessage) Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) { + return obj.Dest.String(), nil +} +func (a *DebugMessage) Type(ctx context.Context, obj *notification.RecentMessage) (string, error) { + return strings.TrimPrefix(obj.Type.String(), "MessageType"), nil +} +func (a *DebugMessage) Status(ctx context.Context, obj *notification.RecentMessage) (string, error) { + var str strings.Builder + switch obj.Status.State { + 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 obj.Status.Details != "" { + str.WriteString(": ") + str.WriteString(obj.Status.Details) + } + return str.String(), nil +} +func (a *DebugMessage) Source(ctx context.Context, obj *notification.RecentMessage) (string, error) { + if obj.Status.SrcValue == "" { + return "", nil + } + + return notification.Dest{Type: obj.Dest.Type, Value: obj.Status.SrcValue}.String(), nil +} +func (a *DebugMessage) ProviderID(ctx context.Context, obj *notification.RecentMessage) (string, error) { + return obj.ProviderID.ExternalID, nil +} + +func (a *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessagesInput) ([]notification.RecentMessage, error) { + var options notification.RecentMessageSearchOptions + if input == nil { + input = &graphql2.DebugMessagesInput{} + } + if input.CreatedAfter != nil { + options.After = *input.CreatedAfter + } + if input.CreatedBefore != nil { + options.Before = *input.CreatedBefore + } + if input.First != nil { + options.Limit = *input.First + } + return a.NotificationStore.RecentMessages(ctx, &options) +} func (a *Query) AuthSubjectsForProvider(ctx context.Context, _first *int, _after *string, providerID string) (conn *graphql2.AuthSubjectConnection, err error) { var first int diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index 8e4c1b9482..f94b7534ce 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -211,6 +211,12 @@ type DebugMessageStatusInput struct { ProviderMessageID string `json:"providerMessageID"` } +type DebugMessagesInput struct { + First *int `json:"first"` + CreatedBefore *time.Time `json:"createdBefore"` + CreatedAfter *time.Time `json:"createdAfter"` +} + type DebugSendSMSInfo struct { ID string `json:"id"` ProviderURL string `json:"providerURL"` diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index aac7ce1bc8..e5170d70e0 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -1,6 +1,9 @@ type Query { phoneNumberInfo(number: String!): PhoneNumberInfo + # Returns the list of recent messages. + debugMessages(input: DebugMessagesInput): [DebugMessage!]! + # Returns the user with the given ID. If no ID is specified, # the current user is implied. user(id: ID): User @@ -108,6 +111,28 @@ type Query { generateSlackAppManifest: String! } +input DebugMessagesInput { + first: Int = 15 + createdBefore: ISOTimestamp + createdAfter: ISOTimestamp +} + +type DebugMessage { + id: ID! + createdAt: ISOTimestamp! + updatedAt: ISOTimestamp! + type: String! + status: String! + userID: ID! + userName: String! + source: String! + destination: String! + serviceID: ID! + serviceName: String! + alertID: Int! + providerID: ID! +} + input SlackChannelSearchOptions { first: Int = 15 after: String = "" diff --git a/notification/recentmessage.go b/notification/recentmessage.go new file mode 100644 index 0000000000..935b9b23b7 --- /dev/null +++ b/notification/recentmessage.go @@ -0,0 +1,152 @@ +package notification + +import ( + "context" + "database/sql" + "fmt" + "text/template" + "time" + + "github.com/pkg/errors" + "github.com/target/goalert/permission" + "github.com/target/goalert/search" + "github.com/target/goalert/util/log" + "github.com/target/goalert/validation/validate" +) + +type RecentMessage struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + Type MessageType + Status Status + UserID string + UserName string + Dest Dest + ServiceID string + ServiceName string + AlertID int + ProviderID ProviderMessageID +} + +var searchTemplate = template.Must(template.New("search").Parse(` + SELECT + m.id, m.created_at, m.last_status_at, m.message_type, + m.last_status, m.status_details, m.provider_seq, m.src_value, + m.user_id, u.name, m.contact_method_id, m.channel_id, cm.type, c.type, cm.value, c.value, + m.service_id, s.name, m.alert_id, m.provider_msg_id + FROM outgoing_messages m + LEFT JOIN users u ON u.id = m.user_id + LEFT JOIN services s ON s.id = m.service_id + LEFT JOIN user_contact_methods cm ON cm.id = m.contact_method_id + LEFT JOIN notification_channels c ON c.id = m.channel_id + WHERE true + {{if not .Before.IsZero}} + AND m.created_at < :before + {{end}} + {{if not .After.IsZero}} + AND m.created_at >= :after + {{end}} + ORDER BY m.created_at DESC + LIMIT {{.Limit}} +`)) + +type RecentMessageSearchOptions struct { + Before time.Time + After time.Time + Limit int +} + +type renderData RecentMessageSearchOptions + +func (opts renderData) Normalize() (*renderData, error) { + if opts.Limit == 0 { + opts.Limit = search.DefaultMaxResults + } + + // set limit higher than normal since this is for admin use + return &opts, validate.Range("Limit", opts.Limit, 0, 1000) +} + +func (opts renderData) QueryArgs() []sql.NamedArg { + return []sql.NamedArg{ + sql.Named("before", opts.Before), + sql.Named("after", opts.After), + } +} + +func (db *DB) RecentMessages(ctx context.Context, opts *RecentMessageSearchOptions) ([]RecentMessage, error) { + err := permission.LimitCheckAny(ctx, permission.Admin) + if err != nil { + return nil, err + } + if opts == nil { + opts = &RecentMessageSearchOptions{} + } + data, err := (*renderData)(opts).Normalize() + if err != nil { + return nil, err + } + query, args, err := search.RenderQuery(ctx, searchTemplate, data) + if err != nil { + return nil, errors.Wrap(err, "render query") + } + + rows, err := db.db.QueryContext(ctx, query, args...) + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + if err != nil { + return nil, err + } + defer rows.Close() + + var msgs []RecentMessage + for rows.Next() { + var lastStatus sql.NullTime + var srcValue, userID, userName, serviceID, serviceName, cmID, chID, providerID, cmVal, chVal sql.NullString + var alertID sql.NullInt64 + var dstType ScannableDestType + var msg RecentMessage + err := rows.Scan( + &msg.ID, &msg.CreatedAt, &lastStatus, &msg.Type, + &msg.Status.State, &msg.Status.Details, &msg.Status.Sequence, &srcValue, + &userID, &userName, + &cmID, &chID, &dstType.CM, &dstType.NC, &cmVal, &chVal, + &serviceID, &serviceName, &alertID, &providerID, + ) + if err != nil { + return nil, err + } + if lastStatus.Valid { + msg.UpdatedAt = lastStatus.Time + } else { + msg.UpdatedAt = msg.CreatedAt + } + msg.Status.SrcValue = srcValue.String + msg.UserID = userID.String + msg.UserName = userName.String + msg.ServiceID = serviceID.String + msg.ServiceName = serviceName.String + msg.AlertID = int(alertID.Int64) + if providerID.Valid { + msg.ProviderID, err = ParseProviderMessageID(providerID.String) + if err != nil { + log.Log(ctx, fmt.Errorf("invalid provider message id '%s': %w", providerID.String, err)) + } + } + msg.Dest.Type = dstType.DestType() + switch { + case msg.Dest.Type.IsUserCM(): + msg.Dest.ID = cmID.String + msg.Dest.Value = cmVal.String + default: + msg.Dest.ID = chID.String + msg.Dest.Value = chVal.String + } + + msgs = append(msgs, msg) + } + + return msgs, err +} diff --git a/notification/status.go b/notification/status.go index 7c4f38a0df..d5019b1864 100644 --- a/notification/status.go +++ b/notification/status.go @@ -1,5 +1,7 @@ package notification +import "fmt" + // Status describes the current state of an outgoing message. type Status struct { @@ -70,3 +72,38 @@ const ( // will also fail. StateFailedPerm ) + +func (s *State) fromString(val string) error { + switch val { + case "pending": + *s = StatePending + case "sending", "queued_remotely": + *s = StateSending + case "sent": + *s = StateSent + case "delivered": + *s = StateDelivered + case "failed": + *s = StateFailedPerm + case "stale": + *s = StateFailedTemp + default: + return fmt.Errorf("unexpected value %q", val) + } + + return nil +} + +func (s *State) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return s.fromString(string(v)) + case string: + return s.fromString(v) + case nil: + *s = StateUnknown + return nil + default: + return fmt.Errorf("unexpected type %T", value) + } +} diff --git a/notification/store.go b/notification/store.go index 1e754d8f42..6a319d5c01 100644 --- a/notification/store.go +++ b/notification/store.go @@ -38,6 +38,8 @@ type Store interface { // FindPendingNotifications will return destination info for alerts that are waiting to be sent FindPendingNotifications(ctx context.Context, alertID int) ([]AlertPendingNotification, error) + + RecentMessages(context.Context, *RecentMessageSearchOptions) ([]RecentMessage, error) } var _ Store = &DB{} diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index b140dfdbcb..b5d6f6f3b6 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -2,6 +2,7 @@ export interface Query { phoneNumberInfo?: PhoneNumberInfo + debugMessages: DebugMessage[] user?: User users: UserConnection alert?: Alert @@ -35,6 +36,28 @@ export interface Query { generateSlackAppManifest: string } +export interface DebugMessagesInput { + first?: number + createdBefore?: ISOTimestamp + createdAfter?: ISOTimestamp +} + +export interface DebugMessage { + id: string + createdAt: ISOTimestamp + updatedAt: ISOTimestamp + type: string + status: string + userID: string + userName: string + source: string + destination: string + serviceID: string + serviceName: string + alertID: number + providerID: string +} + export interface SlackChannelSearchOptions { first?: number after?: string From 3dc977597c2e3677a43e41a851fad97b2853ccca Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 7 Dec 2021 11:55:41 -0600 Subject: [PATCH 2/9] add comment on new type --- notification/recentmessage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/notification/recentmessage.go b/notification/recentmessage.go index 935b9b23b7..0a5043530e 100644 --- a/notification/recentmessage.go +++ b/notification/recentmessage.go @@ -14,6 +14,7 @@ import ( "github.com/target/goalert/validation/validate" ) +// A RecentMessage describes a message that is/was recently processed by the system. type RecentMessage struct { ID string CreatedAt time.Time From 199c19b8b7e1486475de677676a350709a5c9b62 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 7 Dec 2021 12:11:13 -0600 Subject: [PATCH 3/9] fix dest formatting --- graphql2/graphqlapp/query.go | 47 +++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index ce741b8c0c..45723c7dec 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -2,10 +2,13 @@ package graphqlapp import ( context "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" @@ -18,8 +21,50 @@ type DebugMessage App func (a *App) Query() graphql2.QueryResolver { return (*Query)(a) } func (a *App) DebugMessage() graphql2.DebugMessageResolver { return (*DebugMessage)(a) } +func (a *App) formatNC(ctx context.Context, id string) (string, error) { + uid, err := uuid.Parse(id) + if err != nil { + return "", err + } + + n, err := a.NCStore.FindOne(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 (a *DebugMessage) Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) { - return obj.Dest.String(), nil + if !obj.Dest.Type.IsUserCM() { + return (*App)(a).formatNC(ctx, obj.Dest.ID) + } + + var str strings.Builder + str.WriteString((*App)(a).FormatDestFunc(ctx, obj.Dest.Type, obj.Dest.Value)) + switch obj.Dest.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(obj.Dest.Type.String()) + } + + return str.String(), nil } func (a *DebugMessage) Type(ctx context.Context, obj *notification.RecentMessage) (string, error) { return strings.TrimPrefix(obj.Type.String(), "MessageType"), nil From 828c35973e4cd405c45e413b4adb5cf5e953fc23 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 10:00:13 -0600 Subject: [PATCH 4/9] use same formatting for src --- graphql2/graphqlapp/query.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index 45723c7dec..b945bf6827 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -41,15 +41,14 @@ func (a *App) formatNC(ctx context.Context, id string) (string, error) { return fmt.Sprintf("%s (%s)", n.Name, typeName), nil } - -func (a *DebugMessage) Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) { - if !obj.Dest.Type.IsUserCM() { - return (*App)(a).formatNC(ctx, obj.Dest.ID) +func (a *DebugMessage) formatDest(ctx context.Context, dst notification.Dest) (string, error) { + if !dst.Type.IsUserCM() { + return (*App)(a).formatNC(ctx, dst.ID) } var str strings.Builder - str.WriteString((*App)(a).FormatDestFunc(ctx, obj.Dest.Type, obj.Dest.Value)) - switch obj.Dest.Type { + str.WriteString((*App)(a).FormatDestFunc(ctx, dst.Type, dst.Value)) + switch dst.Type { case notification.DestTypeSMS: str.WriteString(" (SMS)") case notification.DestTypeUserEmail: @@ -61,11 +60,15 @@ func (a *DebugMessage) Destination(ctx context.Context, obj *notification.Recent str.WriteString("Webhook") default: str.Reset() - str.WriteString(obj.Dest.Type.String()) + str.WriteString(dst.Type.String()) } return str.String(), nil } +func (a *DebugMessage) Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) { + return a.formatDest(ctx, obj.Dest) +} + func (a *DebugMessage) Type(ctx context.Context, obj *notification.RecentMessage) (string, error) { return strings.TrimPrefix(obj.Type.String(), "MessageType"), nil } @@ -97,8 +100,12 @@ func (a *DebugMessage) Source(ctx context.Context, obj *notification.RecentMessa if obj.Status.SrcValue == "" { return "", nil } + if !obj.Dest.Type.IsUserCM() { + // notification channels are unsupported with the current interface + return "", nil + } - return notification.Dest{Type: obj.Dest.Type, Value: obj.Status.SrcValue}.String(), nil + return a.formatDest(ctx, notification.Dest{Type: obj.Dest.Type, Value: obj.Status.SrcValue}) } func (a *DebugMessage) ProviderID(ctx context.Context, obj *notification.RecentMessage) (string, error) { return obj.ProviderID.ExternalID, nil From d944d7b1dfe3f13f45c0d9de03021ac0c4af89b4 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 10:04:08 -0600 Subject: [PATCH 5/9] add bundled state --- notification/status.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/notification/status.go b/notification/status.go index d5019b1864..6b4309713a 100644 --- a/notification/status.go +++ b/notification/status.go @@ -71,6 +71,9 @@ const ( // invalid config, they should set this state, as without manual intervention, a retry // will also fail. StateFailedPerm + + // StateBundled indicates that the message has been bundled into another message. + StateBundled ) func (s *State) fromString(val string) error { @@ -87,6 +90,8 @@ func (s *State) fromString(val string) error { *s = StateFailedPerm case "stale": *s = StateFailedTemp + case "bundled": + *s = StateBundled default: return fmt.Errorf("unexpected value %q", val) } From 30af4a5d772eaa4c8b580168eba0b1d28cf9facd Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 11:02:11 -0600 Subject: [PATCH 6/9] convert empty values to null for GraphQL response --- graphql2/generated.go | 244 +++++++++++------------------------ graphql2/gqlgen.yml | 2 - graphql2/graphqlapp/query.go | 87 ++++++++----- graphql2/models_gen.go | 16 +++ graphql2/schema.graphql | 14 +- 5 files changed, 153 insertions(+), 210 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index fa36683d71..e97c0b7265 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -24,7 +24,6 @@ import ( "github.com/target/goalert/label" "github.com/target/goalert/limit" "github.com/target/goalert/notice" - "github.com/target/goalert/notification" "github.com/target/goalert/notification/slack" "github.com/target/goalert/notification/twilio" "github.com/target/goalert/oncall" @@ -61,7 +60,6 @@ type Config struct { type ResolverRoot interface { Alert() AlertResolver AlertLogEntry() AlertLogEntryResolver - DebugMessage() DebugMessageResolver EscalationPolicy() EscalationPolicyResolver EscalationPolicyStep() EscalationPolicyStepResolver HeartbeatMonitor() HeartbeatMonitorResolver @@ -571,15 +569,6 @@ type AlertLogEntryResolver interface { Message(ctx context.Context, obj *alertlog.Entry) (string, error) State(ctx context.Context, obj *alertlog.Entry) (*NotificationState, error) } -type DebugMessageResolver interface { - Type(ctx context.Context, obj *notification.RecentMessage) (string, error) - Status(ctx context.Context, obj *notification.RecentMessage) (string, error) - - Source(ctx context.Context, obj *notification.RecentMessage) (string, error) - Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) - - ProviderID(ctx context.Context, obj *notification.RecentMessage) (string, error) -} type EscalationPolicyResolver interface { IsFavorite(ctx context.Context, obj *escalation.Policy) (bool, error) AssignedTo(ctx context.Context, obj *escalation.Policy) ([]assignment.RawTarget, error) @@ -653,7 +642,7 @@ type OnCallShiftResolver interface { } type QueryResolver interface { PhoneNumberInfo(ctx context.Context, number string) (*PhoneNumberInfo, error) - DebugMessages(ctx context.Context, input *DebugMessagesInput) ([]notification.RecentMessage, error) + DebugMessages(ctx context.Context, input *DebugMessagesInput) ([]DebugMessage, error) User(ctx context.Context, id *string) (*user.User, error) Users(ctx context.Context, input *UserSearchOptions, first *int, after *string, search *string) (*UserConnection, error) Alert(ctx context.Context, id int) (*alert.Alert, error) @@ -3487,14 +3476,14 @@ type DebugMessage { updatedAt: ISOTimestamp! type: String! status: String! - userID: ID! - userName: String! - source: String! + userID: ID + userName: String + source: String destination: String! - serviceID: ID! - serviceName: String! - alertID: Int! - providerID: ID! + serviceID: ID + serviceName: String + alertID: Int + providerID: ID } input SlackChannelSearchOptions { @@ -7191,7 +7180,7 @@ func (ec *executionContext) _DebugCarrierInfo_mobileCountryCode(ctx context.Cont return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_id(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_id(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7226,7 +7215,7 @@ func (ec *executionContext) _DebugMessage_id(ctx context.Context, field graphql. return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_createdAt(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_createdAt(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7261,7 +7250,7 @@ func (ec *executionContext) _DebugMessage_createdAt(ctx context.Context, field g return ec.marshalNISOTimestamp2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_updatedAt(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_updatedAt(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7296,7 +7285,7 @@ func (ec *executionContext) _DebugMessage_updatedAt(ctx context.Context, field g return ec.marshalNISOTimestamp2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_type(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_type(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7307,14 +7296,14 @@ func (ec *executionContext) _DebugMessage_type(ctx context.Context, field graphq Object: "DebugMessage", Field: field, Args: nil, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.DebugMessage().Type(rctx, obj) + return obj.Type, nil }) if err != nil { ec.Error(ctx, err) @@ -7331,7 +7320,7 @@ func (ec *executionContext) _DebugMessage_type(ctx context.Context, field graphq return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_status(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_status(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7342,14 +7331,14 @@ func (ec *executionContext) _DebugMessage_status(ctx context.Context, field grap Object: "DebugMessage", Field: field, Args: nil, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.DebugMessage().Status(rctx, obj) + return obj.Status, nil }) if err != nil { ec.Error(ctx, err) @@ -7366,7 +7355,7 @@ func (ec *executionContext) _DebugMessage_status(ctx context.Context, field grap return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_userID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_userID(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7391,17 +7380,14 @@ func (ec *executionContext) _DebugMessage_userID(ctx context.Context, field grap return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalOID2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_userName(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_userName(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7426,17 +7412,14 @@ func (ec *executionContext) _DebugMessage_userName(ctx context.Context, field gr return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_source(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_source(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7447,31 +7430,28 @@ func (ec *executionContext) _DebugMessage_source(ctx context.Context, field grap Object: "DebugMessage", Field: field, Args: nil, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.DebugMessage().Source(rctx, obj) + return obj.Source, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_destination(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_destination(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7482,14 +7462,14 @@ func (ec *executionContext) _DebugMessage_destination(ctx context.Context, field Object: "DebugMessage", Field: field, Args: nil, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.DebugMessage().Destination(rctx, obj) + return obj.Destination, nil }) if err != nil { ec.Error(ctx, err) @@ -7506,7 +7486,7 @@ func (ec *executionContext) _DebugMessage_destination(ctx context.Context, field return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_serviceID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_serviceID(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7531,17 +7511,14 @@ func (ec *executionContext) _DebugMessage_serviceID(ctx context.Context, field g return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalOID2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_serviceName(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_serviceName(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7566,17 +7543,14 @@ func (ec *executionContext) _DebugMessage_serviceName(ctx context.Context, field return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_alertID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_alertID(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7601,17 +7575,14 @@ func (ec *executionContext) _DebugMessage_alertID(ctx context.Context, field gra return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(int) + res := resTmp.(*int) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalOInt2ᚖint(ctx, field.Selections, res) } -func (ec *executionContext) _DebugMessage_providerID(ctx context.Context, field graphql.CollectedField, obj *notification.RecentMessage) (ret graphql.Marshaler) { +func (ec *executionContext) _DebugMessage_providerID(ctx context.Context, field graphql.CollectedField, obj *DebugMessage) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -7622,28 +7593,25 @@ func (ec *executionContext) _DebugMessage_providerID(ctx context.Context, field Object: "DebugMessage", Field: field, Args: nil, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.DebugMessage().ProviderID(rctx, obj) + return obj.ProviderID, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*string) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalOID2ᚖstring(ctx, field.Selections, res) } func (ec *executionContext) _DebugMessageStatusInfo_state(ctx context.Context, field graphql.CollectedField, obj *DebugMessageStatusInfo) (ret graphql.Marshaler) { @@ -11485,9 +11453,9 @@ func (ec *executionContext) _Query_debugMessages(ctx context.Context, field grap } return graphql.Null } - res := resTmp.([]notification.RecentMessage) + res := resTmp.([]DebugMessage) fc.Result = res - return ec.marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessageᚄ(ctx, field.Selections, res) + return ec.marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessageᚄ(ctx, field.Selections, res) } func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -21324,7 +21292,7 @@ func (ec *executionContext) _DebugCarrierInfo(ctx context.Context, sel ast.Selec var debugMessageImplementors = []string{"DebugMessage"} -func (ec *executionContext) _DebugMessage(ctx context.Context, sel ast.SelectionSet, obj *notification.RecentMessage) graphql.Marshaler { +func (ec *executionContext) _DebugMessage(ctx context.Context, sel ast.SelectionSet, obj *DebugMessage) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, debugMessageImplementors) out := graphql.NewFieldSet(fields) @@ -21336,113 +21304,47 @@ func (ec *executionContext) _DebugMessage(ctx context.Context, sel ast.Selection case "id": out.Values[i] = ec._DebugMessage_id(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) + invalids++ } case "createdAt": out.Values[i] = ec._DebugMessage_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) + invalids++ } case "updatedAt": out.Values[i] = ec._DebugMessage_updatedAt(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) + invalids++ } case "type": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._DebugMessage_type(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) + out.Values[i] = ec._DebugMessage_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "status": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._DebugMessage_status(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) - case "userID": - out.Values[i] = ec._DebugMessage_userID(ctx, field, obj) + out.Values[i] = ec._DebugMessage_status(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) + invalids++ } + case "userID": + out.Values[i] = ec._DebugMessage_userID(ctx, field, obj) case "userName": out.Values[i] = ec._DebugMessage_userName(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } case "source": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._DebugMessage_source(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) + out.Values[i] = ec._DebugMessage_source(ctx, field, obj) case "destination": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._DebugMessage_destination(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) - case "serviceID": - out.Values[i] = ec._DebugMessage_serviceID(ctx, field, obj) + out.Values[i] = ec._DebugMessage_destination(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) + invalids++ } + case "serviceID": + out.Values[i] = ec._DebugMessage_serviceID(ctx, field, obj) case "serviceName": out.Values[i] = ec._DebugMessage_serviceName(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } case "alertID": out.Values[i] = ec._DebugMessage_alertID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } case "providerID": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._DebugMessage_providerID(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) + out.Values[i] = ec._DebugMessage_providerID(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -25003,11 +24905,11 @@ func (ec *executionContext) unmarshalNDebugCarrierInfoInput2githubᚗcomᚋtarge return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessage(ctx context.Context, sel ast.SelectionSet, v notification.RecentMessage) graphql.Marshaler { +func (ec *executionContext) marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessage(ctx context.Context, sel ast.SelectionSet, v DebugMessage) graphql.Marshaler { return ec._DebugMessage(ctx, sel, &v) } -func (ec *executionContext) marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessageᚄ(ctx context.Context, sel ast.SelectionSet, v []notification.RecentMessage) graphql.Marshaler { +func (ec *executionContext) marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessageᚄ(ctx context.Context, sel ast.SelectionSet, v []DebugMessage) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -25031,7 +24933,7 @@ func (ec *executionContext) marshalNDebugMessage2ᚕgithubᚗcomᚋtargetᚋgoal if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋnotificationᚐRecentMessage(ctx, sel, v[i]) + ret[i] = ec.marshalNDebugMessage2githubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDebugMessage(ctx, sel, v[i]) } if isLen1 { f(i) diff --git a/graphql2/gqlgen.yml b/graphql2/gqlgen.yml index 2d6b06d65f..22f18618ee 100644 --- a/graphql2/gqlgen.yml +++ b/graphql2/gqlgen.yml @@ -94,8 +94,6 @@ models: model: github.com/target/goalert/graphql2.OnCallNotificationRuleInput WeekdayFilter: model: github.com/target/goalert/util/timeutil.WeekdayFilter - DebugMessage: - model: github.com/target/goalert/notification.RecentMessage ID: model: - github.com/99designs/gqlgen/graphql.ID diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index b945bf6827..77cfe346c2 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -18,8 +18,7 @@ import ( type Query App type DebugMessage App -func (a *App) Query() graphql2.QueryResolver { return (*Query)(a) } -func (a *App) DebugMessage() graphql2.DebugMessageResolver { return (*DebugMessage)(a) } +func (a *App) Query() graphql2.QueryResolver { return (*Query)(a) } func (a *App) formatNC(ctx context.Context, id string) (string, error) { uid, err := uuid.Parse(id) @@ -41,7 +40,8 @@ func (a *App) formatNC(ctx context.Context, id string) (string, error) { return fmt.Sprintf("%s (%s)", n.Name, typeName), nil } -func (a *DebugMessage) formatDest(ctx context.Context, dst notification.Dest) (string, error) { + +func (a *Query) formatDest(ctx context.Context, dst notification.Dest) (string, error) { if !dst.Type.IsUserCM() { return (*App)(a).formatNC(ctx, dst.ID) } @@ -65,16 +65,10 @@ func (a *DebugMessage) formatDest(ctx context.Context, dst notification.Dest) (s return str.String(), nil } -func (a *DebugMessage) Destination(ctx context.Context, obj *notification.RecentMessage) (string, error) { - return a.formatDest(ctx, obj.Dest) -} -func (a *DebugMessage) Type(ctx context.Context, obj *notification.RecentMessage) (string, error) { - return strings.TrimPrefix(obj.Type.String(), "MessageType"), nil -} -func (a *DebugMessage) Status(ctx context.Context, obj *notification.RecentMessage) (string, error) { +func msgStatus(stat notification.Status) string { var str strings.Builder - switch obj.Status.State { + switch stat.State { case notification.StateUnknown: str.WriteString("Unknown") case notification.StateSending: @@ -90,28 +84,14 @@ func (a *DebugMessage) Status(ctx context.Context, obj *notification.RecentMessa case notification.StateFailedPerm: str.WriteString("Failed (permanent)") } - if obj.Status.Details != "" { + if stat.Details != "" { str.WriteString(": ") - str.WriteString(obj.Status.Details) + str.WriteString(stat.Details) } - return str.String(), nil + return str.String() } -func (a *DebugMessage) Source(ctx context.Context, obj *notification.RecentMessage) (string, error) { - if obj.Status.SrcValue == "" { - return "", nil - } - if !obj.Dest.Type.IsUserCM() { - // notification channels are unsupported with the current interface - return "", nil - } - return a.formatDest(ctx, notification.Dest{Type: obj.Dest.Type, Value: obj.Status.SrcValue}) -} -func (a *DebugMessage) ProviderID(ctx context.Context, obj *notification.RecentMessage) (string, error) { - return obj.ProviderID.ExternalID, nil -} - -func (a *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessagesInput) ([]notification.RecentMessage, error) { +func (a *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessagesInput) ([]graphql2.DebugMessage, error) { var options notification.RecentMessageSearchOptions if input == nil { input = &graphql2.DebugMessagesInput{} @@ -125,7 +105,54 @@ func (a *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessages if input.First != nil { options.Limit = *input.First } - return a.NotificationStore.RecentMessages(ctx, &options) + msgs, err := a.NotificationStore.RecentMessages(ctx, &options) + if err != nil { + return nil, err + } + + var res []graphql2.DebugMessage + for _, _m := range msgs { + m := _m // clone since we're taking pointers to fields + dest, err := a.formatDest(ctx, m.Dest) + if err != nil { + return nil, fmt.Errorf("format dest: %w", err) + } + + msg := graphql2.DebugMessage{ + ID: m.ID, + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + Type: strings.TrimPrefix(m.Type.String(), "MessageType"), + Status: msgStatus(m.Status), + Destination: dest, + } + if m.UserID != "" { + msg.UserID = &m.UserID + } + if m.UserName != "" { + msg.UserName = &m.UserName + } + if m.Status.SrcValue != "" && !m.Dest.Type.IsUserCM() { + src, err := a.formatDest(ctx, notification.Dest{Type: m.Dest.Type, Value: m.Status.SrcValue}) + if err != nil { + return nil, fmt.Errorf("format src: %w", err) + } + msg.Source = &src + } + if m.ServiceID != "" { + msg.ServiceID = &m.ServiceID + } + if m.ServiceName != "" { + msg.ServiceName = &m.ServiceName + } + if m.ProviderID.ExternalID != "" { + msg.ProviderID = &m.ProviderID.ExternalID + } + + res = append(res, msg) + } + + return res, nil } func (a *Query) AuthSubjectsForProvider(ctx context.Context, _first *int, _after *string, providerID string) (conn *graphql2.AuthSubjectConnection, err error) { diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index f94b7534ce..c785b9ae44 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -203,6 +203,22 @@ type DebugCarrierInfoInput struct { Number string `json:"number"` } +type DebugMessage struct { + ID string `json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Type string `json:"type"` + Status string `json:"status"` + UserID *string `json:"userID"` + UserName *string `json:"userName"` + Source *string `json:"source"` + Destination string `json:"destination"` + ServiceID *string `json:"serviceID"` + ServiceName *string `json:"serviceName"` + AlertID *int `json:"alertID"` + ProviderID *string `json:"providerID"` +} + type DebugMessageStatusInfo struct { State *NotificationState `json:"state"` } diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index e5170d70e0..bae7d2264d 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -123,14 +123,14 @@ type DebugMessage { updatedAt: ISOTimestamp! type: String! status: String! - userID: ID! - userName: String! - source: String! + userID: ID + userName: String + source: String destination: String! - serviceID: ID! - serviceName: String! - alertID: Int! - providerID: ID! + serviceID: ID + serviceName: String + alertID: Int + providerID: ID } input SlackChannelSearchOptions { From 2d711768a824e3b24d0a90db43e8ac98d0a87429 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 11:08:26 -0600 Subject: [PATCH 7/9] add bundled case --- graphql2/graphqlapp/query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index 77cfe346c2..98d6b80d21 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -69,6 +69,8 @@ func (a *Query) formatDest(ctx context.Context, dst notification.Dest) (string, 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: From c290bfa070ecbb677a7f88dc668925f22d4ac631 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 11:49:49 -0600 Subject: [PATCH 8/9] Update graphql2/graphqlapp/query.go Co-authored-by: David Talbot --- graphql2/graphqlapp/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql2/graphqlapp/query.go b/graphql2/graphqlapp/query.go index 98d6b80d21..2ec95e6902 100644 --- a/graphql2/graphqlapp/query.go +++ b/graphql2/graphqlapp/query.go @@ -134,7 +134,7 @@ func (a *Query) DebugMessages(ctx context.Context, input *graphql2.DebugMessages if m.UserName != "" { msg.UserName = &m.UserName } - if m.Status.SrcValue != "" && !m.Dest.Type.IsUserCM() { + if m.Status.SrcValue != "" && m.Dest.Type.IsUserCM() { src, err := a.formatDest(ctx, notification.Dest{Type: m.Dest.Type, Value: m.Status.SrcValue}) if err != nil { return nil, fmt.Errorf("format src: %w", err) From e19e8fcb47c412a5ebe4869f80dddb9ad2800aaf Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 8 Dec 2021 16:32:05 -0600 Subject: [PATCH 9/9] regen schema --- web/src/schema.d.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index b5d6f6f3b6..0c1767eede 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -48,14 +48,14 @@ export interface DebugMessage { updatedAt: ISOTimestamp type: string status: string - userID: string - userName: string - source: string + userID?: string + userName?: string + source?: string destination: string - serviceID: string - serviceName: string - alertID: number - providerID: string + serviceID?: string + serviceName?: string + alertID?: number + providerID?: string } export interface SlackChannelSearchOptions {