-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor Approval Implementation #1846
Refactor Approval Implementation #1846
Conversation
a3cb0a6
to
ad8d40f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm done for now, I think this can still be vastly simplified
for _, fn := range o.filenames { | ||
owners.Insert(o.repo.FindOwnersForPath(fn)) | ||
} | ||
files := owners.List() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sets.String.List() returns a sorted list, no need to re-sort.
|
||
func (o Owners) IsApproved(currentlyApproved sets.String) bool { | ||
fnMap := o.GetApprovers() //ownersfiles -> Relevant Approvers | ||
for _, file := range o.ListOwnersFiles() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why don't you loop over fnmap
?
|
||
// removeSubdirs takes a list of directories as an input and returns a set of directories with all | ||
// subdirectories removed. E.g. [/a,/a/b/c,/d/e,/d/e/f] -> [/a, /d/e] | ||
func removeSubdirs(dirList []string) sets.String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used yet?
} | ||
files := owners.List() | ||
sort.Strings(files) | ||
return files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should return the set rather than the list here.
copyOfFiles := sets.NewString() | ||
for _, fn := range o.filenames { | ||
copyOfFiles.Insert(fn) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be replaced by ListOwnersFiles (especially if we make it return a set)
for approver := range approverOwnersfiles { | ||
sliceOfKeys = append(sliceOfKeys, approver) | ||
} | ||
sort.Strings(sliceOfKeys) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make a function for that GetAllPotentialApprovers()
people = append(people, sliceOfKeys[order[i]]) | ||
} | ||
|
||
files = []sets.String{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following code then can be removed
sliceOfKeys = append(sliceOfKeys, approver) | ||
} | ||
sort.Strings(sliceOfKeys) | ||
order := rand.Perm(len(approverOwnersfiles)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 4 lines are the only thing left
toDelete := sets.NewString() | ||
// remove all files in the directories that our approver approved AND | ||
// in the subdirectories that s/he approved. HasPrefix finds subdirs | ||
for fn := range copyOfFiles { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't exactly get that.
At this point you have the suggested person, don't you just want to remove from copyOfFiles
(which is a terrible name) the list of files that person can approve? copyOfFiles = copyOfFiles.Difference(filesCanApprove[bestPerson])
and remove a bunch of temporary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suppose for some reason we choose Tim or Brian as the best person. They are only in /OWNERS but not /pkg/api/OWNERS. Checking the prefix makes sure that we not only include the fact that they cover the root, but also all subdirs. Does that make sense?
var bestPerson string | ||
var bestPersonFiles sets.String | ||
for copyOfFiles.Len() > 0 { | ||
maxCovered := 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following 8-10 lines is basically: FindPersonWithBiggestCoverage(toBeApproved, randomizedApprovers, reverseMap) (person string)
Many functions are really only related to finding the list of suggested approvers, I'm wondering if it's a sign that we should cut a new type there. |
revMap := o.GetReverseMap() | ||
|
||
setToCover := sets.NewString() | ||
for _, fn := range o.filenames { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better variable name?
|
||
approverGroup := sets.NewString() | ||
for setToCover.Len() > 0 { | ||
bestPerson := findPersonWithBiggestCoverage(setToCover, randomizedApprovers, revMap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
turned it into a function.
// IsApproved returns a bool indicating if the entire PR is approved | ||
func (o Owners) IsApproved(currentlyApproved sets.String) bool { | ||
//o.GetApprovers returns map ownersfiles -> Relevant Approvers | ||
for _, approvers := range o.GetApprovers() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't right. o.GetApprovers returns the leafApprovers only. We also need to check further up the tree.
@apelisse ping. what do you think about the tests? |
b422f66
to
4123a36
Compare
unapprovedFiles := o.UnapprovedFiles(alreadyApproved) | ||
for _, path := range o.GetOwnersSet().List() { | ||
fullOwnersPath := filepath.Join(path, ownersFileName) | ||
approverSet := o.repo.Approvers(path).Intersection(alreadyApproved) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I create a method attached to owners that returns a map from ownersFiles -> people that have approved them?
} | ||
context.WriteString("\n") | ||
suggestedApprovers := o.GetSuggestedApprovers() | ||
toBeAssigned := sets.NewString(suggestedApprovers...).Difference(alreadyApproved) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function for getting people that should be assigned or is it okay to have the logic here?
64daca8
to
b3ebae1
Compare
|
||
type UnapprovedFile struct { | ||
filepath string | ||
approvers sets.String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed, that's the beauty of it.
// - how an approver can indicate their approval | ||
// - how an approver can cancel their approval | ||
func getMessage(ap Approvers, org, project string) string { | ||
context := bytes.NewBufferString("The following people have approved this PR: ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we turn that into a big string?
testName: "Single Root File PR No One Approved", | ||
filenames: []string{"kubernetes.go"}, | ||
currentlyApproved: sets.NewString(), | ||
expectedUnapproved: sets.NewString(""), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty string means root, that's kind of weird
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but that's what github sends us back. E.g the makefile in the root dir is listed as Makefile
not /Makefile
testName: "Combo and Other; Combo Approved", | ||
filenames: []string{"a/combo/test.go", "a/d/test.go"}, | ||
currentlyApproved: eApprovers, | ||
expectedUnapproved: sets.NewString("a/d"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading this test is far from obvious. eApprovers
has approved, and somehow a/combo
is approved ... Following the top structure helps, but it's still puzzling
testName: "B Only UnApproved", | ||
filenames: []string{"b/test_1.go", "b/test.go"}, | ||
currentlyApproved: bApprovers, | ||
expectedUnapproved: sets.NewString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test for:
filenames: []string{"b/test_1.go", "b/test.go"},
currentlyApproved: rootApprovers,
{ | ||
testName: "Combo and Other; Both Approved", | ||
filenames: []string{"a/combo/test.go", "a/d/test.go"}, | ||
currentlyApproved: edcApprovers.Intersection(dApprovers), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I have no idea what this is. Isn't ecdApprovers.Intersection(dApprovers)
equal to dApprovers
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it is. I added that so it's clear that the dApprovers are present in both sets (otherwise the intersection would be empty set. better to just be dApprovers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT?
filenames: []string{"a/combo/test.go", "a/d/test.go", "c/test"}, | ||
currentlyApproved: cApprovers, | ||
expectedFiles: []File{ApprovedFile{"a/combo", cApprovers}, UnapprovedFile{"a/d"}, ApprovedFile{"c", cApprovers}}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have no test where a group of people approve a dir, and another group approves another dir, and maybe a group approves multiple dirs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this. Can you elaborate more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look at your tests, you always have only one set of approvers.
You don't have a test like:
filenames: []string{"a/something", "b/something"}
currentlyApproved: aApprovers, bApprovers
filenames []string | ||
currentlyApproved sets.String | ||
// testSeed affects who is chosen for CC | ||
testSeed int64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any test where the seed has an impact? Are you testing that different seeds have different results?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, the GetCCs test
func (ap Approvers) UnapprovedFiles() sets.String { | ||
unapproved := sets.NewString() | ||
for fn := range ap.owners.GetOwnersSet() { | ||
approvers := ap.owners.repo.Approvers(fn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't think you should access members from owners directly
92f37a9
to
e981108
Compare
@@ -159,7 +143,7 @@ func getApproveComments(comments []*githubapi.IssueComment) c.FilteredComments { | |||
return c.FilterComments(comments, c.Or{approverMatcher, lgtmMatcher}) | |||
} | |||
|
|||
func (h *ApprovalHandler) updateNotification(obj *github.MungeObject, ownersMap map[string]sets.String, approverSet sets.String, isFullyApproved bool) error { | |||
func (h *ApprovalHandler) updateNotification(obj *github.MungeObject, approversHandler approvers.Approvers) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do we fix this?
4d68cf5
to
0b2a82d
Compare
0b2a82d
to
6bfbe72
Compare
expectedUnapproved: sets.NewString(), | ||
}, | ||
{ | ||
testName: "B Files Approved at Root", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the root test
@k8s-bot bazel test this |
73f771c
to
b2f5f77
Compare
b2f5f77
to
2e02db9
Compare
Once you've validated on your repo that everything is fine, please feel free to merge. /lgtm |
- Redesign with multiple classes each with specific behaviors - Modular design and comprehensive testing
d9dcab9
to
8d1d807
Compare
No description provided.