diff --git a/api/graphql/connections/edges.go b/api/graphql/connections/edges.go index bb8f04ec416e5dd51a8751a1fecd573d2bcc9611..f26b696b63662ce690784586fff32a8c13204293 100644 --- a/api/graphql/connections/edges.go +++ b/api/graphql/connections/edges.go @@ -2,6 +2,17 @@ package connections import "github.com/git-bug/git-bug/entity" +// LazyBoardEdge is a special relay edge used to implement a lazy loading connection +type LazyBoardEdge struct { + Id entity.Id + Cursor string +} + +// GetCursor return the cursor of a LazyBoardEdge +func (lbe LazyBoardEdge) GetCursor() string { + return lbe.Cursor +} + // LazyBugEdge is a special relay edge used to implement a lazy loading connection type LazyBugEdge struct { Id entity.Id diff --git a/api/graphql/graph/board.generated.go b/api/graphql/graph/board.generated.go index a30f352808bece915d8153d584042bfa2d97d678..d0fff8d7e00f3053ae247fafb28a836b4208fcb8 100644 --- a/api/graphql/graph/board.generated.go +++ b/api/graphql/graph/board.generated.go @@ -1480,6 +1480,20 @@ func (ec *executionContext) marshalNBoard2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbu return ret } +func (ec *executionContext) marshalNBoardConnection2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardConnection(ctx context.Context, sel ast.SelectionSet, v models.BoardConnection) graphql.Marshaler { + return ec._BoardConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNBoardConnection2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardConnection(ctx context.Context, sel ast.SelectionSet, v *models.BoardConnection) 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._BoardConnection(ctx, sel, v) +} + func (ec *executionContext) marshalNBoardEdge2ᚕᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.BoardEdge) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -1534,4 +1548,11 @@ func (ec *executionContext) marshalNBoardEdge2ᚖgithubᚗcomᚋgitᚑbugᚋgit return ec._BoardEdge(ctx, sel, v) } +func (ec *executionContext) marshalOBoard2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardWrapper(ctx context.Context, sel ast.SelectionSet, v models.BoardWrapper) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Board(ctx, sel, v) +} + // endregion ***************************** type.gotpl ***************************** diff --git a/api/graphql/graph/repository.generated.go b/api/graphql/graph/repository.generated.go index a592abf671595292052c956084764fd352c3ef93..338ac2ca791a3739e55a2703cc0b8ac91ff1775f 100644 --- a/api/graphql/graph/repository.generated.go +++ b/api/graphql/graph/repository.generated.go @@ -18,6 +18,8 @@ import ( type RepositoryResolver interface { Name(ctx context.Context, obj *models.Repository) (*string, error) + AllBoards(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, query *string) (*models.BoardConnection, error) + Board(ctx context.Context, obj *models.Repository, prefix string) (models.BoardWrapper, error) 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) (models.BugWrapper, error) AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) @@ -30,6 +32,126 @@ type RepositoryResolver interface { // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Repository_allBoards_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Repository_allBoards_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg0 + arg1, err := ec.field_Repository_allBoards_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg1 + arg2, err := ec.field_Repository_allBoards_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg2 + arg3, err := ec.field_Repository_allBoards_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg3 + arg4, err := ec.field_Repository_allBoards_argsQuery(ctx, rawArgs) + if err != nil { + return nil, err + } + args["query"] = arg4 + return args, nil +} +func (ec *executionContext) field_Repository_allBoards_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*string, error) { + if _, ok := rawArgs["after"]; !ok { + var zeroVal *string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOString2ᚖstring(ctx, tmp) + } + + var zeroVal *string + return zeroVal, nil +} + +func (ec *executionContext) field_Repository_allBoards_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*string, error) { + if _, ok := rawArgs["before"]; !ok { + var zeroVal *string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOString2ᚖstring(ctx, tmp) + } + + var zeroVal *string + return zeroVal, nil +} + +func (ec *executionContext) field_Repository_allBoards_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + if _, ok := rawArgs["first"]; !ok { + var zeroVal *int + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Repository_allBoards_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + if _, ok := rawArgs["last"]; !ok { + var zeroVal *int + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Repository_allBoards_argsQuery( + ctx context.Context, + rawArgs map[string]any, +) (*string, error) { + if _, ok := rawArgs["query"]; !ok { + var zeroVal *string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("query")) + if tmp, ok := rawArgs["query"]; ok { + return ec.unmarshalOString2ᚖstring(ctx, tmp) + } + + var zeroVal *string + return zeroVal, nil +} + func (ec *executionContext) field_Repository_allBugs_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -247,6 +369,34 @@ func (ec *executionContext) field_Repository_allIdentities_argsLast( return zeroVal, nil } +func (ec *executionContext) field_Repository_board_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Repository_board_argsPrefix(ctx, rawArgs) + if err != nil { + return nil, err + } + args["prefix"] = arg0 + return args, nil +} +func (ec *executionContext) field_Repository_board_argsPrefix( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + if _, ok := rawArgs["prefix"]; !ok { + var zeroVal string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("prefix")) + if tmp, ok := rawArgs["prefix"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + func (ec *executionContext) field_Repository_bug_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -449,6 +599,143 @@ func (ec *executionContext) fieldContext_Repository_name(_ context.Context, fiel return fc, nil } +func (ec *executionContext) _Repository_allBoards(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Repository_allBoards(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) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Repository().AllBoards(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int), fc.Args["query"].(*string)) + }) + 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.(*models.BoardConnection) + fc.Result = res + return ec.marshalNBoardConnection2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Repository_allBoards(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Repository", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_BoardConnection_edges(ctx, field) + case "nodes": + return ec.fieldContext_BoardConnection_nodes(ctx, field) + case "pageInfo": + return ec.fieldContext_BoardConnection_pageInfo(ctx, field) + case "totalCount": + return ec.fieldContext_BoardConnection_totalCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type BoardConnection", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Repository_allBoards_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Repository_board(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Repository_board(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) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Repository().Board(rctx, obj, fc.Args["prefix"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(models.BoardWrapper) + fc.Result = res + return ec.marshalOBoard2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBoardWrapper(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Repository_board(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Repository", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Board_id(ctx, field) + case "humanId": + return ec.fieldContext_Board_humanId(ctx, field) + case "createdAt": + return ec.fieldContext_Board_createdAt(ctx, field) + case "lastEdit": + return ec.fieldContext_Board_lastEdit(ctx, field) + case "title": + return ec.fieldContext_Board_title(ctx, field) + case "description": + return ec.fieldContext_Board_description(ctx, field) + case "columns": + return ec.fieldContext_Board_columns(ctx, field) + case "actors": + return ec.fieldContext_Board_actors(ctx, field) + case "operations": + return ec.fieldContext_Board_operations(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Board", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Repository_board_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Repository_allBugs(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Repository_allBugs(ctx, field) if err != nil { @@ -908,6 +1195,75 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "allBoards": + 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._Repository_allBoards(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 "board": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Repository_board(ctx, field, obj) + 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 "allBugs": field := field diff --git a/api/graphql/graph/root.generated.go b/api/graphql/graph/root.generated.go index 27ac29f8b74298892724b2161576050e27c65605..b9f5b80d7649bccbfaa1c0c2b6babd8bf2693fc4 100644 --- a/api/graphql/graph/root.generated.go +++ b/api/graphql/graph/root.generated.go @@ -962,6 +962,10 @@ func (ec *executionContext) fieldContext_Query_repository(ctx context.Context, f switch field.Name { case "name": return ec.fieldContext_Repository_name(ctx, field) + case "allBoards": + return ec.fieldContext_Repository_allBoards(ctx, field) + case "board": + return ec.fieldContext_Repository_board(ctx, field) case "allBugs": return ec.fieldContext_Repository_allBugs(ctx, field) case "bug": diff --git a/api/graphql/graph/root_.generated.go b/api/graphql/graph/root_.generated.go index 948aaa04aea7c06da7576c5465fd5cd609f2cc74..c13f38447f8c8b585e8d422d97089e63ed9709c4 100644 --- a/api/graphql/graph/root_.generated.go +++ b/api/graphql/graph/root_.generated.go @@ -482,8 +482,10 @@ type ComplexityRoot struct { } Repository struct { + AllBoards func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int AllBugs func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int AllIdentities func(childComplexity int, after *string, before *string, first *int, last *int) int + Board func(childComplexity int, prefix string) int Bug func(childComplexity int, prefix string) int Identity func(childComplexity int, prefix string) int Name func(childComplexity int) int @@ -2300,6 +2302,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Query.Repository(childComplexity, args["ref"].(*string)), true + case "Repository.allBoards": + if e.complexity.Repository.AllBoards == nil { + break + } + + args, err := ec.field_Repository_allBoards_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Repository.AllBoards(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["query"].(*string)), true + case "Repository.allBugs": if e.complexity.Repository.AllBugs == nil { break @@ -2324,6 +2338,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Repository.AllIdentities(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true + case "Repository.board": + if e.complexity.Repository.Board == nil { + break + } + + args, err := ec.field_Repository_board_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Repository.Board(childComplexity, args["prefix"].(string)), true + case "Repository.bug": if e.complexity.Repository.Bug == nil { break @@ -3338,6 +3364,22 @@ type OperationEdge { """The name of the repository""" name: String + """All the boards""" + allBoards( + """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 + ): BoardConnection! + + board(prefix: String!): Board + """All the bugs""" allBugs( """Returns the elements in the list that come after the specified cursor.""" diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go index 45dc71102045e2e31968b0220e0120eb81c34105..7352f99e0f5d496be475b1d0274c64fd5e203791 100644 --- a/api/graphql/resolvers/repo.go +++ b/api/graphql/resolvers/repo.go @@ -21,6 +21,65 @@ func (repoResolver) Name(_ context.Context, obj *models.Repository) (*string, er return &name, nil } +func (r repoResolver) AllBoards(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, query *string) (*models.BoardConnection, 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.Boards().AllIds() + + // The edger create a custom edge holding just the id + edger := func(id entity.Id, offset int) connections.Edge { + return connections.LazyBoardEdge{ + Id: id, + Cursor: connections.OffsetToCursor(offset), + } + } + + // The conMaker will finally load and compile boards from git to replace the selected edges + conMaker := func(lazyBoardEdges []*connections.LazyBoardEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.BoardConnection, error) { + edges := make([]*models.BoardEdge, len(lazyBoardEdges)) + nodes := make([]models.BoardWrapper, len(lazyBoardEdges)) + + for k, lazyBoardEdge := range lazyBoardEdges { + excerpt, err := obj.Repo.Boards().ResolveExcerpt(lazyBoardEdge.Id) + if err != nil { + return nil, err + } + + i := models.NewLazyBoard(obj.Repo, excerpt) + + edges[k] = &models.BoardEdge{ + Cursor: lazyBoardEdge.Cursor, + Node: i, + } + nodes[k] = i + } + + return &models.BoardConnection{ + Edges: edges, + Nodes: nodes, + PageInfo: info, + TotalCount: totalCount, + }, nil + } + + return connections.Connection(source, edger, conMaker, input) +} + +func (r repoResolver) Board(ctx context.Context, obj *models.Repository, prefix string) (models.BoardWrapper, error) { + excerpt, err := obj.Repo.Boards().ResolveExcerptPrefix(prefix) + if err != nil { + return nil, err + } + + return models.NewLazyBoard(obj.Repo, excerpt), nil +} + func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (*models.BugConnection, error) { input := models.ConnectionInput{ Before: before, diff --git a/api/graphql/schema/repository.graphql b/api/graphql/schema/repository.graphql index ccd409f54e829ddf0a7e6f076d05f0ba80d5ae3e..4531e19a9e26e00b61afb22684c6dc1dcf7a7cd9 100644 --- a/api/graphql/schema/repository.graphql +++ b/api/graphql/schema/repository.graphql @@ -2,6 +2,22 @@ type Repository { """The name of the repository""" name: String + """All the boards""" + allBoards( + """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 + ): BoardConnection! + + board(prefix: String!): Board + """All the bugs""" allBugs( """Returns the elements in the list that come after the specified cursor."""