@@ -267,11 +267,11 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
PublishedAt githubql.DateTime
LastEditedAt *githubql.DateTime
Editor *githubqlActor
Body githubql.String
ReactionGroups reactionGroups
ViewerCanUpdate githubql.Boolean
ViewerCanUpdate bool
Timeline struct {
Nodes []struct {
Typename string `graphql:"__typename"`
ClosedEvent struct {
@@ -315,10 +315,15 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
githubqlActor `graphql:"...on Actor"`
}
} `graphql:"...on ReviewRequestRemovedEvent"`
MergedEvent struct {
event
Commit struct {
OID string
URL string
}
MergeRefName string
} `graphql:"...on MergedEvent"`
PullRequestReview struct {
Author githubqlActor
CreatedAt githubql.DateTime
State githubql.PullRequestReviewState
@@ -332,11 +337,11 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
PublishedAt githubql.DateTime
LastEditedAt *githubql.DateTime
Editor *githubqlActor
Body githubql.String
ReactionGroups reactionGroups
ViewerCanUpdate githubql.Boolean
ViewerCanUpdate bool
}
} `graphql:"comments(first:100)"` // TODO: Pagination...
Reviews struct {
Nodes []struct {
DatabaseID uint64
@@ -345,11 +350,11 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
LastEditedAt *githubql.DateTime
Editor *githubqlActor
Body string
ViewerCanUpdate bool
}
} `graphql:"reviews(first:100)"` // TODO: Figure out how to make pagination across 2 resource types work...
} `graphql:"reviews(first:100)"` // TODO: Pagination... Figure out how to make pagination across 2 resource types work...
} `graphql:"pullRequest(number:$prNumber)"`
} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
}
variables := map[string]interface{}{
"repositoryOwner": githubql.String(repo.Owner),
@@ -365,124 +370,136 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
pr := q.Repository.PullRequest
reactions, err := s.reactions(pr.ReactionGroups)
if err != nil {
return timeline, err
}
var edited *issues.Edited
var edited *changes.Edited
if pr.LastEditedAt != nil {
edited = &issues.Edited{
edited = &changes.Edited{
By: ghActor(*pr.Editor),
At: pr.LastEditedAt.Time,
}
}
timeline = append(timeline, issues.Comment{
timeline = append(timeline, changes.Comment{
ID: prDescriptionCommentID,
User: ghActor(pr.Author),
CreatedAt: pr.PublishedAt.Time,
Edited: edited,
Body: string(pr.Body),
Reactions: reactions,
Editable: bool(pr.ViewerCanUpdate),
Editable: pr.ViewerCanUpdate,
})
}
for _, comment := range q.Repository.PullRequest.Comments.Nodes {
reactions, err := s.reactions(comment.ReactionGroups)
if err != nil {
return timeline, err
}
var edited *issues.Edited
var edited *changes.Edited
if comment.LastEditedAt != nil {
edited = &issues.Edited{
edited = &changes.Edited{
By: ghActor(*comment.Editor),
At: comment.LastEditedAt.Time,
}
}
timeline = append(timeline, issues.Comment{
timeline = append(timeline, changes.Comment{
ID: uint64(comment.DatabaseID),
User: ghActor(comment.Author),
CreatedAt: comment.PublishedAt.Time,
Edited: edited,
Body: string(comment.Body),
Reactions: reactions,
Editable: bool(comment.ViewerCanUpdate),
Editable: comment.ViewerCanUpdate,
})
}
for _, review := range q.Repository.PullRequest.Reviews.Nodes {
if review.Body == "" {
continue
}
var edited *issues.Edited
var edited *changes.Edited
if review.LastEditedAt != nil {
edited = &issues.Edited{
edited = &changes.Edited{
By: ghActor(*review.Editor),
At: review.LastEditedAt.Time,
}
}
timeline = append(timeline, issues.Comment{
timeline = append(timeline, changes.Comment{
ID: review.DatabaseID,
User: ghActor(review.Author),
CreatedAt: review.PublishedAt.Time,
Edited: edited,
Body: review.Body,
Editable: review.ViewerCanUpdate,
})
}
for _, event := range q.Repository.PullRequest.Timeline.Nodes {
e := issues.Event{
//ID: 0, // TODO.
Type: ghEventType(event.Typename),
e := changes.TimelineItem{
//ID: 0, // TODO.
}
switch e.Type {
case issues.Closed:
switch event.Typename {
case "ClosedEvent":
e.Actor = ghActor(event.ClosedEvent.Actor)
e.CreatedAt = event.ClosedEvent.CreatedAt.Time
case issues.Reopened:
e.Payload = changes.ClosedEvent{}
case "ReopenedEvent":
e.Actor = ghActor(event.ReopenedEvent.Actor)
e.CreatedAt = event.ReopenedEvent.CreatedAt.Time
case issues.Renamed:
e.Payload = changes.ReopenedEvent{}
case "RenamedTitleEvent":
e.Actor = ghActor(event.RenamedTitleEvent.Actor)
e.CreatedAt = event.RenamedTitleEvent.CreatedAt.Time
e.Rename = &issues.Rename{
e.Payload = changes.RenamedEvent{
From: event.RenamedTitleEvent.PreviousTitle,
To: event.RenamedTitleEvent.CurrentTitle,
}
case issues.Labeled:
case "LabeledEvent":
e.Actor = ghActor(event.LabeledEvent.Actor)
e.CreatedAt = event.LabeledEvent.CreatedAt.Time
e.Label = &issues.Label{
Name: event.LabeledEvent.Label.Name,
Color: ghColor(event.LabeledEvent.Label.Color),
e.Payload = changes.LabeledEvent{
Label: issues.Label{
Name: event.LabeledEvent.Label.Name,
Color: ghColor(event.LabeledEvent.Label.Color),
},
}
case issues.Unlabeled:
case "UnlabeledEvent":
e.Actor = ghActor(event.UnlabeledEvent.Actor)
e.CreatedAt = event.UnlabeledEvent.CreatedAt.Time
e.Label = &issues.Label{
Name: event.UnlabeledEvent.Label.Name,
Color: ghColor(event.UnlabeledEvent.Label.Color),
e.Payload = changes.UnlabeledEvent{
Label: issues.Label{
Name: event.UnlabeledEvent.Label.Name,
Color: ghColor(event.UnlabeledEvent.Label.Color),
},
}
// TODO: Wait for GitHub to add support.
//case issues.CommentDeleted:
//case "CommentDeletedEvent":
// e.Actor = ghActor(event.CommentDeletedEvent.Actor)
// e.CreatedAt = event.CommentDeletedEvent.CreatedAt.Time
case "ReviewRequestedEvent":
e.Actor = ghActor(event.ReviewRequestedEvent.Actor)
e.CreatedAt = event.ReviewRequestedEvent.CreatedAt.Time
// TODO: Move RequestedReviewer field to changes-only events (it doesn't apply to issues).
e.RequestedReviewer = ghActor(event.ReviewRequestedEvent.RequestedReviewer.githubqlActor)
e.Payload = changes.ReviewRequestedEvent{
RequestedReviewer: ghActor(event.ReviewRequestedEvent.RequestedReviewer.githubqlActor),
}
case "ReviewRequestRemovedEvent":
e.Actor = ghActor(event.ReviewRequestRemovedEvent.Actor)
e.CreatedAt = event.ReviewRequestRemovedEvent.CreatedAt.Time
// TODO: Move RequestedReviewer field to changes-only events (it doesn't apply to issues).
e.RequestedReviewer = ghActor(event.ReviewRequestRemovedEvent.RequestedReviewer.githubqlActor)
e.Payload = changes.ReviewRequestRemovedEvent{
RequestedReviewer: ghActor(event.ReviewRequestRemovedEvent.RequestedReviewer.githubqlActor),
}
case "MergedEvent":
e.Actor = ghActor(event.ReviewRequestRemovedEvent.Actor)
e.CreatedAt = event.ReviewRequestRemovedEvent.CreatedAt.Time
e.Actor = ghActor(event.MergedEvent.Actor)
e.CreatedAt = event.MergedEvent.CreatedAt.Time
e.Payload = changes.MergedEvent{
CommitID: event.MergedEvent.Commit.OID,
CommitHTMLURL: event.MergedEvent.Commit.URL,
RefName: event.MergedEvent.MergeRefName,
}
case "PullRequestReview":
switch event.PullRequestReview.State {
case githubql.PullRequestReviewStateApproved:
// TODO: Make this a thing that ListComments returns, etc. After all, it can have a non-empty body.
e.Type = "ApprovedEvent"
e.Payload = changes.ApprovedEvent{}
default:
continue
}
e.Actor = ghActor(event.PullRequestReview.Author)
e.CreatedAt = event.PullRequestReview.CreatedAt.Time
@@ -582,29 +599,10 @@ func ghPRState(state githubql.PullRequestState) changes.State {
default:
panic("unreachable")
}
}
func ghEventType(typename string) issues.EventType {
switch typename {
case "ReopenedEvent": // TODO: Use githubql.IssueTimelineItemReopenedEvent or so.
return issues.Reopened
case "ClosedEvent": // TODO: Use githubql.IssueTimelineItemClosedEvent or so.
return issues.Closed
case "RenamedTitleEvent":
return issues.Renamed
case "LabeledEvent":
return issues.Labeled
case "UnlabeledEvent":
return issues.Unlabeled
case "CommentDeletedEvent":
return issues.CommentDeleted
default:
return issues.EventType(typename)
}
}
// ghColor converts a GitHub color hex string like "ff0000"
// into an issues.RGB value.
func ghColor(hex string) issues.RGB {
var c issues.RGB
fmt.Sscanf(hex, "%02x%02x%02x", &c.R, &c.G, &c.B)
@@ -615,11 +613,11 @@ type reactionGroups []struct {
Content githubql.ReactionContent
Users struct {
Nodes []githubqlActor
TotalCount githubql.Int
} `graphql:"users(first:10)"`
ViewerHasReacted githubql.Boolean
ViewerHasReacted bool
}
// reactions converts []githubql.ReactionGroup to []reactions.Reaction.
func (s service) reactions(rgs reactionGroups) ([]reactions.Reaction, error) {
var rs []reactions.Reaction
@@ -638,11 +636,11 @@ func (s service) reactions(rgs reactionGroups) ([]reactions.Reaction, error) {
if s.currentUser.ID != 0 && actor.UserSpec == s.currentUser.UserSpec {
addedAuthedUser = true
}
} else if i == len(rg.Users.Nodes) {
// Add authed user last if they've reacted, but haven't been added already.
if bool(rg.ViewerHasReacted) && !addedAuthedUser {
if rg.ViewerHasReacted && !addedAuthedUser {
us = append(us, s.currentUser)
}
} else {
us = append(us, users.User{})
}