Skip to content
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

Show approvals on the PR listing page #8762

Closed
wants to merge 3 commits into from

Conversation

jamesorlakin
Copy link
Contributor

@jamesorlakin jamesorlakin commented Oct 31, 2019

As suggested in #8659, this PR displays the whitelisted approvals in Gitea's PR listing page if a given branch is protected and requires more than 0 approvals to be merged. I've chosen the eye Octicon but perhaps a person one would be better?

image

I've tested this works locally in the browser but as I'm on a Windows environment Docker doesn't work properly for the full test suite 😢 . This is also my first time using Go, so all suggestions (nitpicking) are most welcome!

@codecov-io
Copy link

codecov-io commented Oct 31, 2019

Codecov Report

Merging #8762 into master will decrease coverage by <.01%.
The diff coverage is 46.15%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #8762      +/-   ##
==========================================
- Coverage   41.39%   41.38%   -0.01%     
==========================================
  Files         539      539              
  Lines       69501    69514      +13     
==========================================
- Hits        28770    28769       -1     
- Misses      37032    37051      +19     
+ Partials     3699     3694       -5
Impacted Files Coverage Δ
routers/repo/issue.go 34.48% <46.15%> (+0.11%) ⬆️
modules/indexer/indexer.go 44.73% <0%> (-10.53%) ⬇️
models/unit.go 62.16% <0%> (-5.41%) ⬇️
modules/task/migrate.go 24.35% <0%> (-3.85%) ⬇️
models/error.go 32.08% <0%> (-1.25%) ⬇️
modules/log/event.go 64.61% <0%> (-1.03%) ⬇️
models/pull.go 54.33% <0%> (-0.59%) ⬇️
models/repo.go 48.62% <0%> (+0.05%) ⬆️
modules/migrations/gitea.go 11.77% <0%> (+1.49%) ⬆️
modules/migrations/migrate.go 22.9% <0%> (+1.67%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ebcc381...ade2ffe. Read the comment docs.

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Oct 31, 2019
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
GrantedApprovalsCount int64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be added column so database so should be marked so:

Suggested change
GrantedApprovalsCount int64
GrantedApprovalsCount int64 `xorm:"-"`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

granted aprovals Should be stored for each PR and not for each branch?!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the information Iis stored already somewhere ... have to check where ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah, so any properties I add are mapped to the database by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can get the information via models.GetUniqueApprovalsByPullRequestID(prID int64) ([]*Review, error)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

summary:

  • no changes in models/branches.go
  • call models.GetUniqueApprovalsByPullRequestID(prID int64) in routers/repo/issue.go and store in ctx.Data["GrantedApprovalsCount"]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call models.GetUniqueApprovalsByPullRequestID(prID int64) in routers/repo/issue.go and store in ctx.Data["GrantedApprovalsCount"]

How would I reference that per PR, seeing as I have an array of them? Issues get popped under ctx.Data["Issues"] as a whole so I created a property to be able to access it from each one within the template. I presume something dynamic like ctx.Data["Issues"][i].GrantedApprovals = count isn't possible - I come from a JS background 😉.

The reason I left it as a property of ProtectedBranch was simply because GetGrantedApprovalsCount lived there already, and I just assumed I would keep them together.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can look into it later ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm attempting to change it to a solution similar to ctx.Data["CommitStatus"] and removing the extra property on ProtectedBranch.

Looking at it, GetUniqueApprovalsByPullRequestID will return all approved reviews for a given PR, not just those on the approval whitelist. This might be equally valid - what would the user reasonably expect to see? Personally I think only reviews "which matter" should count to quickly determine if a PR is ready to be merged.

@@ -222,6 +222,14 @@
<a class="ui label has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a>
{{end}}

{{if .IsPull}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be reduced to one if statement:

Suggested change
{{if .IsPull}}
{{if and .IsPull .PullRequest.ProtectedBranch .PullRequest.ProtectedBranch.RequiredApprovals}}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah that's much simpler, will do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this didn't work for branches without protection enabled

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{{if and .IsPull (if ne (index $.RequiredApprovals .PullRequest.ID) 0)}} ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that requires that $.RequiredApprovals contain all indexes? Should be possible by setting them 0 in issue.go.

@lafriks lafriks added the type/enhancement An improvement of existing functionality label Oct 31, 2019
@lafriks lafriks added this to the 1.11.0 milestone Oct 31, 2019
Copy link
Member

@6543 6543 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beside this ... nice idear

ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
GrantedApprovalsCount int64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

granted aprovals Should be stored for each PR and not for each branch?!

ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
GrantedApprovalsCount int64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the information Iis stored already somewhere ... have to check where ...

{{if .IsPull}}
{{if .PullRequest.ProtectedBranch}}
{{if .PullRequest.ProtectedBranch.RequiredApprovals}}
<span class="comment ui right"><i class="octicon octicon-eye"></i> {{.PullRequest.ProtectedBranch.GrantedApprovalsCount}} / {{.PullRequest.ProtectedBranch.RequiredApprovals}}</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<span class="comment ui right"><i class="octicon octicon-eye"></i> {{.PullRequest.ProtectedBranch.GrantedApprovalsCount}} / {{.PullRequest.ProtectedBranch.RequiredApprovals}}</span>
<span class="comment ui right"><i class="octicon octicon-eye"></i> {{.PullRequest.GrantedApprovalsCount}} / {{.PullRequest.ProtectedBranch.RequiredApprovals}}</span>

Continue things .. from abouth

@6543
Copy link
Member

6543 commented Oct 31, 2019

@jamesorlakin look at #8762 (comment) ;)

@lunny
Copy link
Member

lunny commented Oct 31, 2019

I think you guys should consider the page load performance. Protected branches could be load once. Or I should send another PR to do that after this one.


commitStatus[issues[i].PullRequest.ID], _ = issues[i].PullRequest.GetLastCommitStatus()
if err := pull.LoadProtectedBranch(); err == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not handling the error here. Please do something like

if err := pull.LoadProtectedBranch(); err != nil {
    return
}

if pull.ProtectedBranch != nil && pull.ProtectedBranch.RequiredApprovals != 0 {
    ProtectedBranch.GrantedApprovalsCount = pull.ProtectedBranch.GetGrantedApprovalsCount(
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah ok, is there a subtle difference in the behaviour of errors and nil?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function returns nil for an error, there was no error. You don't check for the case where the error was not nil which means you're never actually doing something with the error.

In Go, there is this pattern of keeping a "happy path" of code, that's why you'll see a lot of if err != nil code used to handle errors. Take a look at this article for more information: https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88

@jamesorlakin
Copy link
Contributor Author

Hi all,

Thanks for the comments. I've reworked it to now use a method similar to the last commit status - a map by PR ID for both required approvals and approved accepted ones.

6543 mentioned using GetUniqueApprovalsByPullRequestID. This would surely count all reviews on a PR, not just ones whitelisted needed for the PR to be merged? This raised a good point in my mind though - should the user see all approvals, or just ones relevant to the branch protection?

lunny, you're right, I didn't think about performance! 3 PRs rendered quick enough on my laptop... A to-do (assuming this approach looks okay) would be to cache protected branches during the loop - say a page had 25 PRs all for the same target branch, we would only need to load the ProtectedBranch once leaving just a call to GetGrantedApprovalsCount for each one.

@guillep2k
Copy link
Member

The number of protected branches in a given repository should not be too large (3? 6? 10?). In my experience, it's more performant to load all of them at once rather than reaching for them one by one. Databases do that themselves: for example, for tables under a certain number of rows, indexes are never used for querying (only for updating); the database considers that reading one disk block is the "expensive" part; sorting/filtering is quickly done in memory. Context/process switching from Gitea to and from the database is costly as well.

@guillep2k
Copy link
Member

For the number of approvals, I'd use a single database query, which is much faster than retrieving the information each time:

  • Collect all the issues/prs to show in the page using models.Issues() as normal, so pagination and other options are taken into account.
  • In a slice (array), collect all the ids for rows where .IsPull is true and .IsClosed is false.
  • If the number of ids collected is not zero, make one single query to the database counting approvals for those ids (using group by and count); load that into a map. For that, a method must be created in models/issue.go, where all database accesses should happen. That method should use sess.GroupBy("issue.id").Select("issue.id AS id, COUNT(*) AS count"); see CountIssuesByRepo() for an example.
  • Then that map can be used to display the info in the page.
  • Alternatively, the approval count can be moved into models.Issues() as well.

Hope this helps.

@@ -222,6 +222,14 @@
<a class="ui label has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a>
{{end}}

{{if .IsPull}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{{if and .IsPull (if ne (index $.RequiredApprovals .PullRequest.ID) 0)}} ?

@jamesorlakin
Copy link
Contributor Author

Thanks for the suggestions @guillep2k! Unfortunately I'm not sure I'm capable at dropping to such complex SQL (joining and comparing whitelisted approvers including teams, etc). That said, loading all protected branches at once initially could be better optimised than the individual looping - I'll try and do that.

ids for rows where .IsPull is true and .IsClosed is false.

I would be under the assumption closed PRs should show the same as open ones?

Moving forward, I'm also considering showing total approvals (not whitelisted) for PRs that aren't on a protected branch. This would just be a simple number next to the eye. If possible I'll want to grab all approvals for the PRs at once, and then do the whitelist filtering using a util function (similar to how GetGrantedApprovalsCount works, but without the query) - this may not go to plan...

I've since backported this to our production 1.9 instance and performance seems to be fine - though it's not a great representative measurement!

@6543
Copy link
Member

6543 commented Nov 6, 2019

@jamesorlakin are you still working on this?

@jamesorlakin
Copy link
Contributor Author

@6543 yep I'm keen to finish it, but I likely won't have much time until the weekend. Feel free to tweak if you want to in the meantime 😃 .

if pull.ProtectedBranch != nil && pull.ProtectedBranch.RequiredApprovals != 0 {
requiredApprovals[pull.ID] = pull.ProtectedBranch.RequiredApprovals
approvals[pull.ID] = pull.ProtectedBranch.GetGrantedApprovalsCount(pull)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set value in slice also when branch is not protected? (with 0 value)

@6543
Copy link
Member

6543 commented Dec 17, 2019

@jamesorlakin what is the stats?

@lunny lunny modified the milestones: 1.11.0, 1.12.0 Dec 28, 2019
@6543
Copy link
Member

6543 commented Jan 16, 2020

@jamesorlakin ping

@jamesorlakin
Copy link
Contributor Author

jamesorlakin commented Feb 2, 2020

Hi all,

Apologies for the general radio silence - everything has been a tad busy of recent and it all generally got forgotten about 😥. In the meantime I've had a look around and #9274 seems to be a generally more polished implementation if you would like to go ahead with that. I don't mind continuing on if @oscarcosta doesn't want to.

@lafriks lafriks removed this from the 1.12.0 milestone Feb 2, 2020
@go-gitea go-gitea locked and limited conversation to collaborators Nov 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. type/enhancement An improvement of existing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants