diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py +++ b/rhodecode/controllers/pullrequests.py @@ -26,6 +26,8 @@ import logging import traceback from webob.exc import HTTPNotFound +from collections import defaultdict +from itertools import groupby from pylons import request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect @@ -199,6 +201,24 @@ class PullrequestsController(BaseRepoCon # valid ID if not c.pull_request: raise HTTPNotFound + 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 = [] + 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))] + c.pull_request_reviewers.append([o.user, st]) # pull_requests repo_name we opened it against # ie. other_repo must match @@ -210,23 +230,23 @@ class PullrequestsController(BaseRepoCon # inline comments c.inline_cnt = 0 - c.inline_comments = ChangesetCommentsModel()\ - .get_inline_comments(c.rhodecode_db_repo.repo_id, - pull_request=pull_request_id) + c.inline_comments = cc_model.get_inline_comments( + c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) # comments - c.comments = ChangesetCommentsModel()\ - .get_comments(c.rhodecode_db_repo.repo_id, - pull_request=pull_request_id) + c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) # changeset(pull-request) status - c.current_changeset_status = ChangesetStatusModel()\ - .get_status(c.pull_request.org_repo, - pull_request=c.pull_request) + c.current_changeset_status = cs_model.calculate_status( + c.pull_request_reviewers + ) c.changeset_statuses = ChangesetStatus.STATUSES + return render('/pullrequests/pullrequest_show.html') @jsonify diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py --- a/rhodecode/model/changeset_status.py +++ b/rhodecode/model/changeset_status.py @@ -24,6 +24,7 @@ import logging +from collections import defaultdict from rhodecode.model import BaseModel from rhodecode.model.db import ChangesetStatus, PullRequest @@ -39,6 +40,52 @@ class ChangesetStatusModel(BaseModel): def __get_pull_request(self, pull_request): return self._get_instance(PullRequest, pull_request) + def _get_status_query(self, repo, revision, pull_request, + with_revisions=False): + repo = self._get_repo(repo) + + q = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == repo) + if not with_revisions: + q = q.filter(ChangesetStatus.version == 0) + + if revision: + q = q.filter(ChangesetStatus.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetStatus.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + q.order_by(ChangesetStatus.version.asc()) + return q + + def calculate_status(self, statuses_by_reviewers): + """ + leading one wins, if number of occurences are equal than weaker wins + + :param statuses_by_reviewers: + """ + status = None + 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 + + def get_statuses(self, repo, revision=None, pull_request=None, + with_revisions=False): + q = self._get_status_query(repo, revision, pull_request, + with_revisions) + return q.all() + def get_status(self, repo, revision=None, pull_request=None): """ Returns latest status of changeset for given revision or for given @@ -52,19 +99,7 @@ class ChangesetStatusModel(BaseModel): :param pull_request: pull_request reference :type: """ - repo = self._get_repo(repo) - - q = ChangesetStatus.query()\ - .filter(ChangesetStatus.repo == repo)\ - .filter(ChangesetStatus.version == 0) - - if revision: - q = q.filter(ChangesetStatus.revision == revision) - elif pull_request: - pull_request = self.__get_pull_request(pull_request) - q = q.filter(ChangesetStatus.pull_request == pull_request) - else: - raise Exception('Please specify revision or pull_request') + q = self._get_status_query(repo, revision, pull_request) # need to use first here since there can be multiple statuses # returned from pull_request diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1371,14 +1371,17 @@ class ChangesetStatus(Base, BaseModel): {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' STATUSES = [ - ('not_reviewed', _("Not Reviewed")), # (no icon) and default - ('approved', _("Approved")), - ('rejected', _("Rejected")), - ('under_review', _("Under Review")), + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), ] - DEFAULT = STATUSES[0][0] changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) @@ -1395,6 +1398,12 @@ class ChangesetStatus(Base, BaseModel): comment = relationship('ChangesetComment', lazy='joined') pull_request = relationship('PullRequest', lazy='joined') + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + @classmethod def get_status_lbl(cls, value): return dict(cls.STATUSES).get(value) diff --git a/rhodecode/templates/pullrequests/pullrequest_show.html b/rhodecode/templates/pullrequests/pullrequest_show.html --- a/rhodecode/templates/pullrequests/pullrequest_show.html +++ b/rhodecode/templates/pullrequests/pullrequest_show.html @@ -21,38 +21,62 @@

${_('Title')}: ${c.pull_request.title}

-
+
%if c.current_changeset_status:
[${h.changeset_status_lbl(c.current_changeset_status)}]
%endif
-
+
${h.fmt_date(c.pull_request.created_on)}
- ##DIFF - -
-
-
${h.literal(c.pull_request.description)}
-
-
- ##CS -
${_('Incoming changesets')}
- <%include file="/compare/compare_cs.html" /> - - ## FILES -
${_('Files affected')}
-
- %for fid, change, f, stat in c.files: -
-
${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}
-
${h.fancy_file_stats(stat)}
+ ## REVIEWERS +
+
+
+
${_('Pull request reviewers')}
+
+
+
+
    + %for user,status in c.pull_request_reviewers: +
  • +
    +
    + +
    +
    gravatar
    +
    ${user.username}
    +
  • %endfor -
-
+ +
+
+
+ ##DIFF +
+
+
${h.literal(c.pull_request.description)}
+
+
+ ##CS +
${_('Incoming changesets')}
+ <%include file="/compare/compare_cs.html" /> + + ## FILES +
${_('Files affected')}
+
+ %for fid, change, f, stat in c.files: +
+
${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}
+
${h.fancy_file_stats(stat)}
+
+ %endfor +
+
+