bug: add a new BugExerpt that hold a subset of a bug state for efficient sorting and retrieval

This commit is contained in:
Michael Muré 2018-08-23 19:19:16 +02:00
parent 16f55e3f4d
commit e7648996c8
No known key found for this signature in database
GPG Key ID: A4457C029293126F
11 changed files with 171 additions and 16 deletions

View File

@ -579,6 +579,16 @@ func formatHumanId(id string) string {
return fmt.Sprintf(format, id) return fmt.Sprintf(format, id)
} }
// CreateLamportTime return the Lamport time of creation
func (bug *Bug) CreateLamportTime() util.LamportTime {
return bug.createTime
}
// EditLamportTime return the Lamport time of the last edit
func (bug *Bug) EditLamportTime() util.LamportTime {
return bug.editTime
}
// Lookup for the very first operation of the bug. // Lookup for the very first operation of the bug.
// For a valid Bug, this operation should be a CreateOp // For a valid Bug, this operation should be a CreateOp
func (bug *Bug) FirstOp() Operation { func (bug *Bug) FirstOp() Operation {

View File

@ -2,6 +2,7 @@ package bug
import ( import (
"github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util"
) )
type Interface interface { type Interface interface {
@ -38,6 +39,12 @@ type Interface interface {
// Compile a bug in a easily usable snapshot // Compile a bug in a easily usable snapshot
Compile() Snapshot Compile() Snapshot
// CreateLamportTime return the Lamport time of creation
CreateLamportTime() util.LamportTime
// EditLamportTime return the Lamport time of the last edit
EditLamportTime() util.LamportTime
} }
func bugFromInterface(bug Interface) *Bug { func bugFromInterface(bug Interface) *Bug {

View File

@ -23,6 +23,8 @@ type Operation interface {
OpType() OperationType OpType() OperationType
// Time return the time when the operation was added // Time return the time when the operation was added
Time() time.Time Time() time.Time
// unixTime return the unix timestamp when the operation was added
UnixTime() int64
// Apply the operation to a Snapshot to create the final state // Apply the operation to a Snapshot to create the final state
Apply(snapshot Snapshot) Snapshot Apply(snapshot Snapshot) Snapshot
// Files return the files needed by this operation // Files return the files needed by this operation
@ -36,7 +38,7 @@ type Operation interface {
type OpBase struct { type OpBase struct {
OperationType OperationType OperationType OperationType
Author Person Author Person
UnixTime int64 unixTime int64
} }
// NewOpBase is the constructor for an OpBase // NewOpBase is the constructor for an OpBase
@ -44,7 +46,7 @@ func NewOpBase(opType OperationType, author Person) OpBase {
return OpBase{ return OpBase{
OperationType: opType, OperationType: opType,
Author: author, Author: author,
UnixTime: time.Now().Unix(), unixTime: time.Now().Unix(),
} }
} }
@ -55,7 +57,12 @@ func (op OpBase) OpType() OperationType {
// Time return the time when the operation was added // Time return the time when the operation was added
func (op OpBase) Time() time.Time { func (op OpBase) Time() time.Time {
return time.Unix(op.UnixTime, 0) return time.Unix(op.unixTime, 0)
}
// unixTime return the unix timestamp when the operation was added
func (op OpBase) UnixTime() int64 {
return op.unixTime
} }
// Files return the files needed by this operation // Files return the files needed by this operation

View File

@ -21,7 +21,7 @@ func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
Message: op.Message, Message: op.Message,
Author: op.Author, Author: op.Author,
Files: op.files, Files: op.files,
UnixTime: op.UnixTime, UnixTime: op.UnixTime(),
} }
snapshot.Comments = append(snapshot.Comments, comment) snapshot.Comments = append(snapshot.Comments, comment)

View File

@ -22,7 +22,7 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
{ {
Message: op.Message, Message: op.Message,
Author: op.Author, Author: op.Author,
UnixTime: op.UnixTime, UnixTime: op.UnixTime(),
}, },
} }
snapshot.Author = op.Author snapshot.Author = op.Author

View File

@ -21,7 +21,7 @@ func TestCreate(t *testing.T) {
expected := bug.Snapshot{ expected := bug.Snapshot{
Title: "title", Title: "title",
Comments: []bug.Comment{ Comments: []bug.Comment{
{Author: rene, Message: "message", UnixTime: create.UnixTime}, {Author: rene, Message: "message", UnixTime: create.UnixTime()},
}, },
Author: rene, Author: rene,
CreatedAt: create.Time(), CreatedAt: create.Time(),

View File

@ -37,10 +37,19 @@ func (snap Snapshot) Summary() string {
} }
// Return the last time a bug was modified // Return the last time a bug was modified
func (snap Snapshot) LastEdit() time.Time { func (snap Snapshot) LastEditTime() time.Time {
if len(snap.Operations) == 0 { if len(snap.Operations) == 0 {
return time.Unix(0, 0) return time.Unix(0, 0)
} }
return snap.Operations[len(snap.Operations)-1].Time() return snap.Operations[len(snap.Operations)-1].Time()
} }
// Return the last timestamp a bug was modified
func (snap Snapshot) LastEditUnix() int64 {
if len(snap.Operations) == 0 {
return 0
}
return snap.Operations[len(snap.Operations)-1].UnixTime()
}

92
cache/bug_excerpt.go vendored Normal file
View File

@ -0,0 +1,92 @@
package cache
import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/util"
)
// BugExcerpt hold a subset of the bug values to be able to sort and filter bugs
// efficiently without having to read and compile each raw bugs.
type BugExcerpt struct {
Id string
CreateLamportTime util.LamportTime
EditLamportTime util.LamportTime
CreateUnixTime int64
EditUnixTime int64
Status bug.Status
Author bug.Person
}
func NewBugExcerpt(b *bug.Bug, snap bug.Snapshot) BugExcerpt {
return BugExcerpt{
Id: b.Id(),
CreateLamportTime: b.CreateLamportTime(),
EditLamportTime: b.EditLamportTime(),
CreateUnixTime: b.FirstOp().UnixTime(),
EditUnixTime: snap.LastEditUnix(),
Status: snap.Status,
Author: snap.Author,
}
}
/*
* Sorting
*/
type BugsByCreationTime []*BugExcerpt
func (b BugsByCreationTime) Len() int {
return len(b)
}
func (b BugsByCreationTime) Less(i, j int) bool {
if b[i].CreateLamportTime < b[j].CreateLamportTime {
return true
}
if b[i].CreateLamportTime > b[j].CreateLamportTime {
return false
}
// When the logical clocks are identical, that means we had a concurrent
// edition. In this case we rely on the timestamp. While the timestamp might
// be incorrect due to a badly set clock, the drift in sorting is bounded
// by the first sorting using the logical clock. That means that if users
// synchronize their bugs regularly, the timestamp will rarely be used, and
// should still provide a kinda accurate sorting when needed.
return b[i].CreateUnixTime < b[j].CreateUnixTime
}
func (b BugsByCreationTime) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
type BugsByEditTime []*BugExcerpt
func (b BugsByEditTime) Len() int {
return len(b)
}
func (b BugsByEditTime) Less(i, j int) bool {
if b[i].EditLamportTime < b[j].EditLamportTime {
return true
}
if b[i].EditLamportTime > b[j].EditLamportTime {
return false
}
// When the logical clocks are identical, that means we had a concurrent
// edition. In this case we rely on the timestamp. While the timestamp might
// be incorrect due to a badly set clock, the drift in sorting is bounded
// by the first sorting using the logical clock. That means that if users
// synchronize their bugs regularly, the timestamp will rarely be used, and
// should still provide a kinda accurate sorting when needed.
return b[i].EditUnixTime < b[j].EditUnixTime
}
func (b BugsByEditTime) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}

View File

@ -34,6 +34,7 @@ type Resolvers interface {
Bug_status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) Bug_status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
Bug_lastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error) Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
@ -78,6 +79,7 @@ type AddCommentOperationResolver interface {
type BugResolver interface { type BugResolver interface {
Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error) Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error) Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
} }
@ -124,6 +126,10 @@ func (s shortMapper) Bug_status(ctx context.Context, obj *bug.Snapshot) (models.
return s.r.Bug().Status(ctx, obj) return s.r.Bug().Status(ctx, obj)
} }
func (s shortMapper) Bug_lastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {
return s.r.Bug().LastEdit(ctx, obj)
}
func (s shortMapper) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error) { func (s shortMapper) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error) {
return s.r.Bug().Comments(ctx, obj, after, before, first, last) return s.r.Bug().Comments(ctx, obj, after, before, first, last)
} }
@ -494,14 +500,33 @@ func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.Co
} }
func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler { func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
rctx := graphql.GetResolverContext(ctx) ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
rctx.Object = "Bug" Object: "Bug",
rctx.Args = nil Args: nil,
rctx.Field = field Field: field,
rctx.PushField(field.Alias) })
defer rctx.Pop() return graphql.Defer(func() (ret graphql.Marshaler) {
res := obj.LastEdit() defer func() {
return graphql.MarshalTime(res) if r := recover(); r != nil {
userErr := ec.Recover(ctx, r)
ec.Error(ctx, userErr)
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
return ec.resolvers.Bug_lastEdit(ctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(time.Time)
return graphql.MarshalTime(res)
})
} }
func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler { func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {

View File

@ -2,6 +2,7 @@ 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"
@ -67,3 +68,7 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str
return connections.BugOperationCon(obj.Operations, edger, conMaker, input) return connections.BugOperationCon(obj.Operations, edger, conMaker, input)
} }
func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {
return obj.LastEditTime(), nil
}

View File

@ -290,7 +290,7 @@ func (bt *bugTable) render(v *gocui.View, maxX int) {
title := util.LeftPaddedString(snap.Title, columnWidths["title"], 2) title := util.LeftPaddedString(snap.Title, columnWidths["title"], 2)
author := util.LeftPaddedString(person.Name, columnWidths["author"], 2) author := util.LeftPaddedString(person.Name, columnWidths["author"], 2)
summary := util.LeftPaddedString(snap.Summary(), columnWidths["summary"], 2) summary := util.LeftPaddedString(snap.Summary(), columnWidths["summary"], 2)
lastEdit := util.LeftPaddedString(humanize.Time(snap.LastEdit()), columnWidths["lastEdit"], 2) lastEdit := util.LeftPaddedString(humanize.Time(snap.LastEditTime()), columnWidths["lastEdit"], 2)
fmt.Fprintf(v, "%s %s %s %s %s %s\n", fmt.Fprintf(v, "%s %s %s %s %s %s\n",
util.Cyan(id), util.Cyan(id),