blob: 06abc923cfa5ea5fab5895965e716f7aaea6dd04 [file] [log] [blame]
package incremental
import (
"fmt"
"reflect"
"sync"
"go.skia.org/infra/go/git/repograph"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/task_scheduler/go/db"
"go.skia.org/infra/task_scheduler/go/types"
"go.skia.org/infra/task_scheduler/go/window"
)
// commentsCache is a struct used for tracking various types of comments.
type commentsCache struct {
db db.CommentDB
mtx sync.Mutex
prevComments map[string]*types.RepoComments
repos []string
}
// CommitComment is a wrapper around types.CommitComment which interprets the
// timestamp (in nanoseconds) as the ID, for convenience.
type CommitComment struct {
types.CommitComment
Id string `json:"id"`
}
// TaskComment is a wrapper around types.TaskComment which interprets the
// timestamp (in nanoseconds) as the ID, for convenience.
type TaskComment struct {
types.TaskComment
Id string `json:"id"`
}
// TaskSpecComment is a wrapper around types.TaskSpecComment which interprets the
// timestamp (in nanoseconds) as the ID, for convenience.
type TaskSpecComment struct {
types.TaskSpecComment
Id string `json:"id"`
}
// RepoComments is a variant of types.RepoComments which uses the above wrappers
// around the typical comment types.
type RepoComments struct {
// CommitComments maps commit hash to the comments for that commit,
// sorted by timestamp.
CommitComments map[string][]*CommitComment
// TaskComments maps commit hash and TaskSpec name to the comments for
// the matching Task, sorted by timestamp.
TaskComments map[string]map[string][]*TaskComment
// TaskSpecComments maps TaskSpec name to the comments for that
// TaskSpec, sorted by timestamp.
TaskSpecComments map[string][]*TaskSpecComment
}
// commitComments converts the types.CommitComments to CommitComments.
func commitComments(inp map[string][]*types.CommitComment) map[string][]*CommitComment {
rv := make(map[string][]*CommitComment, len(inp))
for k, v := range inp {
comments := make([]*CommitComment, 0, len(v))
for _, c := range v {
comments = append(comments, &CommitComment{
CommitComment: *c,
Id: fmt.Sprintf("%d", c.Timestamp.UnixNano()),
})
}
rv[k] = comments
}
return rv
}
// taskComments converts the types.TaskComments to TaskComments.
func taskComments(inp map[string]map[string][]*types.TaskComment) map[string]map[string][]*TaskComment {
rv := make(map[string]map[string][]*TaskComment, len(inp))
for k, v := range inp {
submap := make(map[string][]*TaskComment, len(v))
for k2, v2 := range v {
comments := make([]*TaskComment, 0, len(v2))
for _, c := range v2 {
comments = append(comments, &TaskComment{
TaskComment: *c,
Id: fmt.Sprintf("%d", c.Timestamp.UnixNano()),
})
}
submap[k2] = comments
}
rv[k] = submap
}
return rv
}
// taskSpecComments converts the types.TaskSpecComments to TaskSpecComments.
func taskSpecComments(inp map[string][]*types.TaskSpecComment) map[string][]*TaskSpecComment {
rv := make(map[string][]*TaskSpecComment, len(inp))
for k, v := range inp {
comments := make([]*TaskSpecComment, 0, len(v))
for _, c := range v {
comments = append(comments, &TaskSpecComment{
TaskSpecComment: *c,
Id: fmt.Sprintf("%d", c.Timestamp.UnixNano()),
})
}
rv[k] = comments
}
return rv
}
// newCommentsCache returns a commentsCache instance.
func newCommentsCache(d db.CommentDB, repos repograph.Map) *commentsCache {
repoNames := make([]string, 0, len(repos))
for repo := range repos {
repoNames = append(repoNames, repo)
}
pc := make(map[string]*types.RepoComments, len(repoNames))
for _, repo := range repoNames {
pc[repo] = &types.RepoComments{}
}
return &commentsCache{
db: d,
prevComments: pc,
repos: repoNames,
}
}
// Reset clears previously-seen comments from the cache so that the next call
// to Update() returns all comments.
func (c *commentsCache) Reset() {
c.mtx.Lock()
defer c.mtx.Unlock()
c.prevComments = make(map[string]*types.RepoComments, len(c.repos))
for _, repo := range c.repos {
c.prevComments[repo] = &types.RepoComments{}
}
}
// Update returns any sets of comments which have changed since the last call
// to Update.
func (c *commentsCache) Update(w *window.Window) (map[string]RepoComments, error) {
defer metrics2.FuncTimer().Stop()
c.mtx.Lock()
defer c.mtx.Unlock()
repoComments, err := c.db.GetCommentsForRepos(c.repos, w.EarliestStart())
if err != nil {
return nil, err
}
rv := make(map[string]RepoComments, len(c.repos))
for _, rc := range repoComments {
entry := RepoComments{}
if !reflect.DeepEqual(rc.CommitComments, c.prevComments[rc.Repo].CommitComments) && len(rc.CommitComments) > 0 {
entry.CommitComments = commitComments(rc.CommitComments)
}
if !reflect.DeepEqual(rc.TaskComments, c.prevComments[rc.Repo].TaskComments) && len(rc.TaskComments) > 0 {
entry.TaskComments = taskComments(rc.TaskComments)
}
if !reflect.DeepEqual(rc.TaskSpecComments, c.prevComments[rc.Repo].TaskSpecComments) && len(rc.TaskSpecComments) > 0 {
entry.TaskSpecComments = taskSpecComments(rc.TaskSpecComments)
}
rv[rc.Repo] = entry
c.prevComments[rc.Repo] = rc
}
return rv, nil
}