changeset 4359:cfd9115db2a5

pull requests: cleanup of PR status voting calculation
author Mads Kiilerich <madski@unity3d.com>
date Tue, 10 Dec 2013 19:30:37 +0100
parents 068ca0afcf9f
children 05af189da2ae
files kallithea/controllers/pullrequests.py kallithea/model/changeset_status.py kallithea/templates/pullrequests/pullrequest_show.html
diffstat 3 files changed, 45 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/pullrequests.py	Fri Jul 18 18:47:02 2014 +0200
+++ b/kallithea/controllers/pullrequests.py	Tue Dec 10 19:30:37 2013 +0100
@@ -483,25 +483,6 @@
         c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
         cc_model = ChangesetCommentsModel()
         cs_model = ChangesetStatusModel()
-        _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
-                                            pull_request=c.pull_request,
-                                            with_revisions=True)
-
-        cs_statuses = defaultdict(list)
-        for st in _cs_statuses:
-            cs_statuses[st.author.username] += [st]
-
-        c.pull_request_reviewers = []
-        c.pull_request_pending_reviewers = []
-        for o in c.pull_request.reviewers:
-            st = cs_statuses.get(o.user.username, None)
-            if st:
-                sorter = lambda k: k.version
-                st = [(x, list(y)[0])
-                      for x, y in (groupby(sorted(st, key=sorter), sorter))]
-            else:
-                c.pull_request_pending_reviewers.append(o.user)
-            c.pull_request_reviewers.append([o.user, st])
 
         # pull_requests repo_name we opened it against
         # ie. other_repo must match
@@ -526,9 +507,10 @@
                                            pull_request=pull_request_id)
 
         # (badly named) pull-request status calculation based on reviewer votes
-        c.current_changeset_status = cs_model.calculate_status(
-                                        c.pull_request_reviewers,
-                                         )
+        (c.pull_request_reviewers,
+         c.pull_request_pending_reviewers,
+         c.current_voting_result,
+         ) = cs_model.calculate_pull_request_result(c.pull_request)
         c.changeset_statuses = ChangesetStatus.STATUSES
 
         c.as_form = False
--- a/kallithea/model/changeset_status.py	Fri Jul 18 18:47:02 2014 +0200
+++ b/kallithea/model/changeset_status.py	Tue Dec 10 19:30:37 2013 +0100
@@ -65,26 +65,39 @@
         q = q.order_by(ChangesetStatus.version.asc())
         return q
 
-    def calculate_status(self, statuses_by_reviewers):
+    def calculate_pull_request_result(self, pull_request):
         """
-        approved if consensus
-        (old description: leading one wins, if number of occurrences are equal than weaker wins)
-
-        :param statuses_by_reviewers:
+        Policy: approve if consensus. Only approve and reject counts as valid votes.
         """
-        votes = defaultdict(int)
-        reviewers_number = len(statuses_by_reviewers)
-        for user, statuses in statuses_by_reviewers:
-            if statuses:
-                ver, latest = statuses[0]
-                votes[latest.status] += 1
-            else:
-                votes[ChangesetStatus.DEFAULT] += 1
 
-        if votes.get(ChangesetStatus.STATUS_APPROVED) == reviewers_number:
-            return ChangesetStatus.STATUS_APPROVED
-        else:
-            return ChangesetStatus.STATUS_UNDER_REVIEW
+        # collect latest votes from all voters
+        cs_statuses = dict()
+        for st in reversed(self.get_statuses(pull_request.org_repo,
+                                             pull_request=pull_request,
+                                             with_revisions=True)):
+            cs_statuses[st.author.username] = st
+        # collect votes from official reviewers
+        pull_request_reviewers = []
+        pull_request_pending_reviewers = []
+        approved_votes = 0
+        for r in pull_request.reviewers:
+            st = cs_statuses.get(r.user.username)
+            if st and st.status == ChangesetStatus.STATUS_APPROVED:
+                approved_votes += 1
+            if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
+                                       ChangesetStatus.STATUS_UNDER_REVIEW):
+                st = None
+                pull_request_pending_reviewers.append(r.user)
+            pull_request_reviewers.append((r.user, st))
+
+        # calculate result
+        result = ChangesetStatus.STATUS_UNDER_REVIEW
+        if approved_votes and approved_votes == len(pull_request.reviewers):
+            result = ChangesetStatus.STATUS_APPROVED
+
+        return (pull_request_reviewers,
+                pull_request_pending_reviewers,
+                result)
 
     def get_statuses(self, repo, revision=None, pull_request=None,
                      with_revisions=False):
@@ -156,6 +169,7 @@
 
         def _create_status(user, repo, status, comment, revision, pull_request):
             new_status = ChangesetStatus()
+            new_status.version = 0 # default
             new_status.author = self._get_user(user)
             new_status.repo = self._get_repo(repo)
             new_status.status = status
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Fri Jul 18 18:47:02 2014 +0200
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Tue Dec 10 19:30:37 2013 +0100
@@ -78,18 +78,18 @@
 
         <div class="field">
           <div class="label-summary">
-              <label>${_('Review status')}:</label>
+              <label>${_('Reviewer voting result')}:</label>
           </div>
           <div class="input">
             <div class="changeset-status-container" style="float:none;clear:both">
-            %if c.current_changeset_status:
+            %if c.current_voting_result:
               <div class="changeset-status-ico" style="padding:0px 4px 0px 0px">
-                  <img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" title="${_('Pull request status calculated from votes')}"/></div>
+                  <img src="${h.url('/images/icons/flag_status_%s.png' % c.current_voting_result)}" title="${_('Pull request status calculated from votes')}"/></div>
               <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
                 %if c.pull_request.is_closed():
                     ${_('Closed')},
                 %endif
-                ${h.changeset_status_lbl(c.current_changeset_status)}
+                ${h.changeset_status_lbl(c.current_voting_result)}
               </div>
 
             %endif
@@ -103,8 +103,10 @@
           <div class="input">
             % if len(c.pull_request_pending_reviewers) > 0:
                 <div class="tooltip" title="${h.tooltip(', '.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
+            % elif len(c.pull_request_reviewers) > 0:
+                <div>${_('Pull request was reviewed by all reviewers')}</div>
             %else:
-                <div>${_('Pull request was reviewed by all reviewers')}</div>
+                <div>${_('There are no reviewers')}</div>
             %endif
           </div>
         </div>
@@ -182,8 +184,8 @@
             %for member,status in c.pull_request_reviewers:
               <li id="reviewer_${member.user_id}">
                 <div class="reviewers_member">
-                    <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
-                      <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
+                    <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status.status if status else 'not_reviewed'))}">
+                      <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status.status if status else 'not_reviewed')))}"/>
                     </div>
                   <div class="reviewer_gravatar gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
                   <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
@@ -293,7 +295,7 @@
       ## main comment form and it status
       ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
                                 pull_request_id=c.pull_request.pull_request_id),
-                         c.current_changeset_status,
+                         c.current_voting_result,
                          is_pr=True, change_status=c.allowed_to_change_status)}
     %endif