Expose actors and participants in snapshot and bug excerpt

Append operations authors to each list on Apply() call

Expose actors and participants in graphql

Add actor/participant query filter and documentation
This commit is contained in:
Amine Hilaly 2019-03-31 22:32:35 +02:00
parent 5b0a92dea4
commit 2a5fbc4dc9
17 changed files with 368 additions and 38 deletions

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}

22
cache/bug_excerpt.go vendored
View File

@ -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(),

54
cache/filter.go vendored
View File

@ -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
}

10
cache/query.go vendored
View File

@ -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)

3
cache/query_test.go vendored
View File

@ -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},

View File

@ -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.

View File

@ -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 {
@ -3003,6 +3040,134 @@ func (ec *executionContext) _Bug_author(ctx context.Context, field graphql.Colle
return ec._Identity(ctx, field.Selections, &res)
}
// 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)
@ -9637,6 +9802,8 @@ type Bug {
title: String!
labels: [Label!]!
author: Identity!
actors: [Identity]!
participants: [Identity]!
createdAt: Time!
lastEdit: Time!

View File

@ -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

View File

@ -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
}

View File

@ -36,6 +36,8 @@ type Bug {
title: String!
labels: [Label!]!
author: Identity!
actors: [Identity]!
participants: [Identity]!
createdAt: Time!
lastEdit: Time!