Merge pull request #316 from MichaelMure/lazy-graphql

graphql: use the cache in priority for fast browsing in the WebUI
This commit is contained in:
Michael Muré 2020-02-09 02:56:37 +01:00 committed by GitHub
commit 3caffeef4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1948 additions and 1433 deletions

View File

@ -677,6 +677,3 @@ func (bug *Bug) Compile() Snapshot {
return snap return snap
} }
// Sign post method for gqlgen
func (bug *Bug) IsAuthored() {}

View File

@ -21,6 +21,9 @@ type AddCommentOperation struct {
Files []git.Hash `json:"files"` Files []git.Hash `json:"files"`
} }
// Sign-post method for gqlgen
func (op *AddCommentOperation) IsOperation() {}
func (op *AddCommentOperation) base() *OpBase { func (op *AddCommentOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -22,6 +22,9 @@ type CreateOperation struct {
Files []git.Hash `json:"files"` Files []git.Hash `json:"files"`
} }
// Sign-post method for gqlgen
func (op *CreateOperation) IsOperation() {}
func (op *CreateOperation) base() *OpBase { func (op *CreateOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -24,6 +24,9 @@ type EditCommentOperation struct {
Files []git.Hash `json:"files"` Files []git.Hash `json:"files"`
} }
// Sign-post method for gqlgen
func (op *EditCommentOperation) IsOperation() {}
func (op *EditCommentOperation) base() *OpBase { func (op *EditCommentOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -21,6 +21,9 @@ type LabelChangeOperation struct {
Removed []Label `json:"removed"` Removed []Label `json:"removed"`
} }
// Sign-post method for gqlgen
func (op *LabelChangeOperation) IsOperation() {}
func (op *LabelChangeOperation) base() *OpBase { func (op *LabelChangeOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -16,6 +16,9 @@ type NoOpOperation struct {
OpBase OpBase
} }
// Sign-post method for gqlgen
func (op *NoOpOperation) IsOperation() {}
func (op *NoOpOperation) base() *OpBase { func (op *NoOpOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -17,6 +17,9 @@ type SetMetadataOperation struct {
NewMetadata map[string]string `json:"new_metadata"` NewMetadata map[string]string `json:"new_metadata"`
} }
// Sign-post method for gqlgen
func (op *SetMetadataOperation) IsOperation() {}
func (op *SetMetadataOperation) base() *OpBase { func (op *SetMetadataOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -18,6 +18,9 @@ type SetStatusOperation struct {
Status Status `json:"status"` Status Status `json:"status"`
} }
// Sign-post method for gqlgen
func (op *SetStatusOperation) IsOperation() {}
func (op *SetStatusOperation) base() *OpBase { func (op *SetStatusOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -21,6 +21,9 @@ type SetTitleOperation struct {
Was string `json:"was"` Was string `json:"was"`
} }
// Sign-post method for gqlgen
func (op *SetTitleOperation) IsOperation() {}
func (op *SetTitleOperation) base() *OpBase { func (op *SetTitleOperation) base() *OpBase {
return &op.OpBase return &op.OpBase
} }

View File

@ -52,6 +52,9 @@ type Operation interface {
AllMetadata() map[string]string AllMetadata() map[string]string
// GetAuthor return the author identity // GetAuthor return the author identity
GetAuthor() identity.Interface GetAuthor() identity.Interface
// sign-post method for gqlgen
IsOperation()
} }
func deriveId(data []byte) entity.Id { func deriveId(data []byte) entity.Id {

65
cache/filter.go vendored
View File

@ -4,10 +4,17 @@ import (
"strings" "strings"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
) )
// resolver has the resolving functions needed by filters.
// This exist mainly to go through the functions of the cache with proper locking.
type resolver interface {
ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error)
}
// Filter is a predicate that match a subset of bugs // Filter is a predicate that match a subset of bugs
type Filter func(repoCache *RepoCache, excerpt *BugExcerpt) bool type Filter func(excerpt *BugExcerpt, resolver resolver) bool
// StatusFilter return a Filter that match a bug status // StatusFilter return a Filter that match a bug status
func StatusFilter(query string) (Filter, error) { func StatusFilter(query string) (Filter, error) {
@ -16,21 +23,21 @@ func StatusFilter(query string) (Filter, error) {
return nil, err return nil, err
} }
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
return excerpt.Status == status return excerpt.Status == status
}, nil }, nil
} }
// AuthorFilter return a Filter that match a bug author // AuthorFilter return a Filter that match a bug author
func AuthorFilter(query string) Filter { func AuthorFilter(query string) Filter {
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
query = strings.ToLower(query) query = strings.ToLower(query)
// Normal identity // Normal identity
if excerpt.AuthorId != "" { if excerpt.AuthorId != "" {
author, ok := repoCache.identitiesExcerpts[excerpt.AuthorId] author, err := resolver.ResolveIdentityExcerpt(excerpt.AuthorId)
if !ok { if err != nil {
panic("missing identity in the cache") panic(err)
} }
return author.Match(query) return author.Match(query)
@ -43,7 +50,7 @@ func AuthorFilter(query string) Filter {
// LabelFilter return a Filter that match a label // LabelFilter return a Filter that match a label
func LabelFilter(label string) Filter { func LabelFilter(label string) Filter {
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
for _, l := range excerpt.Labels { for _, l := range excerpt.Labels {
if string(l) == label { if string(l) == label {
return true return true
@ -55,13 +62,13 @@ func LabelFilter(label string) Filter {
// ActorFilter return a Filter that match a bug actor // ActorFilter return a Filter that match a bug actor
func ActorFilter(query string) Filter { func ActorFilter(query string) Filter {
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
query = strings.ToLower(query) query = strings.ToLower(query)
for _, id := range excerpt.Actors { for _, id := range excerpt.Actors {
identityExcerpt, ok := repoCache.identitiesExcerpts[id] identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
if !ok { if err != nil {
panic("missing identity in the cache") panic(err)
} }
if identityExcerpt.Match(query) { if identityExcerpt.Match(query) {
@ -74,13 +81,13 @@ func ActorFilter(query string) Filter {
// ParticipantFilter return a Filter that match a bug participant // ParticipantFilter return a Filter that match a bug participant
func ParticipantFilter(query string) Filter { func ParticipantFilter(query string) Filter {
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
query = strings.ToLower(query) query = strings.ToLower(query)
for _, id := range excerpt.Participants { for _, id := range excerpt.Participants {
identityExcerpt, ok := repoCache.identitiesExcerpts[id] identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
if !ok { if err != nil {
panic("missing identity in the cache") panic(err)
} }
if identityExcerpt.Match(query) { if identityExcerpt.Match(query) {
@ -93,7 +100,7 @@ func ParticipantFilter(query string) Filter {
// TitleFilter return a Filter that match if the title contains the given query // TitleFilter return a Filter that match if the title contains the given query
func TitleFilter(query string) Filter { func TitleFilter(query string) Filter {
return func(repo *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
return strings.Contains( return strings.Contains(
strings.ToLower(excerpt.Title), strings.ToLower(excerpt.Title),
strings.ToLower(query), strings.ToLower(query),
@ -103,7 +110,7 @@ func TitleFilter(query string) Filter {
// NoLabelFilter return a Filter that match the absence of labels // NoLabelFilter return a Filter that match the absence of labels
func NoLabelFilter() Filter { func NoLabelFilter() Filter {
return func(repoCache *RepoCache, excerpt *BugExcerpt) bool { return func(excerpt *BugExcerpt, resolver resolver) bool {
return len(excerpt.Labels) == 0 return len(excerpt.Labels) == 0
} }
} }
@ -120,32 +127,32 @@ type Filters struct {
} }
// Match check if a bug match the set of filters // Match check if a bug match the set of filters
func (f *Filters) Match(repoCache *RepoCache, excerpt *BugExcerpt) bool { func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
if match := f.orMatch(f.Status, repoCache, excerpt); !match { if match := f.orMatch(f.Status, excerpt, resolver); !match {
return false return false
} }
if match := f.orMatch(f.Author, repoCache, excerpt); !match { if match := f.orMatch(f.Author, excerpt, resolver); !match {
return false return false
} }
if match := f.orMatch(f.Participant, repoCache, excerpt); !match { if match := f.orMatch(f.Participant, excerpt, resolver); !match {
return false return false
} }
if match := f.orMatch(f.Actor, repoCache, excerpt); !match { if match := f.orMatch(f.Actor, excerpt, resolver); !match {
return false return false
} }
if match := f.andMatch(f.Label, repoCache, excerpt); !match { if match := f.andMatch(f.Label, excerpt, resolver); !match {
return false return false
} }
if match := f.andMatch(f.NoFilters, repoCache, excerpt); !match { if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
return false return false
} }
if match := f.andMatch(f.Title, repoCache, excerpt); !match { if match := f.andMatch(f.Title, excerpt, resolver); !match {
return false return false
} }
@ -153,28 +160,28 @@ func (f *Filters) Match(repoCache *RepoCache, excerpt *BugExcerpt) bool {
} }
// Check if any of the filters provided match the bug // Check if any of the filters provided match the bug
func (*Filters) orMatch(filters []Filter, repoCache *RepoCache, excerpt *BugExcerpt) bool { func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 { if len(filters) == 0 {
return true return true
} }
match := false match := false
for _, f := range filters { for _, f := range filters {
match = match || f(repoCache, excerpt) match = match || f(excerpt, resolver)
} }
return match return match
} }
// Check if all of the filters provided match the bug // Check if all of the filters provided match the bug
func (*Filters) andMatch(filters []Filter, repoCache *RepoCache, excerpt *BugExcerpt) bool { func (*Filters) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 { if len(filters) == 0 {
return true return true
} }
match := true match := true
for _, f := range filters { for _, f := range filters {
match = match && f(repoCache, excerpt) match = match && f(excerpt, resolver)
} }
return match return match

View File

@ -28,7 +28,7 @@ func TestTitleFilter(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
filter := TitleFilter(tt.query) filter := TitleFilter(tt.query)
excerpt := &BugExcerpt{Title: tt.title} excerpt := &BugExcerpt{Title: tt.title}
assert.Equal(t, tt.match, filter(nil, excerpt)) assert.Equal(t, tt.match, filter(excerpt, nil))
}) })
} }
} }

195
cache/repo_cache.go vendored
View File

@ -10,6 +10,7 @@ import (
"path" "path"
"sort" "sort"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -57,11 +58,13 @@ type RepoCache struct {
// the underlying repo // the underlying repo
repo repository.ClockedRepo repo repository.ClockedRepo
muBug sync.RWMutex
// excerpt of bugs data for all bugs // excerpt of bugs data for all bugs
bugExcerpts map[entity.Id]*BugExcerpt bugExcerpts map[entity.Id]*BugExcerpt
// bug loaded in memory // bug loaded in memory
bugs map[entity.Id]*BugCache bugs map[entity.Id]*BugCache
muIdentity sync.RWMutex
// excerpt of identities data for all identities // excerpt of identities data for all identities
identitiesExcerpts map[entity.Id]*IdentityExcerpt identitiesExcerpts map[entity.Id]*IdentityExcerpt
// identities loaded in memory // identities loaded in memory
@ -157,6 +160,11 @@ func (c *RepoCache) lock() error {
} }
func (c *RepoCache) Close() error { func (c *RepoCache) Close() error {
c.muBug.Lock()
defer c.muBug.Unlock()
c.muIdentity.Lock()
defer c.muIdentity.Unlock()
c.identities = make(map[entity.Id]*IdentityCache) c.identities = make(map[entity.Id]*IdentityCache)
c.identitiesExcerpts = nil c.identitiesExcerpts = nil
c.bugs = make(map[entity.Id]*BugCache) c.bugs = make(map[entity.Id]*BugCache)
@ -169,12 +177,16 @@ func (c *RepoCache) Close() error {
// bugUpdated is a callback to trigger when the excerpt of a bug changed, // bugUpdated is a callback to trigger when the excerpt of a bug changed,
// that is each time a bug is updated // that is each time a bug is updated
func (c *RepoCache) bugUpdated(id entity.Id) error { func (c *RepoCache) bugUpdated(id entity.Id) error {
c.muBug.Lock()
b, ok := c.bugs[id] b, ok := c.bugs[id]
if !ok { if !ok {
c.muBug.Unlock()
panic("missing bug in the cache") panic("missing bug in the cache")
} }
c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot()) c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
c.muBug.Unlock()
// we only need to write the bug cache // we only need to write the bug cache
return c.writeBugCache() return c.writeBugCache()
@ -183,12 +195,16 @@ func (c *RepoCache) bugUpdated(id entity.Id) error {
// identityUpdated is a callback to trigger when the excerpt of an identity // identityUpdated is a callback to trigger when the excerpt of an identity
// changed, that is each time an identity is updated // changed, that is each time an identity is updated
func (c *RepoCache) identityUpdated(id entity.Id) error { func (c *RepoCache) identityUpdated(id entity.Id) error {
c.muIdentity.Lock()
i, ok := c.identities[id] i, ok := c.identities[id]
if !ok { if !ok {
c.muIdentity.Unlock()
panic("missing identity in the cache") panic("missing identity in the cache")
} }
c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity) c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
c.muIdentity.Unlock()
// we only need to write the identity cache // we only need to write the identity cache
return c.writeIdentityCache() return c.writeIdentityCache()
@ -205,6 +221,9 @@ func (c *RepoCache) load() error {
// load will try to read from the disk the bug cache file // load will try to read from the disk the bug cache file
func (c *RepoCache) loadBugCache() error { func (c *RepoCache) loadBugCache() error {
c.muBug.Lock()
defer c.muBug.Unlock()
f, err := os.Open(bugCacheFilePath(c.repo)) f, err := os.Open(bugCacheFilePath(c.repo))
if err != nil { if err != nil {
return err return err
@ -234,6 +253,9 @@ func (c *RepoCache) loadBugCache() error {
// load will try to read from the disk the identity cache file // load will try to read from the disk the identity cache file
func (c *RepoCache) loadIdentityCache() error { func (c *RepoCache) loadIdentityCache() error {
c.muIdentity.Lock()
defer c.muIdentity.Unlock()
f, err := os.Open(identityCacheFilePath(c.repo)) f, err := os.Open(identityCacheFilePath(c.repo))
if err != nil { if err != nil {
return err return err
@ -272,6 +294,9 @@ func (c *RepoCache) write() error {
// write will serialize on disk the bug cache file // write will serialize on disk the bug cache file
func (c *RepoCache) writeBugCache() error { func (c *RepoCache) writeBugCache() error {
c.muBug.RLock()
defer c.muBug.RUnlock()
var data bytes.Buffer var data bytes.Buffer
aux := struct { aux := struct {
@ -304,6 +329,9 @@ func (c *RepoCache) writeBugCache() error {
// write will serialize on disk the identity cache file // write will serialize on disk the identity cache file
func (c *RepoCache) writeIdentityCache() error { func (c *RepoCache) writeIdentityCache() error {
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
var data bytes.Buffer var data bytes.Buffer
aux := struct { aux := struct {
@ -343,6 +371,11 @@ func identityCacheFilePath(repo repository.Repo) string {
} }
func (c *RepoCache) buildCache() error { func (c *RepoCache) buildCache() error {
c.muBug.Lock()
defer c.muBug.Unlock()
c.muIdentity.Lock()
defer c.muIdentity.Unlock()
_, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ") _, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ")
c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt) c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt)
@ -378,9 +411,24 @@ func (c *RepoCache) buildCache() error {
return nil return nil
} }
// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
c.muBug.RLock()
defer c.muBug.RUnlock()
e, ok := c.bugExcerpts[id]
if !ok {
return nil, bug.ErrBugNotExist
}
return e, nil
}
// ResolveBug retrieve a bug matching the exact given id // ResolveBug retrieve a bug matching the exact given id
func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) { func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
c.muBug.RLock()
cached, ok := c.bugs[id] cached, ok := c.bugs[id]
c.muBug.RUnlock()
if ok { if ok {
return cached, nil return cached, nil
} }
@ -391,19 +439,20 @@ func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
} }
cached = NewBugCache(c, b) cached = NewBugCache(c, b)
c.muBug.Lock()
c.bugs[id] = cached c.bugs[id] = cached
c.muBug.Unlock()
return cached, nil return cached, nil
} }
// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id // ResolveBugExcerptPrefix retrieve a BugExcerpt matching an id prefix. It fails if multiple
func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) { // bugs match.
e, ok := c.bugExcerpts[id] func (c *RepoCache) ResolveBugExcerptPrefix(prefix string) (*BugExcerpt, error) {
if !ok { return c.ResolveBugExcerptMatcher(func(excerpt *BugExcerpt) bool {
return nil, bug.ErrBugNotExist return excerpt.Id.HasPrefix(prefix)
} })
return e, nil
} }
// ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple // ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
@ -423,7 +472,26 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach
}) })
} }
func (c *RepoCache) ResolveBugExcerptMatcher(f func(*BugExcerpt) bool) (*BugExcerpt, error) {
id, err := c.resolveBugMatcher(f)
if err != nil {
return nil, err
}
return c.ResolveBugExcerpt(id)
}
func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, error) { func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, error) {
id, err := c.resolveBugMatcher(f)
if err != nil {
return nil, err
}
return c.ResolveBug(id)
}
func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, error) {
c.muBug.RLock()
defer c.muBug.RUnlock()
// preallocate but empty // preallocate but empty
matching := make([]entity.Id, 0, 5) matching := make([]entity.Id, 0, 5)
@ -434,18 +502,21 @@ func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, erro
} }
if len(matching) > 1 { if len(matching) > 1 {
return nil, bug.NewErrMultipleMatchBug(matching) return entity.UnsetId, bug.NewErrMultipleMatchBug(matching)
} }
if len(matching) == 0 { if len(matching) == 0 {
return nil, bug.ErrBugNotExist return entity.UnsetId, bug.ErrBugNotExist
} }
return c.ResolveBug(matching[0]) return matching[0], nil
} }
// QueryBugs return the id of all Bug matching the given Query // QueryBugs return the id of all Bug matching the given Query
func (c *RepoCache) QueryBugs(query *Query) []entity.Id { func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
c.muBug.RLock()
defer c.muBug.RUnlock()
if query == nil { if query == nil {
return c.AllBugsIds() return c.AllBugsIds()
} }
@ -453,7 +524,7 @@ func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
var filtered []*BugExcerpt var filtered []*BugExcerpt
for _, excerpt := range c.bugExcerpts { for _, excerpt := range c.bugExcerpts {
if query.Match(c, excerpt) { if query.Match(excerpt, c) {
filtered = append(filtered, excerpt) filtered = append(filtered, excerpt)
} }
} }
@ -488,6 +559,9 @@ func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
// AllBugsIds return all known bug ids // AllBugsIds return all known bug ids
func (c *RepoCache) AllBugsIds() []entity.Id { func (c *RepoCache) AllBugsIds() []entity.Id {
c.muBug.RLock()
defer c.muBug.RUnlock()
result := make([]entity.Id, len(c.bugExcerpts)) result := make([]entity.Id, len(c.bugExcerpts))
i := 0 i := 0
@ -505,6 +579,9 @@ func (c *RepoCache) AllBugsIds() []entity.Id {
// labels are defined in a configuration file. Until that, the default behavior // labels are defined in a configuration file. Until that, the default behavior
// is to return the list of labels already used. // is to return the list of labels already used.
func (c *RepoCache) ValidLabels() []bug.Label { func (c *RepoCache) ValidLabels() []bug.Label {
c.muBug.RLock()
defer c.muBug.RUnlock()
set := map[bug.Label]interface{}{} set := map[bug.Label]interface{}{}
for _, excerpt := range c.bugExcerpts { for _, excerpt := range c.bugExcerpts {
@ -564,12 +641,15 @@ func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title strin
return nil, nil, err return nil, nil, err
} }
c.muBug.Lock()
if _, has := c.bugs[b.Id()]; has { if _, has := c.bugs[b.Id()]; has {
c.muBug.Unlock()
return nil, nil, fmt.Errorf("bug %s already exist in the cache", b.Id()) return nil, nil, fmt.Errorf("bug %s already exist in the cache", b.Id())
} }
cached := NewBugCache(c, b) cached := NewBugCache(c, b)
c.bugs[b.Id()] = cached c.bugs[b.Id()] = cached
c.muBug.Unlock()
// force the write of the excerpt // force the write of the excerpt
err = c.bugUpdated(b.Id()) err = c.bugUpdated(b.Id())
@ -615,7 +695,9 @@ func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
switch result.Status { switch result.Status {
case entity.MergeStatusNew, entity.MergeStatusUpdated: case entity.MergeStatusNew, entity.MergeStatusUpdated:
i := result.Entity.(*identity.Identity) i := result.Entity.(*identity.Identity)
c.muIdentity.Lock()
c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i) c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i)
c.muIdentity.Unlock()
} }
} }
@ -631,7 +713,9 @@ func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
case entity.MergeStatusNew, entity.MergeStatusUpdated: case entity.MergeStatusNew, entity.MergeStatusUpdated:
b := result.Entity.(*bug.Bug) b := result.Entity.(*bug.Bug)
snap := b.Compile() snap := b.Compile()
c.muBug.Lock()
c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap) c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap)
c.muBug.Unlock()
} }
} }
@ -745,9 +829,24 @@ func repoIsAvailable(repo repository.Repo) error {
return nil return nil
} }
// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
e, ok := c.identitiesExcerpts[id]
if !ok {
return nil, identity.ErrIdentityNotExist
}
return e, nil
}
// ResolveIdentity retrieve an identity matching the exact given id // ResolveIdentity retrieve an identity matching the exact given id
func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) { func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
c.muIdentity.RLock()
cached, ok := c.identities[id] cached, ok := c.identities[id]
c.muIdentity.RUnlock()
if ok { if ok {
return cached, nil return cached, nil
} }
@ -758,19 +857,20 @@ func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
} }
cached = NewIdentityCache(c, i) cached = NewIdentityCache(c, i)
c.muIdentity.Lock()
c.identities[id] = cached c.identities[id] = cached
c.muIdentity.Unlock()
return cached, nil return cached, nil
} }
// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id // ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) { // It fails if multiple identities match.
e, ok := c.identitiesExcerpts[id] func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
if !ok { return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
return nil, identity.ErrIdentityNotExist return excerpt.Id.HasPrefix(prefix)
} })
return e, nil
} }
// ResolveIdentityPrefix retrieve an Identity matching an id prefix. // ResolveIdentityPrefix retrieve an Identity matching an id prefix.
@ -789,7 +889,26 @@ func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (
}) })
} }
func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
id, err := c.resolveIdentityMatcher(f)
if err != nil {
return nil, err
}
return c.ResolveIdentityExcerpt(id)
}
func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) { func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
id, err := c.resolveIdentityMatcher(f)
if err != nil {
return nil, err
}
return c.ResolveIdentity(id)
}
func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
// preallocate but empty // preallocate but empty
matching := make([]entity.Id, 0, 5) matching := make([]entity.Id, 0, 5)
@ -800,18 +919,21 @@ func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*Iden
} }
if len(matching) > 1 { if len(matching) > 1 {
return nil, identity.NewErrMultipleMatch(matching) return entity.UnsetId, identity.NewErrMultipleMatch(matching)
} }
if len(matching) == 0 { if len(matching) == 0 {
return nil, identity.ErrIdentityNotExist return entity.UnsetId, identity.ErrIdentityNotExist
} }
return c.ResolveIdentity(matching[0]) return matching[0], nil
} }
// AllIdentityIds return all known identity ids // AllIdentityIds return all known identity ids
func (c *RepoCache) AllIdentityIds() []entity.Id { func (c *RepoCache) AllIdentityIds() []entity.Id {
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
result := make([]entity.Id, len(c.identitiesExcerpts)) result := make([]entity.Id, len(c.identitiesExcerpts))
i := 0 i := 0
@ -829,6 +951,9 @@ func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
return err return err
} }
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
// Make sure that everything is fine // Make sure that everything is fine
if _, ok := c.identities[i.Id()]; !ok { if _, ok := c.identities[i.Id()]; !ok {
panic("SetUserIdentity while the identity is not from the cache, something is wrong") panic("SetUserIdentity while the identity is not from the cache, something is wrong")
@ -847,6 +972,9 @@ func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
} }
} }
c.muIdentity.Lock()
defer c.muIdentity.Unlock()
i, err := identity.GetUserIdentity(c.repo) i, err := identity.GetUserIdentity(c.repo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -859,6 +987,25 @@ func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
return cached, nil return cached, nil
} }
func (c *RepoCache) GetUserIdentityExcerpt() (*IdentityExcerpt, error) {
if c.userIdentityId == "" {
id, err := identity.GetUserIdentityId(c.repo)
if err != nil {
return nil, err
}
c.userIdentityId = id
}
c.muIdentity.RLock()
defer c.muIdentity.RUnlock()
excerpt, ok := c.identitiesExcerpts[c.userIdentityId]
if !ok {
return nil, fmt.Errorf("cache: missing identity excerpt %v", c.userIdentityId)
}
return excerpt, nil
}
func (c *RepoCache) IsUserIdentitySet() (bool, error) { func (c *RepoCache) IsUserIdentitySet() (bool, error) {
return identity.IsUserIdentitySet(c.repo) return identity.IsUserIdentitySet(c.repo)
} }
@ -902,12 +1049,14 @@ func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]str
return nil, err return nil, err
} }
c.muIdentity.Lock()
if _, has := c.identities[i.Id()]; has { if _, has := c.identities[i.Id()]; has {
return nil, fmt.Errorf("identity %s already exist in the cache", i.Id()) return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
} }
cached := NewIdentityCache(c, i) cached := NewIdentityCache(c, i)
c.identities[i.Id()] = cached c.identities[i.Id()] = cached
c.muIdentity.Unlock()
// force the write of the excerpt // force the write of the excerpt
err = c.identityUpdated(i.Id()) err = c.identityUpdated(i.Id())

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/MichaelMure/git-bug
go 1.11 go 1.11
require ( require (
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6 github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b
github.com/MichaelMure/go-term-text v0.2.6 github.com/MichaelMure/go-term-text v0.2.6
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986 github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986

13
go.sum
View File

@ -1,11 +1,11 @@
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6 h1:WU+9Z7AoCyl+HlB2kE0O+4/3BaVqLQfnX5dHigV5p8A= github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0 h1:ADy3XJwhOYg6Pb90XeXazWvO+9gpOsgLuaM1buZUZOY=
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6/go.mod h1:28v/ATDVwPUriwNtAIrQEhRHXJjdi5dVGqxSqPna1I8= github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0/go.mod h1:dfBhwZKMcSYiYRMTs8qWF+Oha6782e1xPfgRmVal9I8=
github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b h1:510xa84qGbDemwTHNio4cLWkdKFxxJgVtsIOH+Ku8bo=
github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b/go.mod h1:dfBhwZKMcSYiYRMTs8qWF+Oha6782e1xPfgRmVal9I8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/MichaelMure/go-term-text v0.2.4 h1:h+lAsjG5o3oNvaJeh7OzE8zdSiB/VyJo9JzSnncrZv4=
github.com/MichaelMure/go-term-text v0.2.4/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
github.com/MichaelMure/go-term-text v0.2.6 h1:dSmJSzk2iI5xWymSMrMbdVM1bxYWu3DjDFhdcJvAuqA= github.com/MichaelMure/go-term-text v0.2.6 h1:dSmJSzk2iI5xWymSMrMbdVM1bxYWu3DjDFhdcJvAuqA=
github.com/MichaelMure/go-term-text v0.2.6/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww= github.com/MichaelMure/go-term-text v0.2.6/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
@ -36,8 +36,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -69,6 +67,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
@ -132,6 +131,8 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUd
github.com/vektah/gqlparser v1.2.1 h1:C+L7Go/eUbN0w6Y0kaiq2W6p2wN5j8wU82EdDXxDivc= github.com/vektah/gqlparser v1.2.1 h1:C+L7Go/eUbN0w6Y0kaiq2W6p2wN5j8wU82EdDXxDivc=
github.com/vektah/gqlparser v1.2.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= github.com/vektah/gqlparser v1.2.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU= github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno= github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno=
github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og= github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=

View File

@ -1,6 +1,6 @@
//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection" //go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection" //go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id 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_identity.go gen "Name=Identity NodeType=models.IdentityWrapper 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_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_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" //go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"

View File

@ -2,6 +2,17 @@ package connections
import "github.com/MichaelMure/git-bug/entity" import "github.com/MichaelMure/git-bug/entity"
// LazyBugEdge is a special relay edge used to implement a lazy loading connection
type LazyBugEdge struct {
Id entity.Id
Cursor string
}
// GetCursor return the cursor of a LazyBugEdge
func (lbe LazyBugEdge) GetCursor() string {
return lbe.Cursor
}
// LazyIdentityEdge is a special relay edge used to implement a lazy loading connection // LazyIdentityEdge is a special relay edge used to implement a lazy loading connection
type LazyIdentityEdge struct { type LazyIdentityEdge struct {
Id entity.Id Id entity.Id

View File

@ -8,23 +8,22 @@ import (
"fmt" "fmt"
"github.com/MichaelMure/git-bug/graphql/models" "github.com/MichaelMure/git-bug/graphql/models"
"github.com/MichaelMure/git-bug/identity"
) )
// IdentityInterfaceEdgeMaker define a function that take a identity.Interface and an offset and // ModelsIdentityWrapperEdgeMaker define a function that take a models.IdentityWrapper and an offset and
// create an Edge. // create an Edge.
type IdentityEdgeMaker func(value identity.Interface, offset int) Edge type IdentityEdgeMaker func(value models.IdentityWrapper, offset int) Edge
// IdentityConMaker define a function that create a models.IdentityConnection // IdentityConMaker define a function that create a models.IdentityConnection
type IdentityConMaker func( type IdentityConMaker func(
edges []*models.IdentityEdge, edges []*models.IdentityEdge,
nodes []identity.Interface, nodes []models.IdentityWrapper,
info *models.PageInfo, info *models.PageInfo,
totalCount int) (*models.IdentityConnection, error) totalCount int) (*models.IdentityConnection, error)
// IdentityCon will paginate a source according to the input of a relay connection // 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) { func IdentityCon(source []models.IdentityWrapper, edgeMaker IdentityEdgeMaker, conMaker IdentityConMaker, input models.ConnectionInput) (*models.IdentityConnection, error) {
var nodes []identity.Interface var nodes []models.IdentityWrapper
var edges []*models.IdentityEdge var edges []*models.IdentityEdge
var cursors []string var cursors []string
var pageInfo = &models.PageInfo{} var pageInfo = &models.PageInfo{}

View File

@ -1,14 +0,0 @@
package connections
import "github.com/MichaelMure/git-bug/entity"
// LazyBugEdge is a special relay edge used to implement a lazy loading connection
type LazyBugEdge struct {
Id entity.Id
Cursor string
}
// GetCursor return the cursor of a LazyBugEdge
func (lbe LazyBugEdge) GetCursor() string {
return lbe.Cursor
}

View File

@ -10,13 +10,24 @@ models:
RepositoryMutation: RepositoryMutation:
model: github.com/MichaelMure/git-bug/graphql/models.RepositoryMutation model: github.com/MichaelMure/git-bug/graphql/models.RepositoryMutation
Bug: Bug:
model: github.com/MichaelMure/git-bug/bug.Snapshot model: github.com/MichaelMure/git-bug/graphql/models.BugWrapper
fields:
actors:
resolver: true
participants:
resolver: true
comments:
resolver: true
timeline:
resolver: true
operations:
resolver: true
Color: Color:
model: image/color.RGBA model: image/color.RGBA
Comment: Comment:
model: github.com/MichaelMure/git-bug/bug.Comment model: github.com/MichaelMure/git-bug/bug.Comment
Identity: Identity:
model: github.com/MichaelMure/git-bug/identity.Interface model: github.com/MichaelMure/git-bug/graphql/models.IdentityWrapper
Label: Label:
model: github.com/MichaelMure/git-bug/bug.Label model: github.com/MichaelMure/git-bug/bug.Label
Hash: Hash:

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ import (
"strconv" "strconv"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/git"
) )
@ -34,7 +33,7 @@ type AddCommentPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation. // The resulting operation.
Operation *bug.AddCommentOperation `json:"operation"` Operation *bug.AddCommentOperation `json:"operation"`
} }
@ -42,8 +41,8 @@ type AddCommentPayload struct {
// The connection type for Bug. // The connection type for Bug.
type BugConnection struct { type BugConnection struct {
// A list of edges. // A list of edges.
Edges []*BugEdge `json:"edges"` Edges []*BugEdge `json:"edges"`
Nodes []*bug.Snapshot `json:"nodes"` Nodes []BugWrapper `json:"nodes"`
// Information to aid in pagination. // Information to aid in pagination.
PageInfo *PageInfo `json:"pageInfo"` PageInfo *PageInfo `json:"pageInfo"`
// Identifies the total count of items in the connection. // Identifies the total count of items in the connection.
@ -55,7 +54,7 @@ type BugEdge struct {
// A cursor for use in pagination. // A cursor for use in pagination.
Cursor string `json:"cursor"` Cursor string `json:"cursor"`
// The item at the end of the edge. // The item at the end of the edge.
Node *bug.Snapshot `json:"node"` Node BugWrapper `json:"node"`
} }
type ChangeLabelInput struct { type ChangeLabelInput struct {
@ -75,7 +74,7 @@ type ChangeLabelPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation. // The resulting operation.
Operation *bug.LabelChangeOperation `json:"operation"` Operation *bug.LabelChangeOperation `json:"operation"`
// The effect each source label had. // The effect each source label had.
@ -95,7 +94,7 @@ type CloseBugPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation. // The resulting operation.
Operation *bug.SetStatusOperation `json:"operation"` Operation *bug.SetStatusOperation `json:"operation"`
} }
@ -125,7 +124,7 @@ type CommitAsNeededPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
} }
type CommitInput struct { type CommitInput struct {
@ -141,19 +140,19 @@ type CommitPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
} }
type IdentityConnection struct { type IdentityConnection struct {
Edges []*IdentityEdge `json:"edges"` Edges []*IdentityEdge `json:"edges"`
Nodes []identity.Interface `json:"nodes"` Nodes []IdentityWrapper `json:"nodes"`
PageInfo *PageInfo `json:"pageInfo"` PageInfo *PageInfo `json:"pageInfo"`
TotalCount int `json:"totalCount"` TotalCount int `json:"totalCount"`
} }
type IdentityEdge struct { type IdentityEdge struct {
Cursor string `json:"cursor"` Cursor string `json:"cursor"`
Node identity.Interface `json:"node"` Node IdentityWrapper `json:"node"`
} }
type LabelConnection struct { type LabelConnection struct {
@ -185,7 +184,7 @@ type NewBugPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The created bug. // The created bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation. // The resulting operation.
Operation *bug.CreateOperation `json:"operation"` Operation *bug.CreateOperation `json:"operation"`
} }
@ -203,7 +202,7 @@ type OpenBugPayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation. // The resulting operation.
Operation *bug.SetStatusOperation `json:"operation"` Operation *bug.SetStatusOperation `json:"operation"`
} }
@ -249,7 +248,7 @@ type SetTitlePayload struct {
// A unique identifier for the client performing the mutation. // A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
// The affected bug. // The affected bug.
Bug *bug.Snapshot `json:"bug"` Bug BugWrapper `json:"bug"`
// The resulting operation // The resulting operation
Operation *bug.SetTitleOperation `json:"operation"` Operation *bug.SetTitleOperation `json:"operation"`
} }

215
graphql/models/lazy_bug.go Normal file
View File

@ -0,0 +1,215 @@
package models
import (
"sync"
"time"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
)
// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
// Depending on the situation, a Bug can already be fully loaded in memory or not.
// This interface is used to wrap either a lazyBug or a loadedBug depending on the situation.
type BugWrapper interface {
Id() entity.Id
LastEdit() time.Time
Status() bug.Status
Title() string
Comments() ([]bug.Comment, error)
Labels() []bug.Label
Author() (IdentityWrapper, error)
Actors() ([]IdentityWrapper, error)
Participants() ([]IdentityWrapper, error)
CreatedAt() time.Time
Timeline() ([]bug.TimelineItem, error)
Operations() ([]bug.Operation, error)
IsAuthored()
}
var _ BugWrapper = &lazyBug{}
// lazyBug is a lazy-loading wrapper that fetch data from the cache (BugExcerpt) in priority,
// and load the complete bug and snapshot only when necessary.
type lazyBug struct {
cache *cache.RepoCache
excerpt *cache.BugExcerpt
mu sync.Mutex
snap *bug.Snapshot
}
func NewLazyBug(cache *cache.RepoCache, excerpt *cache.BugExcerpt) *lazyBug {
return &lazyBug{
cache: cache,
excerpt: excerpt,
}
}
func (lb *lazyBug) load() error {
if lb.snap != nil {
return nil
}
lb.mu.Lock()
defer lb.mu.Unlock()
b, err := lb.cache.ResolveBug(lb.excerpt.Id)
if err != nil {
return err
}
lb.snap = b.Snapshot()
return nil
}
func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) {
i, err := lb.cache.ResolveIdentityExcerpt(id)
if err != nil {
return nil, err
}
return &lazyIdentity{cache: lb.cache, excerpt: i}, nil
}
// Sign post method for gqlgen
func (lb *lazyBug) IsAuthored() {}
func (lb *lazyBug) Id() entity.Id {
return lb.excerpt.Id
}
func (lb *lazyBug) LastEdit() time.Time {
return time.Unix(lb.excerpt.EditUnixTime, 0)
}
func (lb *lazyBug) Status() bug.Status {
return lb.excerpt.Status
}
func (lb *lazyBug) Title() string {
return lb.excerpt.Title
}
func (lb *lazyBug) Comments() ([]bug.Comment, error) {
err := lb.load()
if err != nil {
return nil, err
}
return lb.snap.Comments, nil
}
func (lb *lazyBug) Labels() []bug.Label {
return lb.excerpt.Labels
}
func (lb *lazyBug) Author() (IdentityWrapper, error) {
return lb.identity(lb.excerpt.AuthorId)
}
func (lb *lazyBug) Actors() ([]IdentityWrapper, error) {
result := make([]IdentityWrapper, len(lb.excerpt.Actors))
for i, actorId := range lb.excerpt.Actors {
actor, err := lb.identity(actorId)
if err != nil {
return nil, err
}
result[i] = actor
}
return result, nil
}
func (lb *lazyBug) Participants() ([]IdentityWrapper, error) {
result := make([]IdentityWrapper, len(lb.excerpt.Participants))
for i, participantId := range lb.excerpt.Participants {
participant, err := lb.identity(participantId)
if err != nil {
return nil, err
}
result[i] = participant
}
return result, nil
}
func (lb *lazyBug) CreatedAt() time.Time {
return time.Unix(lb.excerpt.CreateUnixTime, 0)
}
func (lb *lazyBug) Timeline() ([]bug.TimelineItem, error) {
err := lb.load()
if err != nil {
return nil, err
}
return lb.snap.Timeline, nil
}
func (lb *lazyBug) Operations() ([]bug.Operation, error) {
err := lb.load()
if err != nil {
return nil, err
}
return lb.snap.Operations, nil
}
var _ BugWrapper = &loadedBug{}
type loadedBug struct {
*bug.Snapshot
}
func NewLoadedBug(snap *bug.Snapshot) *loadedBug {
return &loadedBug{Snapshot: snap}
}
func (l *loadedBug) LastEdit() time.Time {
return l.Snapshot.LastEditTime()
}
func (l *loadedBug) Status() bug.Status {
return l.Snapshot.Status
}
func (l *loadedBug) Title() string {
return l.Snapshot.Title
}
func (l *loadedBug) Comments() ([]bug.Comment, error) {
return l.Snapshot.Comments, nil
}
func (l *loadedBug) Labels() []bug.Label {
return l.Snapshot.Labels
}
func (l *loadedBug) Author() (IdentityWrapper, error) {
return NewLoadedIdentity(l.Snapshot.Author), nil
}
func (l *loadedBug) Actors() ([]IdentityWrapper, error) {
res := make([]IdentityWrapper, len(l.Snapshot.Actors))
for i, actor := range l.Snapshot.Actors {
res[i] = NewLoadedIdentity(actor)
}
return res, nil
}
func (l *loadedBug) Participants() ([]IdentityWrapper, error) {
res := make([]IdentityWrapper, len(l.Snapshot.Participants))
for i, participant := range l.Snapshot.Participants {
res[i] = NewLoadedIdentity(participant)
}
return res, nil
}
func (l *loadedBug) CreatedAt() time.Time {
return l.Snapshot.CreatedAt
}
func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) {
return l.Snapshot.Timeline, nil
}
func (l *loadedBug) Operations() ([]bug.Operation, error) {
return l.Snapshot.Operations, nil
}

View File

@ -0,0 +1,167 @@
package models
import (
"fmt"
"sync"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/lamport"
"github.com/MichaelMure/git-bug/util/timestamp"
)
// IdentityWrapper is an interface used by the GraphQL resolvers to handle an identity.
// Depending on the situation, an Identity can already be fully loaded in memory or not.
// This interface is used to wrap either a lazyIdentity or a loadedIdentity depending on the situation.
type IdentityWrapper interface {
Id() entity.Id
Name() string
Email() (string, error)
AvatarUrl() (string, error)
Keys() ([]*identity.Key, error)
ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error)
DisplayName() string
IsProtected() (bool, error)
LastModificationLamport() (lamport.Time, error)
LastModification() (timestamp.Timestamp, error)
}
var _ IdentityWrapper = &lazyIdentity{}
type lazyIdentity struct {
cache *cache.RepoCache
excerpt *cache.IdentityExcerpt
mu sync.Mutex
id *cache.IdentityCache
}
func NewLazyIdentity(cache *cache.RepoCache, excerpt *cache.IdentityExcerpt) *lazyIdentity {
return &lazyIdentity{
cache: cache,
excerpt: excerpt,
}
}
func (li *lazyIdentity) load() (*cache.IdentityCache, error) {
if li.id != nil {
return li.id, nil
}
li.mu.Lock()
defer li.mu.Unlock()
id, err := li.cache.ResolveIdentity(li.excerpt.Id)
if err != nil {
return nil, fmt.Errorf("cache: missing identity %v", li.excerpt.Id)
}
li.id = id
return id, nil
}
func (li *lazyIdentity) Id() entity.Id {
return li.excerpt.Id
}
func (li *lazyIdentity) Name() string {
return li.excerpt.Name
}
func (li *lazyIdentity) Email() (string, error) {
id, err := li.load()
if err != nil {
return "", err
}
return id.Email(), nil
}
func (li *lazyIdentity) AvatarUrl() (string, error) {
id, err := li.load()
if err != nil {
return "", err
}
return id.AvatarUrl(), nil
}
func (li *lazyIdentity) Keys() ([]*identity.Key, error) {
id, err := li.load()
if err != nil {
return nil, err
}
return id.Keys(), nil
}
func (li *lazyIdentity) ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error) {
id, err := li.load()
if err != nil {
return nil, err
}
return id.ValidKeysAtTime(time), nil
}
func (li *lazyIdentity) DisplayName() string {
return li.excerpt.DisplayName()
}
func (li *lazyIdentity) IsProtected() (bool, error) {
id, err := li.load()
if err != nil {
return false, err
}
return id.IsProtected(), nil
}
func (li *lazyIdentity) LastModificationLamport() (lamport.Time, error) {
id, err := li.load()
if err != nil {
return 0, err
}
return id.LastModificationLamport(), nil
}
func (li *lazyIdentity) LastModification() (timestamp.Timestamp, error) {
id, err := li.load()
if err != nil {
return 0, err
}
return id.LastModification(), nil
}
var _ IdentityWrapper = &loadedIdentity{}
type loadedIdentity struct {
identity.Interface
}
func NewLoadedIdentity(id identity.Interface) *loadedIdentity {
return &loadedIdentity{Interface: id}
}
func (l loadedIdentity) Email() (string, error) {
return l.Interface.Email(), nil
}
func (l loadedIdentity) AvatarUrl() (string, error) {
return l.Interface.AvatarUrl(), nil
}
func (l loadedIdentity) Keys() ([]*identity.Key, error) {
return l.Interface.Keys(), nil
}
func (l loadedIdentity) ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error) {
return l.Interface.ValidKeysAtTime(time), nil
}
func (l loadedIdentity) IsProtected() (bool, error) {
return l.Interface.IsProtected(), nil
}
func (l loadedIdentity) LastModificationLamport() (lamport.Time, error) {
return l.Interface.LastModificationLamport(), nil
}
func (l loadedIdentity) LastModification() (timestamp.Timestamp, error) {
return l.Interface.LastModification(), nil
}

View File

@ -2,32 +2,30 @@ package resolvers
import ( import (
"context" "context"
"time"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/connections" "github.com/MichaelMure/git-bug/graphql/connections"
"github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models" "github.com/MichaelMure/git-bug/graphql/models"
"github.com/MichaelMure/git-bug/identity"
) )
var _ graph.BugResolver = &bugResolver{} var _ graph.BugResolver = &bugResolver{}
type bugResolver struct{} type bugResolver struct{}
func (bugResolver) ID(ctx context.Context, obj *bug.Snapshot) (string, error) { func (bugResolver) ID(_ context.Context, obj models.BugWrapper) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (bugResolver) HumanID(ctx context.Context, obj *bug.Snapshot) (string, error) { func (bugResolver) HumanID(_ context.Context, obj models.BugWrapper) (string, error) {
return obj.Id().Human(), nil return obj.Id().Human(), nil
} }
func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) { func (bugResolver) Status(_ context.Context, obj models.BugWrapper) (models.Status, error) {
return convertStatus(obj.Status) return convertStatus(obj.Status())
} }
func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.CommentConnection, error) { func (bugResolver) Comments(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.CommentConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -55,10 +53,15 @@ func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *strin
}, nil }, nil
} }
return connections.CommentCon(obj.Comments, edger, conMaker, input) comments, err := obj.Comments()
if err != nil {
return nil, err
}
return connections.CommentCon(comments, edger, conMaker, input)
} }
func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.OperationConnection, error) { func (bugResolver) Operations(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.OperationConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -82,10 +85,15 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str
}, nil }, nil
} }
return connections.OperationCon(obj.Operations, edger, conMaker, input) ops, err := obj.Operations()
if err != nil {
return nil, err
}
return connections.OperationCon(ops, edger, conMaker, input)
} }
func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error) { func (bugResolver) Timeline(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -109,15 +117,15 @@ func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *strin
}, nil }, nil
} }
return connections.TimelineItemCon(obj.Timeline, edger, conMaker, input) timeline, err := obj.Timeline()
if err != nil {
return nil, err
}
return connections.TimelineItemCon(timeline, edger, conMaker, input)
} }
func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (*time.Time, error) { func (bugResolver) Actors(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
t := obj.LastEditTime()
return &t, nil
}
func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -125,14 +133,14 @@ func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string,
Last: last, Last: last,
} }
edger := func(actor identity.Interface, offset int) connections.Edge { edger := func(actor models.IdentityWrapper, offset int) connections.Edge {
return models.IdentityEdge{ return models.IdentityEdge{
Node: actor, Node: actor,
Cursor: connections.OffsetToCursor(offset), Cursor: connections.OffsetToCursor(offset),
} }
} }
conMaker := func(edges []*models.IdentityEdge, nodes []identity.Interface, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) { conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
return &models.IdentityConnection{ return &models.IdentityConnection{
Edges: edges, Edges: edges,
Nodes: nodes, Nodes: nodes,
@ -141,10 +149,15 @@ func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string,
}, nil }, nil
} }
return connections.IdentityCon(obj.Actors, edger, conMaker, input) actors, err := obj.Actors()
if err != nil {
return nil, err
}
return connections.IdentityCon(actors, edger, conMaker, input)
} }
func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) { func (bugResolver) Participants(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -152,14 +165,14 @@ func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *s
Last: last, Last: last,
} }
edger := func(participant identity.Interface, offset int) connections.Edge { edger := func(participant models.IdentityWrapper, offset int) connections.Edge {
return models.IdentityEdge{ return models.IdentityEdge{
Node: participant, Node: participant,
Cursor: connections.OffsetToCursor(offset), Cursor: connections.OffsetToCursor(offset),
} }
} }
conMaker := func(edges []*models.IdentityEdge, nodes []identity.Interface, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) { conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
return &models.IdentityConnection{ return &models.IdentityConnection{
Edges: edges, Edges: edges,
Nodes: nodes, Nodes: nodes,
@ -168,5 +181,10 @@ func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *s
}, nil }, nil
} }
return connections.IdentityCon(obj.Participants, edger, conMaker, input) participants, err := obj.Participants()
if err != nil {
return nil, err
}
return connections.IdentityCon(participants, edger, conMaker, input)
} }

View File

@ -11,14 +11,14 @@ var _ graph.ColorResolver = &colorResolver{}
type colorResolver struct{} type colorResolver struct{}
func (colorResolver) R(ctx context.Context, obj *color.RGBA) (int, error) { func (colorResolver) R(_ context.Context, obj *color.RGBA) (int, error) {
return int(obj.R), nil return int(obj.R), nil
} }
func (colorResolver) G(ctx context.Context, obj *color.RGBA) (int, error) { func (colorResolver) G(_ context.Context, obj *color.RGBA) (int, error) {
return int(obj.G), nil return int(obj.G), nil
} }
func (colorResolver) B(ctx context.Context, obj *color.RGBA) (int, error) { func (colorResolver) B(_ context.Context, obj *color.RGBA) (int, error) {
return int(obj.B), nil return int(obj.B), nil
} }

View File

@ -0,0 +1,17 @@
package resolvers
import (
"context"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.CommentResolver = &commentResolver{}
type commentResolver struct{}
func (c commentResolver) Author(_ context.Context, obj *bug.Comment) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}

View File

@ -4,18 +4,18 @@ import (
"context" "context"
"github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/graphql/models"
) )
var _ graph.IdentityResolver = &identityResolver{} var _ graph.IdentityResolver = &identityResolver{}
type identityResolver struct{} type identityResolver struct{}
func (identityResolver) ID(ctx context.Context, obj identity.Interface) (string, error) { func (identityResolver) ID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (r identityResolver) HumanID(ctx context.Context, obj identity.Interface) (string, error) { func (r identityResolver) HumanID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
return obj.Id().Human(), nil return obj.Id().Human(), nil
} }

View File

@ -23,7 +23,7 @@ func (r mutationResolver) getRepo(ref *string) (*cache.RepoCache, error) {
return r.cache.DefaultRepo() return r.cache.DefaultRepo()
} }
func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error) { func (r mutationResolver) NewBug(_ context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -36,12 +36,12 @@ func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput)
return &models.NewBugPayload{ return &models.NewBugPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
}, nil }, nil
} }
func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) { func (r mutationResolver) AddComment(_ context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -59,12 +59,12 @@ func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommen
return &models.AddCommentPayload{ return &models.AddCommentPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
}, nil }, nil
} }
func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) { func (r mutationResolver) ChangeLabels(_ context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -87,13 +87,13 @@ func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.Change
return &models.ChangeLabelPayload{ return &models.ChangeLabelPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
Results: resultsPtr, Results: resultsPtr,
}, nil }, nil
} }
func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) { func (r mutationResolver) OpenBug(_ context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -111,12 +111,12 @@ func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput
return &models.OpenBugPayload{ return &models.OpenBugPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
}, nil }, nil
} }
func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) { func (r mutationResolver) CloseBug(_ context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -134,12 +134,12 @@ func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInp
return &models.CloseBugPayload{ return &models.CloseBugPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
}, nil }, nil
} }
func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) { func (r mutationResolver) SetTitle(_ context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -157,12 +157,12 @@ func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInp
return &models.SetTitlePayload{ return &models.SetTitlePayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
Operation: op, Operation: op,
}, nil }, nil
} }
func (r mutationResolver) Commit(ctx context.Context, input models.CommitInput) (*models.CommitPayload, error) { func (r mutationResolver) Commit(_ context.Context, input models.CommitInput) (*models.CommitPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -180,11 +180,11 @@ func (r mutationResolver) Commit(ctx context.Context, input models.CommitInput)
return &models.CommitPayload{ return &models.CommitPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
}, nil }, nil
} }
func (r mutationResolver) CommitAsNeeded(ctx context.Context, input models.CommitAsNeededInput) (*models.CommitAsNeededPayload, error) { func (r mutationResolver) CommitAsNeeded(_ context.Context, input models.CommitAsNeededInput) (*models.CommitAsNeededPayload, error) {
repo, err := r.getRepo(input.RepoRef) repo, err := r.getRepo(input.RepoRef)
if err != nil { if err != nil {
return nil, err return nil, err
@ -202,6 +202,6 @@ func (r mutationResolver) CommitAsNeeded(ctx context.Context, input models.Commi
return &models.CommitAsNeededPayload{ return &models.CommitAsNeededPayload{
ClientMutationID: input.ClientMutationID, ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(), Bug: models.NewLoadedBug(b.Snapshot()),
}, nil }, nil
} }

View File

@ -14,11 +14,15 @@ var _ graph.CreateOperationResolver = createOperationResolver{}
type createOperationResolver struct{} type createOperationResolver struct{}
func (createOperationResolver) ID(ctx context.Context, obj *bug.CreateOperation) (string, error) { func (createOperationResolver) ID(_ context.Context, obj *bug.CreateOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (createOperationResolver) Date(ctx context.Context, obj *bug.CreateOperation) (*time.Time, error) { func (createOperationResolver) Author(_ context.Context, obj *bug.CreateOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (createOperationResolver) Date(_ context.Context, obj *bug.CreateOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }
@ -27,11 +31,15 @@ var _ graph.AddCommentOperationResolver = addCommentOperationResolver{}
type addCommentOperationResolver struct{} type addCommentOperationResolver struct{}
func (addCommentOperationResolver) ID(ctx context.Context, obj *bug.AddCommentOperation) (string, error) { func (addCommentOperationResolver) ID(_ context.Context, obj *bug.AddCommentOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (addCommentOperationResolver) Date(ctx context.Context, obj *bug.AddCommentOperation) (*time.Time, error) { func (addCommentOperationResolver) Author(_ context.Context, obj *bug.AddCommentOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (addCommentOperationResolver) Date(_ context.Context, obj *bug.AddCommentOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }
@ -40,15 +48,19 @@ var _ graph.EditCommentOperationResolver = editCommentOperationResolver{}
type editCommentOperationResolver struct{} type editCommentOperationResolver struct{}
func (editCommentOperationResolver) ID(ctx context.Context, obj *bug.EditCommentOperation) (string, error) { func (editCommentOperationResolver) ID(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (editCommentOperationResolver) Target(ctx context.Context, obj *bug.EditCommentOperation) (string, error) { func (editCommentOperationResolver) Target(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
return obj.Target.String(), nil return obj.Target.String(), nil
} }
func (editCommentOperationResolver) Date(ctx context.Context, obj *bug.EditCommentOperation) (*time.Time, error) { func (editCommentOperationResolver) Author(_ context.Context, obj *bug.EditCommentOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (editCommentOperationResolver) Date(_ context.Context, obj *bug.EditCommentOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }
@ -57,11 +69,15 @@ var _ graph.LabelChangeOperationResolver = labelChangeOperationResolver{}
type labelChangeOperationResolver struct{} type labelChangeOperationResolver struct{}
func (labelChangeOperationResolver) ID(ctx context.Context, obj *bug.LabelChangeOperation) (string, error) { func (labelChangeOperationResolver) ID(_ context.Context, obj *bug.LabelChangeOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (labelChangeOperationResolver) Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) { func (labelChangeOperationResolver) Author(_ context.Context, obj *bug.LabelChangeOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (labelChangeOperationResolver) Date(_ context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }
@ -70,16 +86,20 @@ var _ graph.SetStatusOperationResolver = setStatusOperationResolver{}
type setStatusOperationResolver struct{} type setStatusOperationResolver struct{}
func (setStatusOperationResolver) ID(ctx context.Context, obj *bug.SetStatusOperation) (string, error) { func (setStatusOperationResolver) ID(_ context.Context, obj *bug.SetStatusOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (setStatusOperationResolver) Date(ctx context.Context, obj *bug.SetStatusOperation) (*time.Time, error) { func (setStatusOperationResolver) Author(_ context.Context, obj *bug.SetStatusOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (setStatusOperationResolver) Date(_ context.Context, obj *bug.SetStatusOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }
func (setStatusOperationResolver) Status(ctx context.Context, obj *bug.SetStatusOperation) (models.Status, error) { func (setStatusOperationResolver) Status(_ context.Context, obj *bug.SetStatusOperation) (models.Status, error) {
return convertStatus(obj.Status) return convertStatus(obj.Status)
} }
@ -87,11 +107,15 @@ var _ graph.SetTitleOperationResolver = setTitleOperationResolver{}
type setTitleOperationResolver struct{} type setTitleOperationResolver struct{}
func (setTitleOperationResolver) ID(ctx context.Context, obj *bug.SetTitleOperation) (string, error) { func (setTitleOperationResolver) ID(_ context.Context, obj *bug.SetTitleOperation) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (setTitleOperationResolver) Date(ctx context.Context, obj *bug.SetTitleOperation) (*time.Time, error) { func (setTitleOperationResolver) Author(_ context.Context, obj *bug.SetTitleOperation) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (setTitleOperationResolver) Date(_ context.Context, obj *bug.SetTitleOperation) (*time.Time, error) {
t := obj.Time() t := obj.Time()
return &t, nil return &t, nil
} }

View File

@ -14,7 +14,7 @@ type rootQueryResolver struct {
cache *cache.MultiRepoCache cache *cache.MultiRepoCache
} }
func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*models.Repository, error) { func (r rootQueryResolver) DefaultRepository(_ context.Context) (*models.Repository, error) {
repo, err := r.cache.DefaultRepo() repo, err := r.cache.DefaultRepo()
if err != nil { if err != nil {
@ -27,7 +27,7 @@ func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*models.Repos
}, nil }, nil
} }
func (r rootQueryResolver) Repository(ctx context.Context, ref string) (*models.Repository, error) { func (r rootQueryResolver) Repository(_ context.Context, ref string) (*models.Repository, error) {
repo, err := r.cache.ResolveRepo(ref) repo, err := r.cache.ResolveRepo(ref)
if err != nil { if err != nil {

View File

@ -9,14 +9,13 @@ import (
"github.com/MichaelMure/git-bug/graphql/connections" "github.com/MichaelMure/git-bug/graphql/connections"
"github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models" "github.com/MichaelMure/git-bug/graphql/models"
"github.com/MichaelMure/git-bug/identity"
) )
var _ graph.RepositoryResolver = &repoResolver{} var _ graph.RepositoryResolver = &repoResolver{}
type repoResolver struct{} 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) { func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (*models.BugConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -49,22 +48,21 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *
// The conMaker will finally load and compile bugs from git to replace the selected edges // The conMaker will finally load and compile bugs from git to replace the selected edges
conMaker := func(lazyBugEdges []*connections.LazyBugEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.BugConnection, error) { conMaker := func(lazyBugEdges []*connections.LazyBugEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.BugConnection, error) {
edges := make([]*models.BugEdge, len(lazyBugEdges)) edges := make([]*models.BugEdge, len(lazyBugEdges))
nodes := make([]*bug.Snapshot, len(lazyBugEdges)) nodes := make([]models.BugWrapper, len(lazyBugEdges))
for i, lazyBugEdge := range lazyBugEdges { for i, lazyBugEdge := range lazyBugEdges {
b, err := obj.Repo.ResolveBug(lazyBugEdge.Id) excerpt, err := obj.Repo.ResolveBugExcerpt(lazyBugEdge.Id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
snap := b.Snapshot() b := models.NewLazyBug(obj.Repo, excerpt)
edges[i] = &models.BugEdge{ edges[i] = &models.BugEdge{
Cursor: lazyBugEdge.Cursor, Cursor: lazyBugEdge.Cursor,
Node: snap, Node: b,
} }
nodes[i] = snap nodes[i] = b
} }
return &models.BugConnection{ return &models.BugConnection{
@ -78,17 +76,16 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *
return connections.LazyBugCon(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) { func (repoResolver) Bug(_ context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error) {
b, err := obj.Repo.ResolveBugPrefix(prefix) excerpt, err := obj.Repo.ResolveBugExcerptPrefix(prefix)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return b.Snapshot(), nil return models.NewLazyBug(obj.Repo, excerpt), nil
} }
func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) { func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,
@ -110,22 +107,21 @@ func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, a
// The conMaker will finally load and compile identities from git to replace the selected edges // The conMaker will finally load and compile identities from git to replace the selected edges
conMaker := func(lazyIdentityEdges []*connections.LazyIdentityEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) { conMaker := func(lazyIdentityEdges []*connections.LazyIdentityEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
edges := make([]*models.IdentityEdge, len(lazyIdentityEdges)) edges := make([]*models.IdentityEdge, len(lazyIdentityEdges))
nodes := make([]identity.Interface, len(lazyIdentityEdges)) nodes := make([]models.IdentityWrapper, len(lazyIdentityEdges))
for k, lazyIdentityEdge := range lazyIdentityEdges { for k, lazyIdentityEdge := range lazyIdentityEdges {
i, err := obj.Repo.ResolveIdentity(lazyIdentityEdge.Id) excerpt, err := obj.Repo.ResolveIdentityExcerpt(lazyIdentityEdge.Id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ii := identity.Interface(i.Identity) i := models.NewLazyIdentity(obj.Repo, excerpt)
edges[k] = &models.IdentityEdge{ edges[k] = &models.IdentityEdge{
Cursor: lazyIdentityEdge.Cursor, Cursor: lazyIdentityEdge.Cursor,
Node: i.Identity, Node: i,
} }
nodes[k] = ii nodes[k] = i
} }
return &models.IdentityConnection{ return &models.IdentityConnection{
@ -139,27 +135,25 @@ func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, a
return connections.LazyIdentityCon(source, edger, conMaker, input) return connections.LazyIdentityCon(source, edger, conMaker, input)
} }
func (repoResolver) Identity(ctx context.Context, obj *models.Repository, prefix string) (identity.Interface, error) { func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) {
i, err := obj.Repo.ResolveIdentityPrefix(prefix) excerpt, err := obj.Repo.ResolveIdentityExcerptPrefix(prefix)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return i.Identity, nil return models.NewLazyIdentity(obj.Repo, excerpt), nil
} }
func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (identity.Interface, error) { func (repoResolver) UserIdentity(_ context.Context, obj *models.Repository) (models.IdentityWrapper, error) {
i, err := obj.Repo.GetUserIdentity() excerpt, err := obj.Repo.GetUserIdentityExcerpt()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return i.Identity, nil return models.NewLazyIdentity(obj.Repo, excerpt), nil
} }
func (resolver repoResolver) ValidLabels(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) { func (resolver repoResolver) ValidLabels(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) {
input := models.ConnectionInput{ input := models.ConnectionInput{
Before: before, Before: before,
After: after, After: after,

View File

@ -42,6 +42,10 @@ func (RootResolver) Color() graph.ColorResolver {
return &colorResolver{} return &colorResolver{}
} }
func (r RootResolver) Comment() graph.CommentResolver {
return &commentResolver{}
}
func (RootResolver) Label() graph.LabelResolver { func (RootResolver) Label() graph.LabelResolver {
return &labelResolver{} return &labelResolver{}
} }

View File

@ -13,7 +13,7 @@ var _ graph.CommentHistoryStepResolver = commentHistoryStepResolver{}
type commentHistoryStepResolver struct{} type commentHistoryStepResolver struct{}
func (commentHistoryStepResolver) Date(ctx context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) { func (commentHistoryStepResolver) Date(_ context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) {
t := obj.UnixTime.Time() t := obj.UnixTime.Time()
return &t, nil return &t, nil
} }
@ -22,16 +22,20 @@ var _ graph.AddCommentTimelineItemResolver = addCommentTimelineItemResolver{}
type addCommentTimelineItemResolver struct{} type addCommentTimelineItemResolver struct{}
func (addCommentTimelineItemResolver) ID(ctx context.Context, obj *bug.AddCommentTimelineItem) (string, error) { func (addCommentTimelineItemResolver) ID(_ context.Context, obj *bug.AddCommentTimelineItem) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (addCommentTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) { func (addCommentTimelineItemResolver) Author(_ context.Context, obj *bug.AddCommentTimelineItem) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (addCommentTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
t := obj.CreatedAt.Time() t := obj.CreatedAt.Time()
return &t, nil return &t, nil
} }
func (addCommentTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) { func (addCommentTimelineItemResolver) LastEdit(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
t := obj.LastEdit.Time() t := obj.LastEdit.Time()
return &t, nil return &t, nil
} }
@ -40,16 +44,20 @@ var _ graph.CreateTimelineItemResolver = createTimelineItemResolver{}
type createTimelineItemResolver struct{} type createTimelineItemResolver struct{}
func (createTimelineItemResolver) ID(ctx context.Context, obj *bug.CreateTimelineItem) (string, error) { func (createTimelineItemResolver) ID(_ context.Context, obj *bug.CreateTimelineItem) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (createTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) { func (r createTimelineItemResolver) Author(_ context.Context, obj *bug.CreateTimelineItem) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (createTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
t := obj.CreatedAt.Time() t := obj.CreatedAt.Time()
return &t, nil return &t, nil
} }
func (createTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) { func (createTimelineItemResolver) LastEdit(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
t := obj.LastEdit.Time() t := obj.LastEdit.Time()
return &t, nil return &t, nil
} }
@ -58,11 +66,15 @@ var _ graph.LabelChangeTimelineItemResolver = labelChangeTimelineItem{}
type labelChangeTimelineItem struct{} type labelChangeTimelineItem struct{}
func (labelChangeTimelineItem) ID(ctx context.Context, obj *bug.LabelChangeTimelineItem) (string, error) { func (labelChangeTimelineItem) ID(_ context.Context, obj *bug.LabelChangeTimelineItem) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (labelChangeTimelineItem) Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) { func (i labelChangeTimelineItem) Author(_ context.Context, obj *bug.LabelChangeTimelineItem) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (labelChangeTimelineItem) Date(_ context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) {
t := obj.UnixTime.Time() t := obj.UnixTime.Time()
return &t, nil return &t, nil
} }
@ -71,16 +83,20 @@ var _ graph.SetStatusTimelineItemResolver = setStatusTimelineItem{}
type setStatusTimelineItem struct{} type setStatusTimelineItem struct{}
func (setStatusTimelineItem) ID(ctx context.Context, obj *bug.SetStatusTimelineItem) (string, error) { func (setStatusTimelineItem) ID(_ context.Context, obj *bug.SetStatusTimelineItem) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (setStatusTimelineItem) Date(ctx context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) { func (i setStatusTimelineItem) Author(_ context.Context, obj *bug.SetStatusTimelineItem) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (setStatusTimelineItem) Date(_ context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) {
t := obj.UnixTime.Time() t := obj.UnixTime.Time()
return &t, nil return &t, nil
} }
func (setStatusTimelineItem) Status(ctx context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error) { func (setStatusTimelineItem) Status(_ context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error) {
return convertStatus(obj.Status) return convertStatus(obj.Status)
} }
@ -88,11 +104,15 @@ var _ graph.SetTitleTimelineItemResolver = setTitleTimelineItem{}
type setTitleTimelineItem struct{} type setTitleTimelineItem struct{}
func (setTitleTimelineItem) ID(ctx context.Context, obj *bug.SetTitleTimelineItem) (string, error) { func (setTitleTimelineItem) ID(_ context.Context, obj *bug.SetTitleTimelineItem) (string, error) {
return obj.Id().String(), nil return obj.Id().String(), nil
} }
func (setTitleTimelineItem) Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) { func (i setTitleTimelineItem) Author(_ context.Context, obj *bug.SetTitleTimelineItem) (models.IdentityWrapper, error) {
return models.NewLoadedIdentity(obj.Author), nil
}
func (setTitleTimelineItem) Date(_ context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) {
t := obj.UnixTime.Time() t := obj.UnixTime.Time()
return &t, nil return &t, nil
} }

View File

@ -227,28 +227,11 @@ func SetUserIdentity(repo repository.RepoConfig, identity *Identity) error {
// GetUserIdentity read the current user identity, set with a git config entry // GetUserIdentity read the current user identity, set with a git config entry
func GetUserIdentity(repo repository.Repo) (*Identity, error) { func GetUserIdentity(repo repository.Repo) (*Identity, error) {
configs, err := repo.LocalConfig().ReadAll(identityConfigKey) id, err := GetUserIdentityId(repo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(configs) == 0 {
return nil, ErrNoIdentitySet
}
if len(configs) > 1 {
return nil, ErrMultipleIdentitiesSet
}
var id entity.Id
for _, val := range configs {
id = entity.Id(val)
}
if err := id.Validate(); err != nil {
return nil, err
}
i, err := ReadLocal(repo, id) i, err := ReadLocal(repo, id)
if err == ErrIdentityNotExist { if err == ErrIdentityNotExist {
innerErr := repo.LocalConfig().RemoveAll(identityConfigKey) innerErr := repo.LocalConfig().RemoveAll(identityConfigKey)
@ -261,6 +244,32 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) {
return i, nil return i, nil
} }
func GetUserIdentityId(repo repository.Repo) (entity.Id, error) {
configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
if err != nil {
return entity.UnsetId, err
}
if len(configs) == 0 {
return entity.UnsetId, ErrNoIdentitySet
}
if len(configs) > 1 {
return entity.UnsetId, ErrMultipleIdentitiesSet
}
var id entity.Id
for _, val := range configs {
id = entity.Id(val)
}
if err := id.Validate(); err != nil {
return entity.UnsetId, err
}
return id, nil
}
// IsUserIdentitySet say if the user has set his identity // IsUserIdentitySet say if the user has set his identity
func IsUserIdentitySet(repo repository.Repo) (bool, error) { func IsUserIdentitySet(repo repository.Repo) (bool, error) {
configs, err := repo.LocalConfig().ReadAll(identityConfigKey) configs, err := repo.LocalConfig().ReadAll(identityConfigKey)