1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package common
16
17 import (
18 "context"
19 "strings"
20 "time"
21
22 "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
23 )
24
25 type Methods struct {
26 Comments []string `yaml:"comments,omitempty"`
27 CommentPatterns []Regexp `yaml:"comment_patterns,omitempty"`
28 GithubReview *bool `yaml:"github_review,omitempty"`
29 GithubReviewCommentPatterns []Regexp `yaml:"github_review_comment_patterns,omitempty"`
30 BodyPatterns []Regexp `yaml:"body_patterns,omitempty"`
31
32
33
34
35 GithubReviewState pull.ReviewState `yaml:"-" json:"-"`
36 }
37
38 type CandidateType string
39
40 const (
41 ReviewCandidate CandidateType = "review"
42 CommentCandidate CandidateType = "comment"
43 )
44
45 type Candidate struct {
46 Type CandidateType
47 ReviewID string
48 User string
49 CreatedAt time.Time
50 LastEditedAt time.Time
51 }
52
53 type CandidatesByCreationTime []*Candidate
54
55 func (cs CandidatesByCreationTime) Len() int { return len(cs) }
56 func (cs CandidatesByCreationTime) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
57 func (cs CandidatesByCreationTime) Less(i, j int) bool {
58 return cs[i].CreatedAt.Before(cs[j].CreatedAt)
59 }
60
61
62
63
64
65 func (m *Methods) Candidates(ctx context.Context, prctx pull.Context) ([]*Candidate, error) {
66 var candidates []*Candidate
67
68 if len(m.Comments) > 0 || len(m.CommentPatterns) > 0 {
69 comments, err := prctx.Comments()
70 if err != nil {
71 return nil, err
72 }
73
74 for _, c := range comments {
75 if m.CommentMatches(c.Body) {
76 candidates = append(candidates, &Candidate{
77 Type: CommentCandidate,
78 User: c.Author,
79 CreatedAt: c.CreatedAt,
80 LastEditedAt: c.LastEditedAt,
81 })
82 }
83 }
84 }
85
86 if len(m.BodyPatterns) > 0 {
87 prBody, err := prctx.Body()
88 if err != nil {
89 return nil, err
90 }
91 if m.BodyMatches(prBody.Body) {
92 candidates = append(candidates, &Candidate{
93 User: prBody.Author,
94 CreatedAt: prBody.CreatedAt,
95 LastEditedAt: prBody.LastEditedAt,
96 })
97 }
98 }
99
100 if m.GithubReview != nil && *m.GithubReview || len(m.GithubReviewCommentPatterns) > 0 {
101 reviews, err := prctx.Reviews()
102 if err != nil {
103 return nil, err
104 }
105
106 for _, r := range reviews {
107 if r.State == m.GithubReviewState {
108 if len(m.GithubReviewCommentPatterns) > 0 {
109 if m.githubReviewCommentMatches(r.Body) {
110 candidates = append(candidates, &Candidate{
111 Type: ReviewCandidate,
112 ReviewID: r.ID,
113 User: r.Author,
114 CreatedAt: r.CreatedAt,
115 LastEditedAt: r.LastEditedAt,
116 })
117 }
118 } else {
119 candidates = append(candidates, &Candidate{
120 Type: ReviewCandidate,
121 ReviewID: r.ID,
122 User: r.Author,
123 CreatedAt: r.CreatedAt,
124 LastEditedAt: r.LastEditedAt,
125 })
126 }
127 }
128 }
129 }
130
131 return deduplicateCandidates(candidates), nil
132 }
133
134 func deduplicateCandidates(all []*Candidate) []*Candidate {
135 users := make(map[string]*Candidate)
136 for _, c := range all {
137 last, ok := users[c.User]
138 if !ok || last.CreatedAt.Before(c.CreatedAt) {
139 users[c.User] = c
140 }
141 }
142
143 candidates := make([]*Candidate, 0, len(users))
144 for _, c := range users {
145 candidates = append(candidates, c)
146 }
147
148 return candidates
149 }
150
151 func (m *Methods) CommentMatches(commentBody string) bool {
152 for _, comment := range m.Comments {
153 if strings.Contains(commentBody, comment) {
154 return true
155 }
156 }
157 for _, pattern := range m.CommentPatterns {
158 if pattern.Matches(commentBody) {
159 return true
160 }
161 }
162 return false
163 }
164
165 func (m *Methods) githubReviewCommentMatches(commentBody string) bool {
166 for _, pattern := range m.GithubReviewCommentPatterns {
167 if pattern.Matches(commentBody) {
168 return true
169 }
170 }
171 return false
172 }
173
174 func (m *Methods) BodyMatches(prBody string) bool {
175 for _, pattern := range m.BodyPatterns {
176 if pattern.Matches(prBody) {
177 return true
178 }
179 }
180 return false
181 }
182
View as plain text