diff options
26 files changed, 1121 insertions, 130 deletions
@@ -2,7 +2,7 @@ [[projects]] - digest = "1:cc0bbbf01f9f639555f9a9d4d5ff9c72da4c4c4bfe71f02fd318d37498270d80" + digest = "1:e8f6639eaa399c8595b9a2dee175514a9f3842888dc080e2776360dc604150dc" name = "github.com/99designs/gqlgen" packages = [ "cmd", @@ -16,8 +16,8 @@ "internal/imports", ] pruneopts = "UT" - revision = "3a7f37c7e22a8fedce430c4d340ad5c1351198f4" - version = "v0.7.1" + revision = "da1e07f5876c0fb79cbad19006f7135be08590d6" + version = "v0.7.2" [[projects]] branch = "master" diff --git a/graphql/connections/connection_template.go b/graphql/connections/connection_template.go index e20375b8..e98f53eb 100644 --- a/graphql/connections/connection_template.go +++ b/graphql/connections/connection_template.go @@ -7,6 +7,9 @@ import ( "github.com/cheekybits/genny/generic" ) +// Name define the name of the connection +type Name generic.Type + // NodeType define the node type handled by this relay connection type NodeType generic.Type @@ -18,17 +21,17 @@ type ConnectionType generic.Type // NodeTypeEdgeMaker define a function that take a NodeType and an offset and // create an Edge. -type NodeTypeEdgeMaker func(value NodeType, offset int) Edge +type NameEdgeMaker func(value NodeType, offset int) Edge -// NodeTypeConMaker define a function that create a ConnectionType -type NodeTypeConMaker func( +// NameConMaker define a function that create a ConnectionType +type NameConMaker func( edges []EdgeType, nodes []NodeType, info models.PageInfo, totalCount int) (ConnectionType, error) -// NodeTypeCon will paginate a source according to the input of a relay connection -func NodeTypeCon(source []NodeType, edgeMaker NodeTypeEdgeMaker, conMaker NodeTypeConMaker, input models.ConnectionInput) (ConnectionType, error) { +// NameCon will paginate a source according to the input of a relay connection +func NameCon(source []NodeType, edgeMaker NameEdgeMaker, conMaker NameConMaker, input models.ConnectionInput) (ConnectionType, error) { var nodes []NodeType var edges []EdgeType var cursors []string diff --git a/graphql/connections/connections.go b/graphql/connections/connections.go index 68608116..cdc6d07f 100644 --- a/graphql/connections/connections.go +++ b/graphql/connections/connections.go @@ -1,7 +1,8 @@ -//go:generate genny -in=connection_template.go -out=gen_bug.go gen "NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection" -//go:generate genny -in=connection_template.go -out=gen_operation.go gen "NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection" -//go:generate genny -in=connection_template.go -out=gen_comment.go gen "NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection" -//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection" +//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_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" // Package connections implement a generic GraphQL relay connection package connections diff --git a/graphql/connections/gen_bug.go b/graphql/connections/gen_bug.go index 000ff109..ba0a65fa 100644 --- a/graphql/connections/gen_bug.go +++ b/graphql/connections/gen_bug.go @@ -12,17 +12,17 @@ import ( // StringEdgeMaker define a function that take a string and an offset and // create an Edge. -type StringEdgeMaker func(value string, offset int) Edge +type LazyBugEdgeMaker func(value string, offset int) Edge -// StringConMaker define a function that create a models.BugConnection -type StringConMaker func( +// LazyBugConMaker define a function that create a models.BugConnection +type LazyBugConMaker func( edges []LazyBugEdge, nodes []string, info models.PageInfo, totalCount int) (models.BugConnection, error) -// StringCon will paginate a source according to the input of a relay connection -func StringCon(source []string, edgeMaker StringEdgeMaker, conMaker StringConMaker, input models.ConnectionInput) (models.BugConnection, error) { +// LazyBugCon will paginate a source according to the input of a relay connection +func LazyBugCon(source []string, edgeMaker LazyBugEdgeMaker, conMaker LazyBugConMaker, input models.ConnectionInput) (models.BugConnection, error) { var nodes []string var edges []LazyBugEdge var cursors []string diff --git a/graphql/connections/gen_comment.go b/graphql/connections/gen_comment.go index 5c8b0eea..6df21c58 100644 --- a/graphql/connections/gen_comment.go +++ b/graphql/connections/gen_comment.go @@ -13,17 +13,17 @@ import ( // BugCommentEdgeMaker define a function that take a bug.Comment and an offset and // create an Edge. -type BugCommentEdgeMaker func(value bug.Comment, offset int) Edge +type CommentEdgeMaker func(value bug.Comment, offset int) Edge -// BugCommentConMaker define a function that create a models.CommentConnection -type BugCommentConMaker func( +// CommentConMaker define a function that create a models.CommentConnection +type CommentConMaker func( edges []models.CommentEdge, nodes []bug.Comment, info models.PageInfo, totalCount int) (models.CommentConnection, error) -// BugCommentCon will paginate a source according to the input of a relay connection -func BugCommentCon(source []bug.Comment, edgeMaker BugCommentEdgeMaker, conMaker BugCommentConMaker, input models.ConnectionInput) (models.CommentConnection, error) { +// CommentCon will paginate a source according to the input of a relay connection +func CommentCon(source []bug.Comment, edgeMaker CommentEdgeMaker, conMaker CommentConMaker, input models.ConnectionInput) (models.CommentConnection, error) { var nodes []bug.Comment var edges []models.CommentEdge var cursors []string diff --git a/graphql/connections/gen_identity.go b/graphql/connections/gen_identity.go new file mode 100644 index 00000000..28501171 --- /dev/null +++ b/graphql/connections/gen_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) +} diff --git a/graphql/connections/gen_operation.go b/graphql/connections/gen_operation.go index 9a1a53b7..3fbd724f 100644 --- a/graphql/connections/gen_operation.go +++ b/graphql/connections/gen_operation.go @@ -13,17 +13,17 @@ import ( // BugOperationEdgeMaker define a function that take a bug.Operation and an offset and // create an Edge. -type BugOperationEdgeMaker func(value bug.Operation, offset int) Edge +type OperationEdgeMaker func(value bug.Operation, offset int) Edge -// BugOperationConMaker define a function that create a models.OperationConnection -type BugOperationConMaker func( +// OperationConMaker define a function that create a models.OperationConnection +type OperationConMaker func( edges []models.OperationEdge, nodes []bug.Operation, info models.PageInfo, totalCount int) (models.OperationConnection, error) -// BugOperationCon will paginate a source according to the input of a relay connection -func BugOperationCon(source []bug.Operation, edgeMaker BugOperationEdgeMaker, conMaker BugOperationConMaker, input models.ConnectionInput) (models.OperationConnection, error) { +// OperationCon will paginate a source according to the input of a relay connection +func OperationCon(source []bug.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (models.OperationConnection, error) { var nodes []bug.Operation var edges []models.OperationEdge var cursors []string diff --git a/graphql/connections/gen_timeline.go b/graphql/connections/gen_timeline.go index 4d417a45..1a4b2fe5 100644 --- a/graphql/connections/gen_timeline.go +++ b/graphql/connections/gen_timeline.go @@ -13,17 +13,17 @@ import ( // BugTimelineItemEdgeMaker define a function that take a bug.TimelineItem and an offset and // create an Edge. -type BugTimelineItemEdgeMaker func(value bug.TimelineItem, offset int) Edge +type TimelineItemEdgeMaker func(value bug.TimelineItem, offset int) Edge -// BugTimelineItemConMaker define a function that create a models.TimelineItemConnection -type BugTimelineItemConMaker func( +// TimelineItemConMaker define a function that create a models.TimelineItemConnection +type TimelineItemConMaker func( edges []models.TimelineItemEdge, nodes []bug.TimelineItem, info models.PageInfo, totalCount int) (models.TimelineItemConnection, error) -// BugTimelineItemCon will paginate a source according to the input of a relay connection -func BugTimelineItemCon(source []bug.TimelineItem, edgeMaker BugTimelineItemEdgeMaker, conMaker BugTimelineItemConMaker, input models.ConnectionInput) (models.TimelineItemConnection, error) { +// TimelineItemCon will paginate a source according to the input of a relay connection +func TimelineItemCon(source []bug.TimelineItem, edgeMaker TimelineItemEdgeMaker, conMaker TimelineItemConMaker, input models.ConnectionInput) (models.TimelineItemConnection, error) { var nodes []bug.TimelineItem var edges []models.TimelineItemEdge var cursors []string diff --git a/graphql/connections/lazy_identity.go b/graphql/connections/lazy_identity.go new file mode 100644 index 00000000..34ba579a --- /dev/null +++ b/graphql/connections/lazy_identity.go @@ -0,0 +1,12 @@ +package connections + +// LazyIdentityEdge is a special relay edge used to implement a lazy loading connection +type LazyIdentityEdge struct { + Id string + Cursor string +} + +// GetCursor return the cursor of a LazyIdentityEdge +func (lbe LazyIdentityEdge) GetCursor() string { + return lbe.Cursor +} diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go index 548e808a..06ebecc7 100644 --- a/graphql/graph/gen_graph.go +++ b/graphql/graph/gen_graph.go @@ -161,6 +161,7 @@ type ComplexityRoot struct { Identity struct { Id func(childComplexity int) int + HumanId func(childComplexity int) int Name func(childComplexity int) int Email func(childComplexity int) int Login func(childComplexity int) int @@ -169,6 +170,18 @@ type ComplexityRoot struct { IsProtected func(childComplexity int) int } + IdentityConnection struct { + Edges func(childComplexity int) int + Nodes func(childComplexity int) int + PageInfo func(childComplexity int) int + TotalCount func(childComplexity int) int + } + + IdentityEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int + } + LabelChangeOperation struct { Hash func(childComplexity int) int Author func(childComplexity int) int @@ -220,8 +233,11 @@ type ComplexityRoot struct { } Repository struct { - AllBugs func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int - Bug func(childComplexity int, prefix string) int + AllBugs func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int + Bug func(childComplexity int, prefix string) int + AllIdentities func(childComplexity int, after *string, before *string, first *int, last *int) int + Identity func(childComplexity int, prefix string) int + UserIdentity func(childComplexity int) int } SetStatusOperation struct { @@ -297,6 +313,7 @@ type EditCommentOperationResolver interface { } type IdentityResolver interface { ID(ctx context.Context, obj *identity.Interface) (string, error) + HumanID(ctx context.Context, obj *identity.Interface) (string, error) Name(ctx context.Context, obj *identity.Interface) (*string, error) Email(ctx context.Context, obj *identity.Interface) (*string, error) Login(ctx context.Context, obj *identity.Interface) (*string, error) @@ -326,6 +343,9 @@ type QueryResolver interface { type RepositoryResolver interface { AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, query *string) (models.BugConnection, error) Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error) + AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (models.IdentityConnection, error) + Identity(ctx context.Context, obj *models.Repository, prefix string) (*identity.Interface, error) + UserIdentity(ctx context.Context, obj *models.Repository) (*identity.Interface, error) } type SetStatusOperationResolver interface { Date(ctx context.Context, obj *bug.SetStatusOperation) (time.Time, error) @@ -959,6 +979,83 @@ func field_Repository_bug_args(rawArgs map[string]interface{}) (map[string]inter } +func field_Repository_allIdentities_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_Repository_identity_args(rawArgs map[string]interface{}) (map[string]interface{}, error) { + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["prefix"]; ok { + var err error + arg0, err = graphql.UnmarshalString(tmp) + if err != nil { + return nil, err + } + } + args["prefix"] = arg0 + return args, nil + +} + func field___Type_fields_args(rawArgs map[string]interface{}) (map[string]interface{}, error) { args := map[string]interface{}{} var arg0 bool @@ -1465,6 +1562,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Identity.Id(childComplexity), true + case "Identity.humanId": + if e.complexity.Identity.HumanId == nil { + break + } + + return e.complexity.Identity.HumanId(childComplexity), true + case "Identity.name": if e.complexity.Identity.Name == nil { break @@ -1507,6 +1611,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Identity.IsProtected(childComplexity), true + case "IdentityConnection.edges": + if e.complexity.IdentityConnection.Edges == nil { + break + } + + return e.complexity.IdentityConnection.Edges(childComplexity), true + + case "IdentityConnection.nodes": + if e.complexity.IdentityConnection.Nodes == nil { + break + } + + return e.complexity.IdentityConnection.Nodes(childComplexity), true + + case "IdentityConnection.pageInfo": + if e.complexity.IdentityConnection.PageInfo == nil { + break + } + + return e.complexity.IdentityConnection.PageInfo(childComplexity), true + + case "IdentityConnection.totalCount": + if e.complexity.IdentityConnection.TotalCount == nil { + break + } + + return e.complexity.IdentityConnection.TotalCount(childComplexity), true + + case "IdentityEdge.cursor": + if e.complexity.IdentityEdge.Cursor == nil { + break + } + + return e.complexity.IdentityEdge.Cursor(childComplexity), true + + case "IdentityEdge.node": + if e.complexity.IdentityEdge.Node == nil { + break + } + + return e.complexity.IdentityEdge.Node(childComplexity), true + case "LabelChangeOperation.hash": if e.complexity.LabelChangeOperation.Hash == nil { break @@ -1774,6 +1920,37 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Repository.Bug(childComplexity, args["prefix"].(string)), true + case "Repository.allIdentities": + if e.complexity.Repository.AllIdentities == nil { + break + } + + args, err := field_Repository_allIdentities_args(rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Repository.AllIdentities(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true + + case "Repository.identity": + if e.complexity.Repository.Identity == nil { + break + } + + args, err := field_Repository_identity_args(rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Repository.Identity(childComplexity, args["prefix"].(string)), true + + case "Repository.userIdentity": + if e.complexity.Repository.UserIdentity == nil { + break + } + + return e.complexity.Repository.UserIdentity(childComplexity), true + case "SetStatusOperation.hash": if e.complexity.SetStatusOperation.Hash == nil { break @@ -4680,6 +4857,15 @@ func (ec *executionContext) _Identity(ctx context.Context, sel ast.SelectionSet, } wg.Done() }(i, field) + case "humanId": + wg.Add(1) + go func(i int, field graphql.CollectedField) { + out.Values[i] = ec._Identity_humanId(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + wg.Done() + }(i, field) case "name": wg.Add(1) go func(i int, field graphql.CollectedField) { @@ -4761,6 +4947,33 @@ func (ec *executionContext) _Identity_id(ctx context.Context, field graphql.Coll } // nolint: vetshadow +func (ec *executionContext) _Identity_humanId(ctx context.Context, field graphql.CollectedField, obj *identity.Interface) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "Identity", + Args: nil, + 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.Identity().HumanID(rctx, obj) + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return graphql.MarshalString(res) +} + +// nolint: vetshadow func (ec *executionContext) _Identity_name(ctx context.Context, field graphql.CollectedField, obj *identity.Interface) graphql.Marshaler { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { ec.Tracer.EndFieldExecution(ctx) }() @@ -4926,6 +5139,316 @@ func (ec *executionContext) _Identity_isProtected(ctx context.Context, field gra return graphql.MarshalBoolean(res) } +var identityConnectionImplementors = []string{"IdentityConnection"} + +// nolint: gocyclo, errcheck, gas, goconst +func (ec *executionContext) _IdentityConnection(ctx context.Context, sel ast.SelectionSet, obj *models.IdentityConnection) graphql.Marshaler { + fields := graphql.CollectFields(ctx, sel, identityConnectionImplementors) + + out := graphql.NewOrderedMap(len(fields)) + invalid := false + for i, field := range fields { + out.Keys[i] = field.Alias + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("IdentityConnection") + case "edges": + out.Values[i] = ec._IdentityConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + case "nodes": + out.Values[i] = ec._IdentityConnection_nodes(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + case "pageInfo": + out.Values[i] = ec._IdentityConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + case "totalCount": + out.Values[i] = ec._IdentityConnection_totalCount(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + + if invalid { + return graphql.Null + } + return out +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityConnection_edges(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityConnection", + Args: nil, + 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.Edges, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]models.IdentityEdge) + 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 { + + return ec._IdentityEdge(ctx, field.Selections, &res[idx1]) + }() + } + if isLen1 { + f(idx1) + } else { + go f(idx1) + } + + } + wg.Wait() + return arr1 +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityConnection", + Args: nil, + 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.Nodes, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]identity.Interface) + 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 { + + return ec._Identity(ctx, field.Selections, &res[idx1]) + }() + } + if isLen1 { + f(idx1) + } else { + go f(idx1) + } + + } + wg.Wait() + return arr1 +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityConnection", + Args: nil, + 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.PageInfo, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(models.PageInfo) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + + return ec._PageInfo(ctx, field.Selections, &res) +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityConnection", + Args: nil, + 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.TotalCount, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return graphql.MarshalInt(res) +} + +var identityEdgeImplementors = []string{"IdentityEdge"} + +// nolint: gocyclo, errcheck, gas, goconst +func (ec *executionContext) _IdentityEdge(ctx context.Context, sel ast.SelectionSet, obj *models.IdentityEdge) graphql.Marshaler { + fields := graphql.CollectFields(ctx, sel, identityEdgeImplementors) + + out := graphql.NewOrderedMap(len(fields)) + invalid := false + for i, field := range fields { + out.Keys[i] = field.Alias + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("IdentityEdge") + case "cursor": + out.Values[i] = ec._IdentityEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + case "node": + out.Values[i] = ec._IdentityEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + + if invalid { + return graphql.Null + } + return out +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEdge) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityEdge", + Args: nil, + 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.Cursor, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return graphql.MarshalString(res) +} + +// nolint: vetshadow +func (ec *executionContext) _IdentityEdge_node(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEdge) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "IdentityEdge", + Args: nil, + 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.Node, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(identity.Interface) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + + return ec._Identity(ctx, field.Selections, &res) +} + var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"} // nolint: gocyclo, errcheck, gas, goconst @@ -6313,6 +6836,27 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe out.Values[i] = ec._Repository_bug(ctx, field, obj) wg.Done() }(i, field) + case "allIdentities": + wg.Add(1) + go func(i int, field graphql.CollectedField) { + out.Values[i] = ec._Repository_allIdentities(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + wg.Done() + }(i, field) + case "identity": + wg.Add(1) + go func(i int, field graphql.CollectedField) { + out.Values[i] = ec._Repository_identity(ctx, field, obj) + wg.Done() + }(i, field) + case "userIdentity": + wg.Add(1) + go func(i int, field graphql.CollectedField) { + out.Values[i] = ec._Repository_userIdentity(ctx, field, obj) + wg.Done() + }(i, field) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -6393,6 +6937,104 @@ func (ec *executionContext) _Repository_bug(ctx context.Context, field graphql.C return ec._Bug(ctx, field.Selections, res) } +// nolint: vetshadow +func (ec *executionContext) _Repository_allIdentities(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rawArgs := field.ArgumentMap(ec.Variables) + args, err := field_Repository_allIdentities_args(rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx := &graphql.ResolverContext{ + Object: "Repository", + 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.Repository().AllIdentities(rctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)) + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(models.IdentityConnection) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + + return ec._IdentityConnection(ctx, field.Selections, &res) +} + +// nolint: vetshadow +func (ec *executionContext) _Repository_identity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rawArgs := field.ArgumentMap(ec.Variables) + args, err := field_Repository_identity_args(rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx := &graphql.ResolverContext{ + Object: "Repository", + 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.Repository().Identity(rctx, obj, args["prefix"].(string)) + }) + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*identity.Interface) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + + if res == nil { + return graphql.Null + } + + return ec._Identity(ctx, field.Selections, res) +} + +// nolint: vetshadow +func (ec *executionContext) _Repository_userIdentity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "Repository", + Args: nil, + 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.Repository().UserIdentity(rctx, obj) + }) + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*identity.Interface) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + + if res == nil { + return graphql.Null + } + + return ec._Identity(ctx, field.Selections, res) +} + var setStatusOperationImplementors = []string{"SetStatusOperation", "Operation", "Authored"} // nolint: gocyclo, errcheck, gas, goconst @@ -8987,7 +9629,9 @@ enum Status { } type Bug { + """The identifier for this bug""" id: String! + """The human version (truncated) identifier for this bug""" humanId: String! status: Status! title: String! @@ -9049,26 +9693,13 @@ type BugEdge { node: Bug! } -type Repository { - allBugs( - """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 - """A query to select and order bugs""" - query: String - ): BugConnection! - bug(prefix: String!): Bug -} `}, &ast.Source{Name: "schema/identity.graphql", Input: `"""Represents an identity""" type Identity { """The identifier for this identity""" id: String! + """The human version (truncated) identifier for this identity""" + humanId: String! """The name of the person, if known.""" name: String """The email of the person, if known.""" @@ -9082,6 +9713,18 @@ type Identity { """isProtected is true if the chain of git commits started to be signed. If that's the case, only signed commit with a valid key for this identity can be added.""" isProtected: Boolean! +} + +type IdentityConnection { + edges: [IdentityEdge!]! + nodes: [Identity!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type IdentityEdge { + cursor: String! + node: Identity! }`}, &ast.Source{Name: "schema/operations.graphql", Input: `"""An operation applied to a bug.""" interface Operation { @@ -9184,29 +9827,42 @@ type LabelChangeOperation implements Operation & Authored { removed: [Label!]! } `}, - &ast.Source{Name: "schema/root.graphql", Input: `scalar Time -scalar Label -scalar Hash - -"""Information about pagination in a connection.""" -type PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! - """When paginating backwards, the cursor to continue.""" - startCursor: String! - """When paginating forwards, the cursor to continue.""" - endCursor: String! -} - -"""An object that has an author.""" -interface Authored { - """The author of this object.""" - author: Identity! -} - -type Query { + &ast.Source{Name: "schema/repository.graphql", Input: ` +type Repository { + """All the bugs""" + allBugs( + """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 + """A query to select and order bugs""" + query: String + ): BugConnection! + + bug(prefix: String!): Bug + + """All the identities""" + allIdentities( + """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! + + identity(prefix: String!):Identity + + """The identity created or selected by the user as its own""" + userIdentity:Identity +}`}, + &ast.Source{Name: "schema/root.graphql", Input: `type Query { defaultRepository: Repository repository(id: String!): Repository } @@ -9310,4 +9966,25 @@ type SetTitleTimelineItem implements TimelineItem { was: String! } `}, + &ast.Source{Name: "schema/types.graphql", Input: `scalar Time +scalar Label +scalar Hash + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + """When paginating backwards, the cursor to continue.""" + startCursor: String! + """When paginating forwards, the cursor to continue.""" + endCursor: String! +} + +"""An object that has an author.""" +interface Authored { + """The author of this object.""" + author: Identity! +}`}, ) diff --git a/graphql/models/edges.go b/graphql/models/edges.go index 1dc42583..4bf10fd3 100644 --- a/graphql/models/edges.go +++ b/graphql/models/edges.go @@ -19,3 +19,8 @@ func (e CommentEdge) GetCursor() string { func (e TimelineItemEdge) GetCursor() string { return e.Cursor } + +// GetCursor return the cursor entry of an edge +func (e IdentityEdge) GetCursor() string { + return e.Cursor +} diff --git a/graphql/models/gen_models.go b/graphql/models/gen_models.go index 71a6b78b..172fe033 100644 --- a/graphql/models/gen_models.go +++ b/graphql/models/gen_models.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/identity" ) // An object that has an author. @@ -41,6 +42,18 @@ type CommentEdge struct { Node bug.Comment `json:"node"` } +type IdentityConnection struct { + Edges []IdentityEdge `json:"edges"` + Nodes []identity.Interface `json:"nodes"` + PageInfo PageInfo `json:"pageInfo"` + TotalCount int `json:"totalCount"` +} + +type IdentityEdge struct { + Cursor string `json:"cursor"` + Node identity.Interface `json:"node"` +} + // The connection type for an Operation type OperationConnection struct { Edges []OperationEdge `json:"edges"` diff --git a/graphql/resolvers/bug.go b/graphql/resolvers/bug.go index 76eed55d..7af04934 100644 --- a/graphql/resolvers/bug.go +++ b/graphql/resolvers/bug.go @@ -6,9 +6,12 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/graphql/connections" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" ) +var _ graph.BugResolver = &bugResolver{} + type bugResolver struct{} func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) { @@ -39,7 +42,7 @@ func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *strin }, nil } - return connections.BugCommentCon(obj.Comments, edger, conMaker, input) + return connections.CommentCon(obj.Comments, edger, conMaker, input) } func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error) { @@ -66,7 +69,7 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str }, nil } - return connections.BugOperationCon(obj.Operations, edger, conMaker, input) + return connections.OperationCon(obj.Operations, edger, conMaker, input) } func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.TimelineItemConnection, error) { @@ -93,7 +96,7 @@ func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *strin }, nil } - return connections.BugTimelineItemCon(obj.Timeline, edger, conMaker, input) + return connections.TimelineItemCon(obj.Timeline, edger, conMaker, input) } func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) { diff --git a/graphql/resolvers/identity.go b/graphql/resolvers/identity.go index d4f9bba2..05f7207e 100644 --- a/graphql/resolvers/identity.go +++ b/graphql/resolvers/identity.go @@ -3,15 +3,22 @@ package resolvers import ( "context" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/identity" ) +var _ graph.IdentityResolver = &identityResolver{} + type identityResolver struct{} func (identityResolver) ID(ctx context.Context, obj *identity.Interface) (string, error) { return (*obj).Id(), nil } +func (identityResolver) HumanID(ctx context.Context, obj *identity.Interface) (string, error) { + return (*obj).HumanId(), nil +} + func (identityResolver) Name(ctx context.Context, obj *identity.Interface) (*string, error) { return nilIfEmpty((*obj).Name()) } diff --git a/graphql/resolvers/mutation.go b/graphql/resolvers/mutation.go index be6956af..73d39da8 100644 --- a/graphql/resolvers/mutation.go +++ b/graphql/resolvers/mutation.go @@ -5,9 +5,12 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/util/git" ) +var _ graph.MutationResolver = &mutationResolver{} + type mutationResolver struct { cache *cache.MultiRepoCache } diff --git a/graphql/resolvers/query.go b/graphql/resolvers/query.go index b5763b72..80b5a896 100644 --- a/graphql/resolvers/query.go +++ b/graphql/resolvers/query.go @@ -4,9 +4,12 @@ import ( "context" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" ) +var _ graph.QueryResolver = &rootQueryResolver{} + type rootQueryResolver struct { cache *cache.MultiRepoCache } diff --git a/graphql/resolvers/repo.go b/graphql/resolvers/repo.go index c696ff34..9003fbf9 100644 --- a/graphql/resolvers/repo.go +++ b/graphql/resolvers/repo.go @@ -6,9 +6,13 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/graphql/connections" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" + "github.com/MichaelMure/git-bug/identity" ) +var _ graph.RepositoryResolver = &repoResolver{} + type repoResolver struct{} func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (models.BugConnection, error) { @@ -70,7 +74,7 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after * }, nil } - return connections.StringCon(source, edger, conMaker, input) + return connections.LazyBugCon(source, edger, conMaker, input) } func (repoResolver) Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error) { @@ -82,3 +86,78 @@ func (repoResolver) Bug(ctx context.Context, obj *models.Repository, prefix stri return b.Snapshot(), nil } + +func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (models.IdentityConnection, error) { + input := models.ConnectionInput{ + Before: before, + After: after, + First: first, + Last: last, + } + + // Simply pass a []string with the ids to the pagination algorithm + source := obj.Repo.AllIdentityIds() + + // The edger create a custom edge holding just the id + edger := func(id string, offset int) connections.Edge { + return connections.LazyIdentityEdge{ + Id: id, + Cursor: connections.OffsetToCursor(offset), + } + } + + // The conMaker will finally load and compile identities from git to replace the selected edges + conMaker := func(lazyIdentityEdges []connections.LazyIdentityEdge, lazyNode []string, info models.PageInfo, totalCount int) (models.IdentityConnection, error) { + edges := make([]models.IdentityEdge, len(lazyIdentityEdges)) + nodes := make([]identity.Interface, len(lazyIdentityEdges)) + + for k, lazyIdentityEdge := range lazyIdentityEdges { + i, err := obj.Repo.ResolveIdentity(lazyIdentityEdge.Id) + + if err != nil { + return models.IdentityConnection{}, err + } + + ii := identity.Interface(i.Identity) + + edges[k] = models.IdentityEdge{ + Cursor: lazyIdentityEdge.Cursor, + Node: ii, + } + nodes[k] = ii + } + + return models.IdentityConnection{ + Edges: edges, + Nodes: nodes, + PageInfo: info, + TotalCount: totalCount, + }, nil + } + + return connections.LazyIdentityCon(source, edger, conMaker, input) +} + +func (repoResolver) Identity(ctx context.Context, obj *models.Repository, prefix string) (*identity.Interface, error) { + i, err := obj.Repo.ResolveIdentityPrefix(prefix) + + if err != nil { + return nil, err + } + + ii := identity.Interface(i.Identity) + + return &ii, nil +} + +func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (*identity.Interface, error) { + i, err := obj.Repo.GetUserIdentity() + + if err != nil { + return nil, err + } + + ii := identity.Interface(i.Identity) + + return &ii, nil +} diff --git a/graphql/resolvers/root.go b/graphql/resolvers/root.go index cfdfe346..7414a097 100644 --- a/graphql/resolvers/root.go +++ b/graphql/resolvers/root.go @@ -6,6 +6,8 @@ import ( "github.com/MichaelMure/git-bug/graphql/graph" ) +var _ graph.ResolverRoot = &RootResolver{} + type RootResolver struct { cache.MultiRepoCache } diff --git a/graphql/schema/bug.graphql b/graphql/schema/bug.graphql index 7e1c57b5..a1a61e7e 100644 --- a/graphql/schema/bug.graphql +++ b/graphql/schema/bug.graphql @@ -28,7 +28,9 @@ enum Status { } type Bug { + """The identifier for this bug""" id: String! + """The human version (truncated) identifier for this bug""" humanId: String! status: Status! title: String! @@ -90,18 +92,3 @@ type BugEdge { node: Bug! } -type Repository { - allBugs( - """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 - """A query to select and order bugs""" - query: String - ): BugConnection! - bug(prefix: String!): Bug -} diff --git a/graphql/schema/identity.graphql b/graphql/schema/identity.graphql index 18666f76..6872ecb9 100644 --- a/graphql/schema/identity.graphql +++ b/graphql/schema/identity.graphql @@ -2,6 +2,8 @@ type Identity { """The identifier for this identity""" id: String! + """The human version (truncated) identifier for this identity""" + humanId: String! """The name of the person, if known.""" name: String """The email of the person, if known.""" @@ -16,3 +18,15 @@ type Identity { If that's the case, only signed commit with a valid key for this identity can be added.""" isProtected: Boolean! } + +type IdentityConnection { + edges: [IdentityEdge!]! + nodes: [Identity!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type IdentityEdge { + cursor: String! + node: Identity! +}
\ No newline at end of file diff --git a/graphql/schema/repository.graphql b/graphql/schema/repository.graphql new file mode 100644 index 00000000..a4f4b424 --- /dev/null +++ b/graphql/schema/repository.graphql @@ -0,0 +1,35 @@ + +type Repository { + """All the bugs""" + allBugs( + """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 + """A query to select and order bugs""" + query: String + ): BugConnection! + + bug(prefix: String!): Bug + + """All the identities""" + allIdentities( + """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! + + identity(prefix: String!):Identity + + """The identity created or selected by the user as its own""" + userIdentity:Identity +}
\ No newline at end of file diff --git a/graphql/schema/root.graphql b/graphql/schema/root.graphql index 7b43366f..2d430aa5 100644 --- a/graphql/schema/root.graphql +++ b/graphql/schema/root.graphql @@ -1,25 +1,3 @@ -scalar Time -scalar Label -scalar Hash - -"""Information about pagination in a connection.""" -type PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! - """When paginating backwards, the cursor to continue.""" - startCursor: String! - """When paginating forwards, the cursor to continue.""" - endCursor: String! -} - -"""An object that has an author.""" -interface Authored { - """The author of this object.""" - author: Identity! -} - type Query { defaultRepository: Repository repository(id: String!): Repository diff --git a/graphql/schema/types.graphql b/graphql/schema/types.graphql new file mode 100644 index 00000000..2eaa07a2 --- /dev/null +++ b/graphql/schema/types.graphql @@ -0,0 +1,21 @@ +scalar Time +scalar Label +scalar Hash + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + """When paginating backwards, the cursor to continue.""" + startCursor: String! + """When paginating forwards, the cursor to continue.""" + endCursor: String! +} + +"""An object that has an author.""" +interface Authored { + """The author of this object.""" + author: Identity! +}
\ No newline at end of file diff --git a/vendor/github.com/99designs/gqlgen/graphql/version.go b/vendor/github.com/99designs/gqlgen/graphql/version.go index 8cf3c9ba..490ff3ff 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/version.go +++ b/vendor/github.com/99designs/gqlgen/graphql/version.go @@ -1,3 +1,3 @@ package graphql -const Version = "dev" +const Version = "v0.7.2" diff --git a/vendor/github.com/99designs/gqlgen/handler/graphql.go b/vendor/github.com/99designs/gqlgen/handler/graphql.go index eb8880de..7c5f70cf 100644 --- a/vendor/github.com/99designs/gqlgen/handler/graphql.go +++ b/vendor/github.com/99designs/gqlgen/handler/graphql.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "strings" + "time" "github.com/99designs/gqlgen/complexity" "github.com/99designs/gqlgen/graphql" @@ -25,15 +26,16 @@ type params struct { } type Config struct { - cacheSize int - upgrader websocket.Upgrader - recover graphql.RecoverFunc - errorPresenter graphql.ErrorPresenterFunc - resolverHook graphql.FieldMiddleware - requestHook graphql.RequestMiddleware - tracer graphql.Tracer - complexityLimit int - disableIntrospection bool + cacheSize int + upgrader websocket.Upgrader + recover graphql.RecoverFunc + errorPresenter graphql.ErrorPresenterFunc + resolverHook graphql.FieldMiddleware + requestHook graphql.RequestMiddleware + tracer graphql.Tracer + complexityLimit int + disableIntrospection bool + connectionKeepAlivePingInterval time.Duration } func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext { @@ -243,6 +245,14 @@ func CacheSize(size int) Option { const DefaultCacheSize = 1000 +// WebsocketKeepAliveDuration allows you to reconfigure the keepAlive behavior. +// By default, keep-alive is disabled. +func WebsocketKeepAliveDuration(duration time.Duration) Option { + return func(cfg *Config) { + cfg.connectionKeepAlivePingInterval = duration + } +} + func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc { cfg := &Config{ cacheSize: DefaultCacheSize, diff --git a/vendor/github.com/99designs/gqlgen/handler/websocket.go b/vendor/github.com/99designs/gqlgen/handler/websocket.go index dae262bd..09800c17 100644 --- a/vendor/github.com/99designs/gqlgen/handler/websocket.go +++ b/vendor/github.com/99designs/gqlgen/handler/websocket.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "sync" + "time" "github.com/99designs/gqlgen/graphql" "github.com/gorilla/websocket" @@ -27,7 +28,7 @@ const ( dataMsg = "data" // Server -> Client errorMsg = "error" // Server -> Client completeMsg = "complete" // Server -> Client - //connectionKeepAliveMsg = "ka" // Server -> Client TODO: keepalives + connectionKeepAliveMsg = "ka" // Server -> Client ) type operationMessage struct { @@ -37,12 +38,13 @@ type operationMessage struct { } type wsConnection struct { - ctx context.Context - conn *websocket.Conn - exec graphql.ExecutableSchema - active map[string]context.CancelFunc - mu sync.Mutex - cfg *Config + ctx context.Context + conn *websocket.Conn + exec graphql.ExecutableSchema + active map[string]context.CancelFunc + mu sync.Mutex + cfg *Config + keepAliveTicker *time.Ticker initPayload InitPayload } @@ -109,6 +111,20 @@ func (c *wsConnection) write(msg *operationMessage) { } func (c *wsConnection) run() { + // We create a cancellation that will shutdown the keep-alive when we leave + // this function. + ctx, cancel := context.WithCancel(c.ctx) + defer cancel() + + // Create a timer that will fire every interval to keep the connection alive. + if c.cfg.connectionKeepAlivePingInterval != 0 { + c.mu.Lock() + c.keepAliveTicker = time.NewTicker(c.cfg.connectionKeepAlivePingInterval) + c.mu.Unlock() + + go c.keepAlive(ctx) + } + for { message := c.readOp() if message == nil { @@ -141,6 +157,18 @@ func (c *wsConnection) run() { } } +func (c *wsConnection) keepAlive(ctx context.Context) { + for { + select { + case <-ctx.Done(): + c.keepAliveTicker.Stop() + return + case <-c.keepAliveTicker.C: + c.write(&operationMessage{Type: connectionKeepAliveMsg}) + } + } +} + func (c *wsConnection) subscribe(message *operationMessage) bool { var reqParams params if err := jsonDecode(bytes.NewReader(message.Payload), &reqParams); err != nil { |