...

Source file src/edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common/methods.go

Documentation: edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common

     1  // Copyright 2018 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  	// If GithubReview is true, GithubReviewState is the state a review must
    33  	// have to be considered a candidated. It is currently excluded from
    34  	// serialized forms and should be set by the application.
    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  // Candidates returns a list of user candidates based on the configured
    62  // methods. A given user will appear at most once in the list. If that user has
    63  // taken multiple actions that match the methods, only the most recent by event
    64  // order is included. The order of the candidates is unspecified.
    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