aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bug/op_add_comment.go3
-rw-r--r--bug/op_create.go3
-rw-r--r--bug/op_create_test.go6
-rw-r--r--bug/op_edit_comment.go2
-rw-r--r--bug/op_label_change.go2
-rw-r--r--bug/op_set_status.go1
-rw-r--r--bug/op_set_title.go1
-rw-r--r--bug/snapshot.go36
-rw-r--r--cache/bug_excerpt.go22
-rw-r--r--cache/filter.go54
-rw-r--r--cache/query.go10
-rw-r--r--cache/query_test.go3
-rw-r--r--doc/queries.md21
-rw-r--r--graphql/graph/gen_graph.go189
-rw-r--r--graphql/graphql_test.go30
-rw-r--r--graphql/resolvers/bug.go21
-rw-r--r--graphql/schema/bug.graphql2
17 files changed, 368 insertions, 38 deletions
diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go
index c1e0838b..8cde20e8 100644
--- a/bug/op_add_comment.go
+++ b/bug/op_add_comment.go
@@ -29,6 +29,9 @@ func (op *AddCommentOperation) Hash() (git.Hash, error) {
}
func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
+ snapshot.addActor(op.Author)
+ snapshot.addParticipant(op.Author)
+
hash, err := op.Hash()
if err != nil {
// Should never error unless a programming error happened
diff --git a/bug/op_create.go b/bug/op_create.go
index d5852db9..f3757f89 100644
--- a/bug/op_create.go
+++ b/bug/op_create.go
@@ -30,6 +30,9 @@ func (op *CreateOperation) Hash() (git.Hash, error) {
}
func (op *CreateOperation) Apply(snapshot *Snapshot) {
+ snapshot.addActor(op.Author)
+ snapshot.addParticipant(op.Author)
+
hash, err := op.Hash()
if err != nil {
// Should never error unless a programming error happened
diff --git a/bug/op_create_test.go b/bug/op_create_test.go
index aa1d8c10..2a88f256 100644
--- a/bug/op_create_test.go
+++ b/bug/op_create_test.go
@@ -35,8 +35,10 @@ func TestCreate(t *testing.T) {
Comments: []Comment{
comment,
},
- Author: rene,
- CreatedAt: create.Time(),
+ Author: rene,
+ Participants: []identity.Interface{rene},
+ Actors: []identity.Interface{rene},
+ CreatedAt: create.Time(),
Timeline: []TimelineItem{
&CreateTimelineItem{
CommentTimelineItem: NewCommentTimelineItem(hash, comment),
diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go
index 5a223e01..cf5c2d51 100644
--- a/bug/op_edit_comment.go
+++ b/bug/op_edit_comment.go
@@ -33,6 +33,8 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
// Todo: currently any message can be edited, even by a different author
// crypto signature are needed.
+ snapshot.addActor(op.Author)
+
var target TimelineItem
var commentIndex int
diff --git a/bug/op_label_change.go b/bug/op_label_change.go
index 84542b6e..a2108941 100644
--- a/bug/op_label_change.go
+++ b/bug/op_label_change.go
@@ -31,6 +31,8 @@ func (op *LabelChangeOperation) Hash() (git.Hash, error) {
// Apply apply the operation
func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
+ snapshot.addActor(op.Author)
+
// Add in the set
AddLoop:
for _, added := range op.Added {
diff --git a/bug/op_set_status.go b/bug/op_set_status.go
index 0105d78d..57d4cf22 100644
--- a/bug/op_set_status.go
+++ b/bug/op_set_status.go
@@ -27,6 +27,7 @@ func (op *SetStatusOperation) Hash() (git.Hash, error) {
func (op *SetStatusOperation) Apply(snapshot *Snapshot) {
snapshot.Status = op.Status
+ snapshot.addActor(op.Author)
hash, err := op.Hash()
if err != nil {
diff --git a/bug/op_set_title.go b/bug/op_set_title.go
index 084838cb..ca27adee 100644
--- a/bug/op_set_title.go
+++ b/bug/op_set_title.go
@@ -31,6 +31,7 @@ func (op *SetTitleOperation) Hash() (git.Hash, error) {
func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
snapshot.Title = op.Title
+ snapshot.addActor(op.Author)
hash, err := op.Hash()
if err != nil {
diff --git a/bug/snapshot.go b/bug/snapshot.go
index 83b94416..53f6873a 100644
--- a/bug/snapshot.go
+++ b/bug/snapshot.go
@@ -12,12 +12,14 @@ import (
type Snapshot struct {
id string
- Status Status
- Title string
- Comments []Comment
- Labels []Label
- Author identity.Interface
- CreatedAt time.Time
+ Status Status
+ Title string
+ Comments []Comment
+ Labels []Label
+ Author identity.Interface
+ Actors []identity.Interface
+ Participants []identity.Interface
+ CreatedAt time.Time
Timeline []TimelineItem
@@ -62,3 +64,25 @@ func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) {
return nil, fmt.Errorf("timeline item not found")
}
+
+// append the operation author to the actors list
+func (snap *Snapshot) addActor(actor identity.Interface) {
+ for _, a := range snap.Actors {
+ if actor.Id() == a.Id() {
+ return
+ }
+ }
+
+ snap.Actors = append(snap.Actors, actor)
+}
+
+// append the operation author to the participants list
+func (snap *Snapshot) addParticipant(participant identity.Interface) {
+ for _, p := range snap.Participants {
+ if participant.Id() == p.Id() {
+ return
+ }
+ }
+
+ snap.Participants = append(snap.Participants, participant)
+}
diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go
index a50d8c66..d78def4e 100644
--- a/cache/bug_excerpt.go
+++ b/cache/bug_excerpt.go
@@ -23,10 +23,12 @@ type BugExcerpt struct {
CreateUnixTime int64
EditUnixTime int64
- Status bug.Status
- Labels []bug.Label
- Title string
- LenComments int
+ Status bug.Status
+ Labels []bug.Label
+ Title string
+ LenComments int
+ Actors []string
+ Participants []string
// If author is identity.Bare, LegacyAuthor is set
// If author is identity.Identity, AuthorId is set and data is deported
@@ -44,6 +46,16 @@ type LegacyAuthorExcerpt struct {
}
func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
+ participantsIds := make([]string, len(snap.Participants))
+ for i, participant := range snap.Participants {
+ participantsIds[i] = participant.Id()
+ }
+
+ actorsIds := make([]string, len(snap.Actors))
+ for i, actor := range snap.Actors {
+ actorsIds[i] = actor.Id()
+ }
+
e := &BugExcerpt{
Id: b.Id(),
CreateLamportTime: b.CreateLamportTime(),
@@ -52,6 +64,8 @@ func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
EditUnixTime: snap.LastEditUnix(),
Status: snap.Status,
Labels: snap.Labels,
+ Actors: actorsIds,
+ Participants: participantsIds,
Title: snap.Title,
LenComments: len(snap.Comments),
CreateMetadata: b.FirstOp().AllMetadata(),
diff --git a/cache/filter.go b/cache/filter.go
index 7f010608..48ee6678 100644
--- a/cache/filter.go
+++ b/cache/filter.go
@@ -55,6 +55,40 @@ func LabelFilter(label string) Filter {
}
}
+// ActorFilter return a Filter that match a bug actor
+func ActorFilter(actor string) Filter {
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
+ for _, identityExcerpt := range repoCache.identitiesExcerpts {
+ if strings.Contains(strings.ToLower(identityExcerpt.Name), actor) ||
+ actor == identityExcerpt.Id || actor == identityExcerpt.Login {
+ for _, actorId := range excerpt.Actors {
+ if identityExcerpt.Id == actorId {
+ return true
+ }
+ }
+ }
+ }
+ return false
+ }
+}
+
+// ParticipantFilter return a Filter that match a bug participant
+func ParticipantFilter(participant string) Filter {
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
+ for _, identityExcerpt := range repoCache.identitiesExcerpts {
+ if strings.Contains(strings.ToLower(identityExcerpt.Name), participant) ||
+ participant == identityExcerpt.Id || participant == identityExcerpt.Login {
+ for _, participantId := range excerpt.Participants {
+ if identityExcerpt.Id == participantId {
+ return true
+ }
+ }
+ }
+ }
+ return false
+ }
+}
+
// TitleFilter return a Filter that match if the title contains the given query
func TitleFilter(query string) Filter {
return func(repo *RepoCache, excerpt *BugExcerpt) bool {
@@ -74,11 +108,13 @@ func NoLabelFilter() Filter {
// Filters is a collection of Filter that implement a complex filter
type Filters struct {
- Status []Filter
- Author []Filter
- Label []Filter
- Title []Filter
- NoFilters []Filter
+ Status []Filter
+ Author []Filter
+ Actor []Filter
+ Participant []Filter
+ Label []Filter
+ Title []Filter
+ NoFilters []Filter
}
// Match check if a bug match the set of filters
@@ -91,6 +127,14 @@ func (f *Filters) Match(repoCache *RepoCache, excerpt *BugExcerpt) bool {
return false
}
+ if match := f.orMatch(f.Participant, repoCache, excerpt); !match {
+ return false
+ }
+
+ if match := f.orMatch(f.Actor, repoCache, excerpt); !match {
+ return false
+ }
+
if match := f.andMatch(f.Label, repoCache, excerpt); !match {
return false
}
diff --git a/cache/query.go b/cache/query.go
index 39815d32..633ef1c2 100644
--- a/cache/query.go
+++ b/cache/query.go
@@ -56,13 +56,21 @@ func ParseQuery(query string) (*Query, error) {
f := AuthorFilter(qualifierQuery)
result.Author = append(result.Author, f)
+ case "actor":
+ f := ActorFilter(qualifierQuery)
+ result.Actor = append(result.Actor, f)
+
+ case "participant":
+ f := ParticipantFilter(qualifierQuery)
+ result.Participant = append(result.Participant, f)
+
case "label":
f := LabelFilter(qualifierQuery)
result.Label = append(result.Label, f)
case "title":
f := TitleFilter(qualifierQuery)
- result.Label = append(result.Title, f)
+ result.Title = append(result.Title, f)
case "no":
err := result.parseNoFilter(qualifierQuery)
diff --git a/cache/query_test.go b/cache/query_test.go
index f34b3e6a..9ae62ac4 100644
--- a/cache/query_test.go
+++ b/cache/query_test.go
@@ -19,6 +19,9 @@ func TestQueryParse(t *testing.T) {
{"author:rene", true},
{`author:"René Descartes"`, true},
+ {"actor:bernhard", true},
+ {"participant:leonhard", true},
+
{"label:hello", true},
{`label:"Good first issue"`, true},
diff --git a/doc/queries.md b/doc/queries.md
index 224b59a0..857600e8 100644
--- a/doc/queries.md
+++ b/doc/queries.md
@@ -33,6 +33,27 @@ You can filter based on the person who opened the bug.
| `author:QUERY` | `author:descartes` matches bugs opened by `René Descartes` or `Robert Descartes` |
| | `author:"rené descartes"` matches bugs opened by `René Descartes` |
+### Filtering by participant
+
+You can filter based on the person who participated in the bug (Opened the bug or added a comment).
+
+| Qualifier | Example |
+| --- | --- |
+| `participant:QUERY` | `participant:descartes` matches bugs opened or commented by `René Descartes` or `Robert Descartes` |
+| | `participant:"rené descartes"` matches bugs opened or commented by `René Descartes` |
+
+### Filtering by actor
+
+You can filter based on the person who interacted with the bug.
+
+| Qualifier | Example |
+| --- | --- |
+| `actor:QUERY` | `actor:descartes` matches bugs edited by `René Descartes` or `Robert Descartes` |
+| | `actor:"rené descartes"` matches bugs edited by `René Descartes` |
+| `
+
+**NOTE**: interaction with bugs include: opening the bug, adding comments, adding/removing labels etc...
+
### Filtering by label
You can filter based on the bug's label.
diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go
index 06ebecc7..9da0c665 100644
--- a/graphql/graph/gen_graph.go
+++ b/graphql/graph/gen_graph.go
@@ -81,17 +81,19 @@ type ComplexityRoot struct {
}
Bug struct {
- Id func(childComplexity int) int
- HumanId func(childComplexity int) int
- Status func(childComplexity int) int
- Title func(childComplexity int) int
- Labels func(childComplexity int) int
- Author func(childComplexity int) int
- CreatedAt func(childComplexity int) int
- LastEdit func(childComplexity int) int
- Comments func(childComplexity int, after *string, before *string, first *int, last *int) int
- Timeline func(childComplexity int, after *string, before *string, first *int, last *int) int
- Operations func(childComplexity int, after *string, before *string, first *int, last *int) int
+ Id func(childComplexity int) int
+ HumanId func(childComplexity int) int
+ Status func(childComplexity int) int
+ Title func(childComplexity int) int
+ Labels func(childComplexity int) int
+ Author func(childComplexity int) int
+ Actors func(childComplexity int) int
+ Participants func(childComplexity int) int
+ CreatedAt func(childComplexity int) int
+ LastEdit func(childComplexity int) int
+ Comments func(childComplexity int, after *string, before *string, first *int, last *int) int
+ Timeline func(childComplexity int, after *string, before *string, first *int, last *int) int
+ Operations func(childComplexity int, after *string, before *string, first *int, last *int) int
}
BugConnection struct {
@@ -293,6 +295,9 @@ type AddCommentTimelineItemResolver interface {
type BugResolver interface {
Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
+ Actors(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error)
+ Participants(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error)
+
LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.TimelineItemConnection, error)
@@ -1239,6 +1244,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Bug.Author(childComplexity), true
+ case "Bug.actors":
+ if e.complexity.Bug.Actors == nil {
+ break
+ }
+
+ return e.complexity.Bug.Actors(childComplexity), true
+
+ case "Bug.participants":
+ if e.complexity.Bug.Participants == nil {
+ break
+ }
+
+ return e.complexity.Bug.Participants(childComplexity), true
+
case "Bug.createdAt":
if e.complexity.Bug.CreatedAt == nil {
break
@@ -2779,6 +2798,24 @@ func (ec *executionContext) _Bug(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalid = true
}
+ case "actors":
+ wg.Add(1)
+ go func(i int, field graphql.CollectedField) {
+ out.Values[i] = ec._Bug_actors(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ invalid = true
+ }
+ wg.Done()
+ }(i, field)
+ case "participants":
+ wg.Add(1)
+ go func(i int, field graphql.CollectedField) {
+ out.Values[i] = ec._Bug_participants(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ invalid = true
+ }
+ wg.Done()
+ }(i, field)
case "createdAt":
out.Values[i] = ec._Bug_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
@@ -3004,6 +3041,134 @@ func (ec *executionContext) _Bug_author(ctx context.Context, field graphql.Colle
}
// nolint: vetshadow
+func (ec *executionContext) _Bug_actors(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Bug",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Bug().Actors(rctx, obj)
+ })
+ if resTmp == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.([]*identity.Interface)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+ arr1 := make(graphql.Array, len(res))
+ var wg sync.WaitGroup
+
+ isLen1 := len(res) == 1
+ if !isLen1 {
+ wg.Add(len(res))
+ }
+
+ for idx1 := range res {
+ idx1 := idx1
+ rctx := &graphql.ResolverContext{
+ Index: &idx1,
+ Result: res[idx1],
+ }
+ ctx := graphql.WithResolverContext(ctx, rctx)
+ f := func(idx1 int) {
+ if !isLen1 {
+ defer wg.Done()
+ }
+ arr1[idx1] = func() graphql.Marshaler {
+
+ if res[idx1] == nil {
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res[idx1])
+ }()
+ }
+ if isLen1 {
+ f(idx1)
+ } else {
+ go f(idx1)
+ }
+
+ }
+ wg.Wait()
+ return arr1
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Bug_participants(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Bug",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Bug().Participants(rctx, obj)
+ })
+ if resTmp == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.([]*identity.Interface)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+ arr1 := make(graphql.Array, len(res))
+ var wg sync.WaitGroup
+
+ isLen1 := len(res) == 1
+ if !isLen1 {
+ wg.Add(len(res))
+ }
+
+ for idx1 := range res {
+ idx1 := idx1
+ rctx := &graphql.ResolverContext{
+ Index: &idx1,
+ Result: res[idx1],
+ }
+ ctx := graphql.WithResolverContext(ctx, rctx)
+ f := func(idx1 int) {
+ if !isLen1 {
+ defer wg.Done()
+ }
+ arr1[idx1] = func() graphql.Marshaler {
+
+ if res[idx1] == nil {
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res[idx1])
+ }()
+ }
+ if isLen1 {
+ f(idx1)
+ } else {
+ go f(idx1)
+ }
+
+ }
+ wg.Wait()
+ return arr1
+}
+
+// nolint: vetshadow
func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@@ -9637,6 +9802,8 @@ type Bug {
title: String!
labels: [Label!]!
author: Identity!
+ actors: [Identity]!
+ participants: [Identity]!
createdAt: Time!
lastEdit: Time!
diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go
index d571ce51..0b522b5e 100644
--- a/graphql/graphql_test.go
+++ b/graphql/graphql_test.go
@@ -50,6 +50,16 @@ func TestQueries(t *testing.T) {
email
avatarUrl
}
+ actors {
+ name
+ email
+ avatarUrl
+ }
+ participants {
+ name
+ email
+ avatarUrl
+ }
createdAt
humanId
@@ -112,7 +122,7 @@ func TestQueries(t *testing.T) {
}
}`
- type Person struct {
+ type Identity struct {
Name string `json:"name"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
@@ -123,13 +133,15 @@ func TestQueries(t *testing.T) {
AllBugs struct {
PageInfo models.PageInfo
Nodes []struct {
- Author Person
- CreatedAt string `json:"createdAt"`
- HumanId string `json:"humanId"`
- Id string
- LastEdit string `json:"lastEdit"`
- Status string
- Title string
+ Author Identity
+ Actors []Identity
+ Participants []Identity
+ CreatedAt string `json:"createdAt"`
+ HumanId string `json:"humanId"`
+ Id string
+ LastEdit string `json:"lastEdit"`
+ Status string
+ Title string
Comments struct {
PageInfo models.PageInfo
@@ -142,7 +154,7 @@ func TestQueries(t *testing.T) {
Operations struct {
PageInfo models.PageInfo
Nodes []struct {
- Author Person
+ Author Identity
Date string
Title string
Files []string
diff --git a/graphql/resolvers/bug.go b/graphql/resolvers/bug.go
index 7af04934..f48ff0a7 100644
--- a/graphql/resolvers/bug.go
+++ b/graphql/resolvers/bug.go
@@ -8,6 +8,7 @@ import (
"github.com/MichaelMure/git-bug/graphql/connections"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/identity"
)
var _ graph.BugResolver = &bugResolver{}
@@ -102,3 +103,23 @@ func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *strin
func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {
return obj.LastEditTime(), nil
}
+
+func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error) {
+ actorsp := make([]*identity.Interface, len(obj.Actors))
+
+ for i, actor := range obj.Actors {
+ actorsp[i] = &actor
+ }
+
+ return actorsp, nil
+}
+
+func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot) ([]*identity.Interface, error) {
+ participantsp := make([]*identity.Interface, len(obj.Participants))
+
+ for i, participant := range obj.Participants {
+ participantsp[i] = &participant
+ }
+
+ return participantsp, nil
+}
diff --git a/graphql/schema/bug.graphql b/graphql/schema/bug.graphql
index a1a61e7e..e294a363 100644
--- a/graphql/schema/bug.graphql
+++ b/graphql/schema/bug.graphql
@@ -36,6 +36,8 @@ type Bug {
title: String!
labels: [Label!]!
author: Identity!
+ actors: [Identity]!
+ participants: [Identity]!
createdAt: Time!
lastEdit: Time!