graphql: make Bug's actors and participants a connection

Michael Muré created

Change summary

graphql/connections/connection_template.go |   3 
graphql/connections/connections.go         |   5 
graphql/connections/gen_identity.go        |  27 
graphql/connections/gen_lazy_bug.go        |   0 
graphql/connections/gen_lazy_identity.go   | 110 +++++++
graphql/graph/gen_graph.go                 | 340 +++++++++++++++--------
graphql/graphql_test.go                    |  75 +++-
graphql/resolvers/bug.go                   |  54 +++
graphql/schema/bug.graphql                 |  27 +
9 files changed, 469 insertions(+), 172 deletions(-)

Detailed changes

graphql/connections/connection_template.go 🔗

@@ -3,8 +3,9 @@ package connections
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/graphql/models"
 	"github.com/cheekybits/genny/generic"
+
+	"github.com/MichaelMure/git-bug/graphql/models"
 )
 
 // Name define the name of the connection

graphql/connections/connections.go 🔗

@@ -1,5 +1,6 @@
-//go:generate genny -in=connection_template.go -out=gen_bug.go gen "Name=LazyBug NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
-//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=LazyIdentity NodeType=string EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
+//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
+//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=string EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
+//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=identity.Interface EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
 //go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
 //go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
 //go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"

graphql/connections/gen_identity.go 🔗

@@ -8,23 +8,24 @@ import (
 	"fmt"
 
 	"github.com/MichaelMure/git-bug/graphql/models"
+	"github.com/MichaelMure/git-bug/identity"
 )
 
-// StringEdgeMaker define a function that take a string and an offset and
+// IdentityInterfaceEdgeMaker define a function that take a identity.Interface and an offset and
 // create an Edge.
-type LazyIdentityEdgeMaker func(value string, offset int) Edge
+type IdentityEdgeMaker func(value identity.Interface, offset int) Edge
 
-// LazyIdentityConMaker define a function that create a models.IdentityConnection
-type LazyIdentityConMaker func(
-	edges []LazyIdentityEdge,
-	nodes []string,
+// IdentityConMaker define a function that create a models.IdentityConnection
+type IdentityConMaker func(
+	edges []models.IdentityEdge,
+	nodes []identity.Interface,
 	info models.PageInfo,
 	totalCount int) (models.IdentityConnection, error)
 
-// LazyIdentityCon will paginate a source according to the input of a relay connection
-func LazyIdentityCon(source []string, edgeMaker LazyIdentityEdgeMaker, conMaker LazyIdentityConMaker, input models.ConnectionInput) (models.IdentityConnection, error) {
-	var nodes []string
-	var edges []LazyIdentityEdge
+// IdentityCon will paginate a source according to the input of a relay connection
+func IdentityCon(source []identity.Interface, edgeMaker IdentityEdgeMaker, conMaker IdentityConMaker, input models.ConnectionInput) (models.IdentityConnection, error) {
+	var nodes []identity.Interface
+	var edges []models.IdentityEdge
 	var cursors []string
 	var pageInfo models.PageInfo
 	var totalCount = len(source)
@@ -56,18 +57,18 @@ func LazyIdentityCon(source []string, edgeMaker LazyIdentityEdgeMaker, conMaker
 				break
 			}
 
-			edges = append(edges, edge.(LazyIdentityEdge))
+			edges = append(edges, edge.(models.IdentityEdge))
 			cursors = append(cursors, edge.GetCursor())
 			nodes = append(nodes, value)
 		}
 	} else {
-		edges = make([]LazyIdentityEdge, len(source))
+		edges = make([]models.IdentityEdge, len(source))
 		cursors = make([]string, len(source))
 		nodes = source
 
 		for i, value := range source {
 			edge := edgeMaker(value, i+offset)
-			edges[i] = edge.(LazyIdentityEdge)
+			edges[i] = edge.(models.IdentityEdge)
 			cursors[i] = edge.GetCursor()
 		}
 	}

graphql/connections/gen_lazy_identity.go 🔗

@@ -0,0 +1,110 @@
+// This file was automatically generated by genny.
+// Any changes will be lost if this file is regenerated.
+// see https://github.com/cheekybits/genny
+
+package connections
+
+import (
+	"fmt"
+
+	"github.com/MichaelMure/git-bug/graphql/models"
+)
+
+// StringEdgeMaker define a function that take a string and an offset and
+// create an Edge.
+type LazyIdentityEdgeMaker func(value string, offset int) Edge
+
+// LazyIdentityConMaker define a function that create a models.IdentityConnection
+type LazyIdentityConMaker func(
+	edges []LazyIdentityEdge,
+	nodes []string,
+	info models.PageInfo,
+	totalCount int) (models.IdentityConnection, error)
+
+// LazyIdentityCon will paginate a source according to the input of a relay connection
+func LazyIdentityCon(source []string, edgeMaker LazyIdentityEdgeMaker, conMaker LazyIdentityConMaker, input models.ConnectionInput) (models.IdentityConnection, error) {
+	var nodes []string
+	var edges []LazyIdentityEdge
+	var cursors []string
+	var pageInfo models.PageInfo
+	var totalCount = len(source)
+
+	emptyCon, _ := conMaker(edges, nodes, pageInfo, 0)
+
+	offset := 0
+
+	if input.After != nil {
+		for i, value := range source {
+			edge := edgeMaker(value, i)
+			if edge.GetCursor() == *input.After {
+				// remove all previous element including the "after" one
+				source = source[i+1:]
+				offset = i + 1
+				pageInfo.HasPreviousPage = true
+				break
+			}
+		}
+	}
+
+	if input.Before != nil {
+		for i, value := range source {
+			edge := edgeMaker(value, i+offset)
+
+			if edge.GetCursor() == *input.Before {
+				// remove all after element including the "before" one
+				pageInfo.HasNextPage = true
+				break
+			}
+
+			edges = append(edges, edge.(LazyIdentityEdge))
+			cursors = append(cursors, edge.GetCursor())
+			nodes = append(nodes, value)
+		}
+	} else {
+		edges = make([]LazyIdentityEdge, len(source))
+		cursors = make([]string, len(source))
+		nodes = source
+
+		for i, value := range source {
+			edge := edgeMaker(value, i+offset)
+			edges[i] = edge.(LazyIdentityEdge)
+			cursors[i] = edge.GetCursor()
+		}
+	}
+
+	if input.First != nil {
+		if *input.First < 0 {
+			return emptyCon, fmt.Errorf("first less than zero")
+		}
+
+		if len(edges) > *input.First {
+			// Slice result to be of length first by removing edges from the end
+			edges = edges[:*input.First]
+			cursors = cursors[:*input.First]
+			nodes = nodes[:*input.First]
+			pageInfo.HasNextPage = true
+		}
+	}
+
+	if input.Last != nil {
+		if *input.Last < 0 {
+			return emptyCon, fmt.Errorf("last less than zero")
+		}
+
+		if len(edges) > *input.Last {
+			// Slice result to be of length last by removing edges from the start
+			edges = edges[len(edges)-*input.Last:]
+			cursors = cursors[len(cursors)-*input.Last:]
+			nodes = nodes[len(nodes)-*input.Last:]
+			pageInfo.HasPreviousPage = true
+		}
+	}
+
+	// Fill up pageInfo cursors
+	if len(cursors) > 0 {
+		pageInfo.StartCursor = cursors[0]
+		pageInfo.EndCursor = cursors[len(cursors)-1]
+	}
+
+	return conMaker(edges, nodes, pageInfo, totalCount)
+}

graphql/graph/gen_graph.go 🔗

@@ -87,10 +87,10 @@ type ComplexityRoot struct {
 		Title        func(childComplexity int) int
 		Labels       func(childComplexity int) int
 		Author       func(childComplexity int) int
-		Actors       func(childComplexity int) int
-		Participants func(childComplexity int) int
 		CreatedAt    func(childComplexity int) int
 		LastEdit     func(childComplexity int) int
+		Actors       func(childComplexity int, after *string, before *string, first *int, last *int) int
+		Participants func(childComplexity int, after *string, before *string, first *int, last *int) int
 		Comments     func(childComplexity int, after *string, before *string, first *int, last *int) int
 		Timeline     func(childComplexity int, after *string, before *string, first *int, last *int) int
 		Operations   func(childComplexity int, after *string, before *string, first *int, last *int) int
@@ -295,10 +295,9 @@ type AddCommentTimelineItemResolver interface {
 type BugResolver interface {
 	Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
 
-	Actors(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error)
-	Participants(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error)
-
 	LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
+	Actors(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.IdentityConnection, error)
+	Participants(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.IdentityConnection, error)
 	Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
 	Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.TimelineItemConnection, error)
 	Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
@@ -367,6 +366,130 @@ type SetTitleTimelineItemResolver interface {
 	Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (time.Time, error)
 }
 
+func field_Bug_actors_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := rawArgs["after"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["after"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["before"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg1 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["before"] = arg1
+	var arg2 *int
+	if tmp, ok := rawArgs["first"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg2 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["first"] = arg2
+	var arg3 *int
+	if tmp, ok := rawArgs["last"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg3 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["last"] = arg3
+	return args, nil
+
+}
+
+func field_Bug_participants_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := rawArgs["after"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["after"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["before"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg1 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["before"] = arg1
+	var arg2 *int
+	if tmp, ok := rawArgs["first"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg2 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["first"] = arg2
+	var arg3 *int
+	if tmp, ok := rawArgs["last"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg3 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["last"] = arg3
+	return args, nil
+
+}
+
 func field_Bug_comments_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	args := map[string]interface{}{}
 	var arg0 *string
@@ -1244,33 +1367,43 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Bug.Author(childComplexity), true
 
-	case "Bug.actors":
-		if e.complexity.Bug.Actors == nil {
+	case "Bug.createdAt":
+		if e.complexity.Bug.CreatedAt == nil {
 			break
 		}
 
-		return e.complexity.Bug.Actors(childComplexity), true
+		return e.complexity.Bug.CreatedAt(childComplexity), true
 
-	case "Bug.participants":
-		if e.complexity.Bug.Participants == nil {
+	case "Bug.lastEdit":
+		if e.complexity.Bug.LastEdit == nil {
 			break
 		}
 
-		return e.complexity.Bug.Participants(childComplexity), true
+		return e.complexity.Bug.LastEdit(childComplexity), true
 
-	case "Bug.createdAt":
-		if e.complexity.Bug.CreatedAt == nil {
+	case "Bug.actors":
+		if e.complexity.Bug.Actors == nil {
 			break
 		}
 
-		return e.complexity.Bug.CreatedAt(childComplexity), true
+		args, err := field_Bug_actors_args(rawArgs)
+		if err != nil {
+			return 0, false
+		}
 
-	case "Bug.lastEdit":
-		if e.complexity.Bug.LastEdit == nil {
+		return e.complexity.Bug.Actors(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
+
+	case "Bug.participants":
+		if e.complexity.Bug.Participants == nil {
 			break
 		}
 
-		return e.complexity.Bug.LastEdit(childComplexity), true
+		args, err := field_Bug_participants_args(rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Bug.Participants(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
 
 	case "Bug.comments":
 		if e.complexity.Bug.Comments == nil {
@@ -2798,33 +2931,33 @@ func (ec *executionContext) _Bug(ctx context.Context, sel ast.SelectionSet, obj
 			if out.Values[i] == graphql.Null {
 				invalid = true
 			}
-		case "actors":
+		case "createdAt":
+			out.Values[i] = ec._Bug_createdAt(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		case "lastEdit":
 			wg.Add(1)
 			go func(i int, field graphql.CollectedField) {
-				out.Values[i] = ec._Bug_actors(ctx, field, obj)
+				out.Values[i] = ec._Bug_lastEdit(ctx, field, obj)
 				if out.Values[i] == graphql.Null {
 					invalid = true
 				}
 				wg.Done()
 			}(i, field)
-		case "participants":
+		case "actors":
 			wg.Add(1)
 			go func(i int, field graphql.CollectedField) {
-				out.Values[i] = ec._Bug_participants(ctx, field, obj)
+				out.Values[i] = ec._Bug_actors(ctx, field, obj)
 				if out.Values[i] == graphql.Null {
 					invalid = true
 				}
 				wg.Done()
 			}(i, field)
-		case "createdAt":
-			out.Values[i] = ec._Bug_createdAt(ctx, field, obj)
-			if out.Values[i] == graphql.Null {
-				invalid = true
-			}
-		case "lastEdit":
+		case "participants":
 			wg.Add(1)
 			go func(i int, field graphql.CollectedField) {
-				out.Values[i] = ec._Bug_lastEdit(ctx, field, obj)
+				out.Values[i] = ec._Bug_participants(ctx, field, obj)
 				if out.Values[i] == graphql.Null {
 					invalid = true
 				}
@@ -3041,7 +3174,7 @@ func (ec *executionContext) _Bug_author(ctx context.Context, field graphql.Colle
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _Bug_actors(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
 	rctx := &graphql.ResolverContext{
@@ -3053,7 +3186,7 @@ func (ec *executionContext) _Bug_actors(ctx context.Context, field graphql.Colle
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Bug().Actors(rctx, obj)
+		return obj.CreatedAt, nil
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -3061,51 +3194,14 @@ func (ec *executionContext) _Bug_actors(ctx context.Context, field graphql.Colle
 		}
 		return graphql.Null
 	}
-	res := resTmp.([]*identity.Interface)
+	res := resTmp.(time.Time)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
-	arr1 := make(graphql.Array, len(res))
-	var wg sync.WaitGroup
-
-	isLen1 := len(res) == 1
-	if !isLen1 {
-		wg.Add(len(res))
-	}
-
-	for idx1 := range res {
-		idx1 := idx1
-		rctx := &graphql.ResolverContext{
-			Index:  &idx1,
-			Result: res[idx1],
-		}
-		ctx := graphql.WithResolverContext(ctx, rctx)
-		f := func(idx1 int) {
-			if !isLen1 {
-				defer wg.Done()
-			}
-			arr1[idx1] = func() graphql.Marshaler {
-
-				if res[idx1] == nil {
-					return graphql.Null
-				}
-
-				return ec._Identity(ctx, field.Selections, res[idx1])
-			}()
-		}
-		if isLen1 {
-			f(idx1)
-		} else {
-			go f(idx1)
-		}
-
-	}
-	wg.Wait()
-	return arr1
+	return graphql.MarshalTime(res)
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _Bug_participants(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
 	rctx := &graphql.ResolverContext{
@@ -3117,7 +3213,7 @@ func (ec *executionContext) _Bug_participants(ctx context.Context, field graphql
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Bug().Participants(rctx, obj)
+		return ec.resolvers.Bug().LastEdit(rctx, obj)
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -3125,63 +3221,32 @@ func (ec *executionContext) _Bug_participants(ctx context.Context, field graphql
 		}
 		return graphql.Null
 	}
-	res := resTmp.([]*identity.Interface)
+	res := resTmp.(time.Time)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
-	arr1 := make(graphql.Array, len(res))
-	var wg sync.WaitGroup
-
-	isLen1 := len(res) == 1
-	if !isLen1 {
-		wg.Add(len(res))
-	}
-
-	for idx1 := range res {
-		idx1 := idx1
-		rctx := &graphql.ResolverContext{
-			Index:  &idx1,
-			Result: res[idx1],
-		}
-		ctx := graphql.WithResolverContext(ctx, rctx)
-		f := func(idx1 int) {
-			if !isLen1 {
-				defer wg.Done()
-			}
-			arr1[idx1] = func() graphql.Marshaler {
-
-				if res[idx1] == nil {
-					return graphql.Null
-				}
-
-				return ec._Identity(ctx, field.Selections, res[idx1])
-			}()
-		}
-		if isLen1 {
-			f(idx1)
-		} else {
-			go f(idx1)
-		}
-
-	}
-	wg.Wait()
-	return arr1
+	return graphql.MarshalTime(res)
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+func (ec *executionContext) _Bug_actors(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := field_Bug_actors_args(rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
 	rctx := &graphql.ResolverContext{
 		Object: "Bug",
-		Args:   nil,
+		Args:   args,
 		Field:  field,
 	}
 	ctx = graphql.WithResolverContext(ctx, rctx)
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return obj.CreatedAt, nil
+		return ec.resolvers.Bug().Actors(rctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int))
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -3189,26 +3254,33 @@ func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.Co
 		}
 		return graphql.Null
 	}
-	res := resTmp.(time.Time)
+	res := resTmp.(models.IdentityConnection)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return graphql.MarshalTime(res)
+
+	return ec._IdentityConnection(ctx, field.Selections, &res)
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+func (ec *executionContext) _Bug_participants(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := field_Bug_participants_args(rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
 	rctx := &graphql.ResolverContext{
 		Object: "Bug",
-		Args:   nil,
+		Args:   args,
 		Field:  field,
 	}
 	ctx = graphql.WithResolverContext(ctx, rctx)
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Bug().LastEdit(rctx, obj)
+		return ec.resolvers.Bug().Participants(rctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int))
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -3216,10 +3288,11 @@ func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.Col
 		}
 		return graphql.Null
 	}
-	res := resTmp.(time.Time)
+	res := resTmp.(models.IdentityConnection)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return graphql.MarshalTime(res)
+
+	return ec._IdentityConnection(ctx, field.Selections, &res)
 }
 
 // nolint: vetshadow
@@ -9802,11 +9875,34 @@ type Bug {
   title: String!
   labels: [Label!]!
   author: Identity!
-  actors: [Identity]!
-  participants: [Identity]!
   createdAt: Time!
   lastEdit: Time!
 
+  """The actors of the bug. Actors are Identity that have interacted with the bug."""
+  actors(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
+  """The participants of the bug. Participants are Identity that have created or
+  added a comment on the bug."""
+  participants(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
   comments(
     """Returns the elements in the list that come after the specified cursor."""
     after: String

graphql/graphql_test.go 🔗

@@ -50,16 +50,6 @@ func TestQueries(t *testing.T) {
                 email
                 avatarUrl
               }
-              actors {
-                name
-                email
-                avatarUrl
-              }
-              participants {
-                name
-                email
-                avatarUrl
-              }
       
               createdAt
               humanId
@@ -67,6 +57,36 @@ func TestQueries(t *testing.T) {
               lastEdit
               status
               title
+
+              actors(first: 10) {
+                pageInfo {
+                  endCursor
+                  hasNextPage
+                  startCursor
+                  hasPreviousPage
+                }
+				nodes {
+				  id
+				  humanId
+				  name
+				  displayName
+				}
+			  }
+
+			  participants(first: 10) {
+                pageInfo {
+                  endCursor
+                  hasNextPage
+                  startCursor
+                  hasPreviousPage
+                }
+				nodes {
+				  id
+				  humanId
+				  name
+				  displayName
+				}
+			  }
       
               comments(first: 2) {
                 pageInfo {
@@ -123,9 +143,12 @@ func TestQueries(t *testing.T) {
       }`
 
 	type Identity struct {
-		Name      string `json:"name"`
-		Email     string `json:"email"`
-		AvatarUrl string `json:"avatarUrl"`
+		Id          string `json:"id"`
+		HumanId     string `json:"humanId"`
+		Name        string `json:"name"`
+		Email       string `json:"email"`
+		AvatarUrl   string `json:"avatarUrl"`
+		DisplayName string `json:"displayName"`
 	}
 
 	var resp struct {
@@ -133,15 +156,23 @@ func TestQueries(t *testing.T) {
 			AllBugs struct {
 				PageInfo models.PageInfo
 				Nodes    []struct {
-					Author       Identity
-					Actors       []Identity
-					Participants []Identity
-					CreatedAt    string `json:"createdAt"`
-					HumanId      string `json:"humanId"`
-					Id           string
-					LastEdit     string `json:"lastEdit"`
-					Status       string
-					Title        string
+					Author    Identity
+					CreatedAt string `json:"createdAt"`
+					HumanId   string `json:"humanId"`
+					Id        string
+					LastEdit  string `json:"lastEdit"`
+					Status    string
+					Title     string
+
+					Actors struct {
+						PageInfo models.PageInfo
+						Nodes    []Identity
+					}
+
+					Participants struct {
+						PageInfo models.PageInfo
+						Nodes    []Identity
+					}
 
 					Comments struct {
 						PageInfo models.PageInfo

graphql/resolvers/bug.go 🔗

@@ -104,22 +104,56 @@ func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time,
 	return obj.LastEditTime(), nil
 }
 
-func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error) {
-	actorsp := make([]*identity.Interface, len(obj.Actors))
+func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.IdentityConnection, error) {
+	input := models.ConnectionInput{
+		Before: before,
+		After:  after,
+		First:  first,
+		Last:   last,
+	}
 
-	for i, actor := range obj.Actors {
-		actorsp[i] = &actor
+	edger := func(actor identity.Interface, offset int) connections.Edge {
+		return models.IdentityEdge{
+			Node:   actor,
+			Cursor: connections.OffsetToCursor(offset),
+		}
+	}
+
+	conMaker := func(edges []models.IdentityEdge, nodes []identity.Interface, info models.PageInfo, totalCount int) (models.IdentityConnection, error) {
+		return models.IdentityConnection{
+			Edges:      edges,
+			Nodes:      nodes,
+			PageInfo:   info,
+			TotalCount: totalCount,
+		}, nil
 	}
 
-	return actorsp, nil
+	return connections.IdentityCon(obj.Actors, edger, conMaker, input)
 }
 
-func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error) {
-	participantsp := make([]*identity.Interface, len(obj.Participants))
+func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.IdentityConnection, error) {
+	input := models.ConnectionInput{
+		Before: before,
+		After:  after,
+		First:  first,
+		Last:   last,
+	}
 
-	for i, participant := range obj.Participants {
-		participantsp[i] = &participant
+	edger := func(participant identity.Interface, offset int) connections.Edge {
+		return models.IdentityEdge{
+			Node:   participant,
+			Cursor: connections.OffsetToCursor(offset),
+		}
+	}
+
+	conMaker := func(edges []models.IdentityEdge, nodes []identity.Interface, info models.PageInfo, totalCount int) (models.IdentityConnection, error) {
+		return models.IdentityConnection{
+			Edges:      edges,
+			Nodes:      nodes,
+			PageInfo:   info,
+			TotalCount: totalCount,
+		}, nil
 	}
 
-	return participantsp, nil
+	return connections.IdentityCon(obj.Participants, edger, conMaker, input)
 }

graphql/schema/bug.graphql 🔗

@@ -36,11 +36,34 @@ type Bug {
   title: String!
   labels: [Label!]!
   author: Identity!
-  actors: [Identity]!
-  participants: [Identity]!
   createdAt: Time!
   lastEdit: Time!
 
+  """The actors of the bug. Actors are Identity that have interacted with the bug."""
+  actors(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
+  """The participants of the bug. Participants are Identity that have created or
+  added a comment on the bug."""
+  participants(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
   comments(
     """Returns the elements in the list that come after the specified cursor."""
     after: String