From d839c3645a8e7787b5bbecc72d9524f342946719 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 7 Feb 2024 15:25:40 -0600 Subject: [PATCH] graphql: destinations: add `dest` as a field on OnCallNotificationRule (#3659) * remove unused `typeInfo` and rename `displayInfo` * add resolver and sched dest field * update schema --- graphql2/generated.go | 349 ++++++++++++------ graphql2/graph/destinations.graphqls | 7 +- graphql2/graphqlapp/compat.go | 81 ++++ graphql2/graphqlapp/destinationdisplayinfo.go | 20 + graphql2/graphqlapp/schedule.go | 4 + graphql2/models_gen.go | 7 +- graphql2/schema.graphql | 1 + web/src/schema.d.ts | 4 +- 8 files changed, 343 insertions(+), 130 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index f2641df023..90c35db1c0 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -66,6 +66,7 @@ type ResolverRoot interface { Alert() AlertResolver AlertLogEntry() AlertLogEntryResolver AlertMetric() AlertMetricResolver + Destination() DestinationResolver EscalationPolicy() EscalationPolicyResolver EscalationPolicyStep() EscalationPolicyStepResolver FieldValuePair() FieldValuePairResolver @@ -216,10 +217,9 @@ type ComplexityRoot struct { } Destination struct { - Display func(childComplexity int) int - Type func(childComplexity int) int - TypeInfo func(childComplexity int) int - Values func(childComplexity int) int + DisplayInfo func(childComplexity int) int + Type func(childComplexity int) int + Values func(childComplexity int) int } DestinationDisplayInfo struct { @@ -439,6 +439,7 @@ type ComplexityRoot struct { } OnCallNotificationRule struct { + Dest func(childComplexity int) int ID func(childComplexity int) int Target func(childComplexity int) int Time func(childComplexity int) int @@ -783,6 +784,9 @@ type AlertMetricResolver interface { TimeToAck(ctx context.Context, obj *alertmetrics.Metric) (*timeutil.ISODuration, error) TimeToClose(ctx context.Context, obj *alertmetrics.Metric) (*timeutil.ISODuration, error) } +type DestinationResolver interface { + DisplayInfo(ctx context.Context, obj *Destination) (*DestinationDisplayInfo, error) +} type EscalationPolicyResolver interface { IsFavorite(ctx context.Context, obj *escalation.Policy) (bool, error) AssignedTo(ctx context.Context, obj *escalation.Policy) ([]assignment.RawTarget, error) @@ -870,6 +874,7 @@ type MutationResolver interface { } type OnCallNotificationRuleResolver interface { Target(ctx context.Context, obj *schedule.OnCallNotificationRule) (*assignment.RawTarget, error) + Dest(ctx context.Context, obj *schedule.OnCallNotificationRule) (*Destination, error) } type OnCallShiftResolver interface { User(ctx context.Context, obj *oncall.Shift) (*user.User, error) @@ -1509,12 +1514,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DebugSendSMSInfo.ProviderURL(childComplexity), true - case "Destination.display": - if e.complexity.Destination.Display == nil { + case "Destination.displayInfo": + if e.complexity.Destination.DisplayInfo == nil { break } - return e.complexity.Destination.Display(childComplexity), true + return e.complexity.Destination.DisplayInfo(childComplexity), true case "Destination.type": if e.complexity.Destination.Type == nil { @@ -1523,13 +1528,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Destination.Type(childComplexity), true - case "Destination.typeInfo": - if e.complexity.Destination.TypeInfo == nil { - break - } - - return e.complexity.Destination.TypeInfo(childComplexity), true - case "Destination.values": if e.complexity.Destination.Values == nil { break @@ -2847,6 +2845,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.NotificationState.Status(childComplexity), true + case "OnCallNotificationRule.dest": + if e.complexity.OnCallNotificationRule.Dest == nil { + break + } + + return e.complexity.OnCallNotificationRule.Dest(childComplexity), true + case "OnCallNotificationRule.id": if e.complexity.OnCallNotificationRule.ID == nil { break @@ -9567,8 +9572,8 @@ func (ec *executionContext) fieldContext_Destination_values(ctx context.Context, return fc, nil } -func (ec *executionContext) _Destination_typeInfo(ctx context.Context, field graphql.CollectedField, obj *Destination) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Destination_typeInfo(ctx, field) +func (ec *executionContext) _Destination_displayInfo(ctx context.Context, field graphql.CollectedField, obj *Destination) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Destination_displayInfo(ctx, field) if err != nil { return graphql.Null } @@ -9581,79 +9586,7 @@ func (ec *executionContext) _Destination_typeInfo(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.TypeInfo, 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.(*DestinationTypeInfo) - fc.Result = res - return ec.marshalNDestinationTypeInfo2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDestinationTypeInfo(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Destination_typeInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Destination", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "type": - return ec.fieldContext_DestinationTypeInfo_type(ctx, field) - case "name": - return ec.fieldContext_DestinationTypeInfo_name(ctx, field) - case "iconURL": - return ec.fieldContext_DestinationTypeInfo_iconURL(ctx, field) - case "iconAltText": - return ec.fieldContext_DestinationTypeInfo_iconAltText(ctx, field) - case "disabledMessage": - return ec.fieldContext_DestinationTypeInfo_disabledMessage(ctx, field) - case "enabled": - return ec.fieldContext_DestinationTypeInfo_enabled(ctx, field) - case "requiredFields": - return ec.fieldContext_DestinationTypeInfo_requiredFields(ctx, field) - case "userDisclaimer": - return ec.fieldContext_DestinationTypeInfo_userDisclaimer(ctx, field) - case "isContactMethod": - return ec.fieldContext_DestinationTypeInfo_isContactMethod(ctx, field) - case "isEPTarget": - return ec.fieldContext_DestinationTypeInfo_isEPTarget(ctx, field) - case "isSchedOnCallNotify": - return ec.fieldContext_DestinationTypeInfo_isSchedOnCallNotify(ctx, field) - case "supportsStatusUpdates": - return ec.fieldContext_DestinationTypeInfo_supportsStatusUpdates(ctx, field) - case "statusUpdatesRequired": - return ec.fieldContext_DestinationTypeInfo_statusUpdatesRequired(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type DestinationTypeInfo", field.Name) - }, - } - return fc, nil -} - -func (ec *executionContext) _Destination_display(ctx context.Context, field graphql.CollectedField, obj *Destination) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Destination_display(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Display, nil + return ec.resolvers.Destination().DisplayInfo(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -9670,12 +9603,12 @@ func (ec *executionContext) _Destination_display(ctx context.Context, field grap return ec.marshalNDestinationDisplayInfo2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDestinationDisplayInfo(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Destination_display(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Destination_displayInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Destination", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "text": @@ -17498,6 +17431,82 @@ func (ec *executionContext) fieldContext_OnCallNotificationRule_target(ctx conte return fc, nil } +func (ec *executionContext) _OnCallNotificationRule_dest(ctx context.Context, field graphql.CollectedField, obj *schedule.OnCallNotificationRule) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OnCallNotificationRule_dest(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.OnCallNotificationRule().Dest(rctx, obj) + } + directive1 := func(ctx context.Context) (interface{}, error) { + flagName, err := ec.unmarshalNString2string(ctx, "dest-types") + if err != nil { + return nil, err + } + if ec.directives.Experimental == nil { + return nil, errors.New("directive experimental is not implemented") + } + return ec.directives.Experimental(ctx, obj, directive0, flagName) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*Destination); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/target/goalert/graphql2.Destination`, tmp) + }) + 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.(*Destination) + fc.Result = res + return ec.marshalNDestination2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDestination(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OnCallNotificationRule_dest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OnCallNotificationRule", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "type": + return ec.fieldContext_Destination_type(ctx, field) + case "values": + return ec.fieldContext_Destination_values(ctx, field) + case "displayInfo": + return ec.fieldContext_Destination_displayInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Destination", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _OnCallNotificationRule_time(ctx context.Context, field graphql.CollectedField, obj *schedule.OnCallNotificationRule) (ret graphql.Marshaler) { fc, err := ec.fieldContext_OnCallNotificationRule_time(ctx, field) if err != nil { @@ -20974,8 +20983,32 @@ func (ec *executionContext) _Query_destinationFieldSearch(ctx context.Context, f } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().DestinationFieldSearch(rctx, fc.Args["input"].(DestinationFieldSearchInput)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DestinationFieldSearch(rctx, fc.Args["input"].(DestinationFieldSearchInput)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + flagName, err := ec.unmarshalNString2string(ctx, "dest-types") + if err != nil { + return nil, err + } + if ec.directives.Experimental == nil { + return nil, errors.New("directive experimental is not implemented") + } + return ec.directives.Experimental(ctx, nil, directive0, flagName) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*FieldValueConnection); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/target/goalert/graphql2.FieldValueConnection`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -21035,8 +21068,32 @@ func (ec *executionContext) _Query_destinationFieldValueName(ctx context.Context } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().DestinationFieldValueName(rctx, fc.Args["input"].(DestinationFieldValidateInput)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DestinationFieldValueName(rctx, fc.Args["input"].(DestinationFieldValidateInput)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + flagName, err := ec.unmarshalNString2string(ctx, "dest-types") + if err != nil { + return nil, err + } + if ec.directives.Experimental == nil { + return nil, errors.New("directive experimental is not implemented") + } + return ec.directives.Experimental(ctx, nil, directive0, flagName) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(string); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be string`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -23367,6 +23424,8 @@ func (ec *executionContext) fieldContext_Schedule_onCallNotificationRules(ctx co return ec.fieldContext_OnCallNotificationRule_id(ctx, field) case "target": return ec.fieldContext_OnCallNotificationRule_target(ctx, field) + case "dest": + return ec.fieldContext_OnCallNotificationRule_dest(ctx, field) case "time": return ec.fieldContext_OnCallNotificationRule_time(ctx, field) case "weekdayFilter": @@ -27382,10 +27441,8 @@ func (ec *executionContext) fieldContext_UserContactMethod_dest(ctx context.Cont return ec.fieldContext_Destination_type(ctx, field) case "values": return ec.fieldContext_Destination_values(ctx, field) - case "typeInfo": - return ec.fieldContext_Destination_typeInfo(ctx, field) - case "display": - return ec.fieldContext_Destination_display(ctx, field) + case "displayInfo": + return ec.fieldContext_Destination_displayInfo(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Destination", field.Name) }, @@ -35717,23 +35774,49 @@ func (ec *executionContext) _Destination(ctx context.Context, sel ast.SelectionS case "type": out.Values[i] = ec._Destination_type(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "values": out.Values[i] = ec._Destination_values(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } - case "typeInfo": - out.Values[i] = ec._Destination_typeInfo(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + case "displayInfo": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Destination_displayInfo(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res } - case "display": - out.Values[i] = ec._Destination_display(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -37783,6 +37866,42 @@ func (ec *executionContext) _OnCallNotificationRule(ctx context.Context, sel ast continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "dest": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._OnCallNotificationRule_dest(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "time": out.Values[i] = ec._OnCallNotificationRule_time(ctx, field, obj) @@ -43329,16 +43448,6 @@ func (ec *executionContext) marshalNDestinationTypeInfo2ᚕgithubᚗcomᚋtarget return ret } -func (ec *executionContext) marshalNDestinationTypeInfo2ᚖgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐDestinationTypeInfo(ctx context.Context, sel ast.SelectionSet, v *DestinationTypeInfo) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "the requested element is null which the schema does not allow") - } - return graphql.Null - } - return ec._DestinationTypeInfo(ctx, sel, v) -} - func (ec *executionContext) marshalNEscalationPolicy2githubᚗcomᚋtargetᚋgoalertᚋescalationᚐPolicy(ctx context.Context, sel ast.SelectionSet, v escalation.Policy) graphql.Marshaler { return ec._EscalationPolicy(ctx, sel, &v) } diff --git a/graphql2/graph/destinations.graphqls b/graphql2/graph/destinations.graphqls index de06f31d18..98515de8e3 100644 --- a/graphql2/graph/destinations.graphqls +++ b/graphql2/graph/destinations.graphqls @@ -7,8 +7,8 @@ extend type Query { # It does not guarantee that the value is valid for the destination type, only # that it is valid for the field (i.e., syntax/formatting). destinationFieldValidate(input: DestinationFieldValidateInput!): Boolean! @experimental(flagName: "dest-types") - destinationFieldSearch(input: DestinationFieldSearchInput!): FieldValueConnection! - destinationFieldValueName(input: DestinationFieldValidateInput!): String! + destinationFieldSearch(input: DestinationFieldSearchInput!): FieldValueConnection! @experimental(flagName: "dest-types") + destinationFieldValueName(input: DestinationFieldValidateInput!): String! @experimental(flagName: "dest-types") # destinationDisplayInfo returns the display information for a destination. destinationDisplayInfo(input: DestinationInput!): DestinationDisplayInfo! @experimental(flagName: "dest-types") @@ -43,8 +43,7 @@ input DestinationFieldSearchInput { type Destination { type: DestinationType! values: [FieldValuePair!]! - typeInfo: DestinationTypeInfo! - display: DestinationDisplayInfo! + displayInfo: DestinationDisplayInfo! @goField(forceResolver: true) } # DestinationDisplayInfo provides information for displaying a destination. diff --git a/graphql2/graphqlapp/compat.go b/graphql2/graphqlapp/compat.go index 089ac53cba..86acd5a87a 100644 --- a/graphql2/graphqlapp/compat.go +++ b/graphql2/graphqlapp/compat.go @@ -1,10 +1,91 @@ package graphqlapp import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/google/uuid" "github.com/target/goalert/graphql2" + "github.com/target/goalert/notificationchannel" "github.com/target/goalert/user/contactmethod" ) +// CompatNCToDest converts a notification channel to a destination. +func (a *App) CompatNCToDest(ctx context.Context, ncID uuid.UUID) (*graphql2.Destination, error) { + nc, err := a.FindOneNC(ctx, ncID) + if err != nil { + return nil, err + } + + switch nc.Type { + case notificationchannel.TypeSlackChan: + ch, err := a.SlackStore.Channel(ctx, nc.Value) + if err != nil { + return nil, err + } + + return &graphql2.Destination{ + Type: destSlackChan, + Values: []graphql2.FieldValuePair{ + { + FieldID: fieldSlackChanID, + Value: nc.Value, + Label: ch.Name, + }, + }, + }, nil + case notificationchannel.TypeSlackUG: + ugID, chanID, ok := strings.Cut(nc.Value, ":") + if !ok { + return nil, fmt.Errorf("invalid slack usergroup pair: %s", nc.Value) + } + ug, err := a.SlackStore.UserGroup(ctx, ugID) + if err != nil { + return nil, err + } + ch, err := a.SlackStore.Channel(ctx, chanID) + if err != nil { + return nil, err + } + + return &graphql2.Destination{ + Type: destSlackUG, + Values: []graphql2.FieldValuePair{ + { + FieldID: fieldSlackUGID, + Value: ugID, + Label: ug.Handle, + }, + { + FieldID: fieldSlackChanID, + Value: chanID, + Label: ch.Name, + }, + }, + }, nil + case notificationchannel.TypeWebhook: + u, err := url.Parse(nc.Value) + if err != nil { + return nil, err + } + + return &graphql2.Destination{ + Type: destWebhook, + Values: []graphql2.FieldValuePair{ + { + FieldID: fieldWebhookURL, + Value: nc.Value, + Label: u.Hostname(), + }, + }, + }, nil + default: + return nil, fmt.Errorf("unsupported notification channel type: %s", nc.Type) + } +} + // CompatDestToCMTypeVal converts a graphql2.DestinationInput to a contactmethod.Type and string value // for the built-in destination types. func CompatDestToCMTypeVal(d graphql2.DestinationInput) (contactmethod.Type, string) { diff --git a/graphql2/graphqlapp/destinationdisplayinfo.go b/graphql2/graphqlapp/destinationdisplayinfo.go index 3157bacb66..491154a9af 100644 --- a/graphql2/graphqlapp/destinationdisplayinfo.go +++ b/graphql2/graphqlapp/destinationdisplayinfo.go @@ -11,6 +11,26 @@ import ( "github.com/target/goalert/validation" ) +type ( + Destination App +) + +func (a *App) Destination() graphql2.DestinationResolver { return (*Destination)(a) } + +// DisplayInfo will return the display information for a destination by mapping to Query.DestinationDisplayInfo. +func (a *Destination) DisplayInfo(ctx context.Context, obj *graphql2.Destination) (*graphql2.DestinationDisplayInfo, error) { + if obj.DisplayInfo != nil { + return obj.DisplayInfo, nil + } + + values := make([]graphql2.FieldValueInput, len(obj.Values)) + for i, v := range obj.Values { + values[i] = graphql2.FieldValueInput{FieldID: v.FieldID, Value: v.Value} + } + + return (*Query)(a).DestinationDisplayInfo(ctx, graphql2.DestinationInput{Type: obj.Type, Values: values}) +} + func (a *Query) DestinationDisplayInfo(ctx context.Context, dest graphql2.DestinationInput) (*graphql2.DestinationDisplayInfo, error) { app := (*App)(a) cfg := config.FromContext(ctx) diff --git a/graphql2/graphqlapp/schedule.go b/graphql2/graphqlapp/schedule.go index d528679e82..1adba6160b 100644 --- a/graphql2/graphqlapp/schedule.go +++ b/graphql2/graphqlapp/schedule.go @@ -36,6 +36,10 @@ func (a *App) OnCallNotificationRule() graphql2.OnCallNotificationRuleResolver { return (*OnCallNotificationRule)(a) } +func (a *OnCallNotificationRule) Dest(ctx context.Context, raw *schedule.OnCallNotificationRule) (*graphql2.Destination, error) { + return (*App)(a).CompatNCToDest(ctx, raw.ChannelID) +} + func (a *OnCallNotificationRule) Target(ctx context.Context, raw *schedule.OnCallNotificationRule) (*assignment.RawTarget, error) { ch, err := (*App)(a).FindOneNC(ctx, raw.ChannelID) if err != nil { diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index 90dd644370..bf47d05580 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -295,10 +295,9 @@ type DebugSendSMSInput struct { } type Destination struct { - Type string `json:"type"` - Values []FieldValuePair `json:"values"` - TypeInfo *DestinationTypeInfo `json:"typeInfo"` - Display *DestinationDisplayInfo `json:"display"` + Type string `json:"type"` + Values []FieldValuePair `json:"values"` + DisplayInfo *DestinationDisplayInfo `json:"displayInfo"` } type DestinationDisplayInfo struct { diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index 971b394370..ffe43e07c5 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -652,6 +652,7 @@ input OnCallNotificationRuleInput { type OnCallNotificationRule { id: ID! target: Target! + dest: Destination! @experimental(flagName: "dest-types") time: ClockTime weekdayFilter: WeekdayFilter } diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index ad90eed780..061da7906f 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -343,9 +343,8 @@ export interface DebugSendSMSInput { } export interface Destination { - display: DestinationDisplayInfo + displayInfo: DestinationDisplayInfo type: DestinationType - typeInfo: DestinationTypeInfo values: FieldValuePair[] } @@ -666,6 +665,7 @@ export interface NotificationState { export type NotificationStatus = 'ERROR' | 'OK' | 'WARN' export interface OnCallNotificationRule { + dest: Destination id: string target: Target time?: null | ClockTime