Show More
@@ -26,6 +26,8 b' import logging' | |||
|
26 | 26 | import traceback |
|
27 | 27 | |
|
28 | 28 | from webob.exc import HTTPNotFound |
|
29 | from collections import defaultdict | |
|
30 | from itertools import groupby | |
|
29 | 31 | |
|
30 | 32 | from pylons import request, response, session, tmpl_context as c, url |
|
31 | 33 | from pylons.controllers.util import abort, redirect |
@@ -199,6 +201,24 b' class PullrequestsController(BaseRepoCon' | |||
|
199 | 201 | # valid ID |
|
200 | 202 | if not c.pull_request: |
|
201 | 203 | raise HTTPNotFound |
|
204 | cc_model = ChangesetCommentsModel() | |
|
205 | cs_model = ChangesetStatusModel() | |
|
206 | _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, | |
|
207 | pull_request=c.pull_request, | |
|
208 | with_revisions=True) | |
|
209 | ||
|
210 | cs_statuses = defaultdict(list) | |
|
211 | for st in _cs_statuses: | |
|
212 | cs_statuses[st.author.username] += [st] | |
|
213 | ||
|
214 | c.pull_request_reviewers = [] | |
|
215 | for o in c.pull_request.reviewers: | |
|
216 | st = cs_statuses.get(o.user.username, None) | |
|
217 | if st: | |
|
218 | sorter = lambda k: k.version | |
|
219 | st = [(x, list(y)[0]) | |
|
220 | for x, y in (groupby(sorted(st, key=sorter), sorter))] | |
|
221 | c.pull_request_reviewers.append([o.user, st]) | |
|
202 | 222 | |
|
203 | 223 | # pull_requests repo_name we opened it against |
|
204 | 224 | # ie. other_repo must match |
@@ -210,23 +230,23 b' class PullrequestsController(BaseRepoCon' | |||
|
210 | 230 | |
|
211 | 231 | # inline comments |
|
212 | 232 | c.inline_cnt = 0 |
|
213 |
c.inline_comments = |
|
|
214 |
|
|
|
215 |
|
|
|
233 | c.inline_comments = cc_model.get_inline_comments( | |
|
234 | c.rhodecode_db_repo.repo_id, | |
|
235 | pull_request=pull_request_id) | |
|
216 | 236 | # count inline comments |
|
217 | 237 | for __, lines in c.inline_comments: |
|
218 | 238 | for comments in lines.values(): |
|
219 | 239 | c.inline_cnt += len(comments) |
|
220 | 240 | # comments |
|
221 | c.comments = ChangesetCommentsModel()\ | |
|
222 | .get_comments(c.rhodecode_db_repo.repo_id, | |
|
223 | pull_request=pull_request_id) | |
|
241 | c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, | |
|
242 | pull_request=pull_request_id) | |
|
224 | 243 | |
|
225 | 244 | # changeset(pull-request) status |
|
226 |
c.current_changeset_status = |
|
|
227 |
|
|
|
228 |
|
|
|
245 | c.current_changeset_status = cs_model.calculate_status( | |
|
246 | c.pull_request_reviewers | |
|
247 | ) | |
|
229 | 248 | c.changeset_statuses = ChangesetStatus.STATUSES |
|
249 | ||
|
230 | 250 | return render('/pullrequests/pullrequest_show.html') |
|
231 | 251 | |
|
232 | 252 | @jsonify |
@@ -24,6 +24,7 b'' | |||
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | import logging |
|
27 | from collections import defaultdict | |
|
27 | 28 | |
|
28 | 29 | from rhodecode.model import BaseModel |
|
29 | 30 | from rhodecode.model.db import ChangesetStatus, PullRequest |
@@ -39,6 +40,52 b' class ChangesetStatusModel(BaseModel):' | |||
|
39 | 40 | def __get_pull_request(self, pull_request): |
|
40 | 41 | return self._get_instance(PullRequest, pull_request) |
|
41 | 42 | |
|
43 | def _get_status_query(self, repo, revision, pull_request, | |
|
44 | with_revisions=False): | |
|
45 | repo = self._get_repo(repo) | |
|
46 | ||
|
47 | q = ChangesetStatus.query()\ | |
|
48 | .filter(ChangesetStatus.repo == repo) | |
|
49 | if not with_revisions: | |
|
50 | q = q.filter(ChangesetStatus.version == 0) | |
|
51 | ||
|
52 | if revision: | |
|
53 | q = q.filter(ChangesetStatus.revision == revision) | |
|
54 | elif pull_request: | |
|
55 | pull_request = self.__get_pull_request(pull_request) | |
|
56 | q = q.filter(ChangesetStatus.pull_request == pull_request) | |
|
57 | else: | |
|
58 | raise Exception('Please specify revision or pull_request') | |
|
59 | q.order_by(ChangesetStatus.version.asc()) | |
|
60 | return q | |
|
61 | ||
|
62 | def calculate_status(self, statuses_by_reviewers): | |
|
63 | """ | |
|
64 | leading one wins, if number of occurences are equal than weaker wins | |
|
65 | ||
|
66 | :param statuses_by_reviewers: | |
|
67 | """ | |
|
68 | status = None | |
|
69 | votes = defaultdict(int) | |
|
70 | reviewers_number = len(statuses_by_reviewers) | |
|
71 | for user, statuses in statuses_by_reviewers: | |
|
72 | if statuses: | |
|
73 | ver, latest = statuses[0] | |
|
74 | votes[latest.status] += 1 | |
|
75 | else: | |
|
76 | votes[ChangesetStatus.DEFAULT] += 1 | |
|
77 | ||
|
78 | if votes.get(ChangesetStatus.STATUS_APPROVED) == reviewers_number: | |
|
79 | return ChangesetStatus.STATUS_APPROVED | |
|
80 | else: | |
|
81 | return ChangesetStatus.STATUS_UNDER_REVIEW | |
|
82 | ||
|
83 | def get_statuses(self, repo, revision=None, pull_request=None, | |
|
84 | with_revisions=False): | |
|
85 | q = self._get_status_query(repo, revision, pull_request, | |
|
86 | with_revisions) | |
|
87 | return q.all() | |
|
88 | ||
|
42 | 89 | def get_status(self, repo, revision=None, pull_request=None): |
|
43 | 90 | """ |
|
44 | 91 | Returns latest status of changeset for given revision or for given |
@@ -52,19 +99,7 b' class ChangesetStatusModel(BaseModel):' | |||
|
52 | 99 | :param pull_request: pull_request reference |
|
53 | 100 | :type: |
|
54 | 101 | """ |
|
55 | repo = self._get_repo(repo) | |
|
56 | ||
|
57 | q = ChangesetStatus.query()\ | |
|
58 | .filter(ChangesetStatus.repo == repo)\ | |
|
59 | .filter(ChangesetStatus.version == 0) | |
|
60 | ||
|
61 | if revision: | |
|
62 | q = q.filter(ChangesetStatus.revision == revision) | |
|
63 | elif pull_request: | |
|
64 | pull_request = self.__get_pull_request(pull_request) | |
|
65 | q = q.filter(ChangesetStatus.pull_request == pull_request) | |
|
66 | else: | |
|
67 | raise Exception('Please specify revision or pull_request') | |
|
102 | q = self._get_status_query(repo, revision, pull_request) | |
|
68 | 103 | |
|
69 | 104 | # need to use first here since there can be multiple statuses |
|
70 | 105 | # returned from pull_request |
@@ -1371,14 +1371,17 b' class ChangesetStatus(Base, BaseModel):' | |||
|
1371 | 1371 | {'extend_existing': True, 'mysql_engine': 'InnoDB', |
|
1372 | 1372 | 'mysql_charset': 'utf8'} |
|
1373 | 1373 | ) |
|
1374 | STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' | |
|
1375 | STATUS_APPROVED = 'approved' | |
|
1376 | STATUS_REJECTED = 'rejected' | |
|
1377 | STATUS_UNDER_REVIEW = 'under_review' | |
|
1374 | 1378 | |
|
1375 | 1379 | STATUSES = [ |
|
1376 |
( |
|
|
1377 |
( |
|
|
1378 |
( |
|
|
1379 |
( |
|
|
1380 | (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default | |
|
1381 | (STATUS_APPROVED, _("Approved")), | |
|
1382 | (STATUS_REJECTED, _("Rejected")), | |
|
1383 | (STATUS_UNDER_REVIEW, _("Under Review")), | |
|
1380 | 1384 | ] |
|
1381 | DEFAULT = STATUSES[0][0] | |
|
1382 | 1385 | |
|
1383 | 1386 | changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) |
|
1384 | 1387 | repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) |
@@ -1395,6 +1398,12 b' class ChangesetStatus(Base, BaseModel):' | |||
|
1395 | 1398 | comment = relationship('ChangesetComment', lazy='joined') |
|
1396 | 1399 | pull_request = relationship('PullRequest', lazy='joined') |
|
1397 | 1400 | |
|
1401 | def __unicode__(self): | |
|
1402 | return u"<%s('%s:%s')>" % ( | |
|
1403 | self.__class__.__name__, | |
|
1404 | self.status, self.author | |
|
1405 | ) | |
|
1406 | ||
|
1398 | 1407 | @classmethod |
|
1399 | 1408 | def get_status_lbl(cls, value): |
|
1400 | 1409 | return dict(cls.STATUSES).get(value) |
@@ -21,38 +21,62 b'' | |||
|
21 | 21 | </div> |
|
22 | 22 | |
|
23 | 23 | <h3>${_('Title')}: ${c.pull_request.title}</h3> |
|
24 |
<div class="changeset-status-container" style="float:left;padding:0px 20px |
|
|
24 | <div class="changeset-status-container" style="float:left;padding:0px 20px 0px 20px"> | |
|
25 | 25 | %if c.current_changeset_status: |
|
26 | 26 | <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div> |
|
27 | 27 | <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div> |
|
28 | 28 | %endif |
|
29 | 29 | </div> |
|
30 | <div style="padding:4px"> | |
|
30 | <div style="padding:4px 4px 10px 4px"> | |
|
31 | 31 | <div>${h.fmt_date(c.pull_request.created_on)}</div> |
|
32 | 32 | </div> |
|
33 | 33 | |
|
34 | ##DIFF | |
|
35 | ||
|
36 | <div class="table"> | |
|
37 | <div id="body" class="diffblock"> | |
|
38 |
<div style="white-space:pre-wrap;padding:5px">${ |
|
|
39 | </div> | |
|
40 | <div id="changeset_compare_view_content"> | |
|
41 | ##CS | |
|
42 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div> | |
|
43 | <%include file="/compare/compare_cs.html" /> | |
|
44 | ||
|
45 | ## FILES | |
|
46 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div> | |
|
47 | <div class="cs_files"> | |
|
48 | %for fid, change, f, stat in c.files: | |
|
49 | <div class="cs_${change}"> | |
|
50 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> | |
|
51 | <div class="changes">${h.fancy_file_stats(stat)}</div> | |
|
34 | ## REVIEWERS | |
|
35 | <div> | |
|
36 | <div class="table" style="float:right;width:46%;clear:none"> | |
|
37 | <div id="body" class="diffblock"> | |
|
38 | <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div> | |
|
39 | </div> | |
|
40 | <div style="border: 1px solid #CCC"> | |
|
41 | <div class="group_members_wrap"> | |
|
42 | <ul class="group_members"> | |
|
43 | %for user,status in c.pull_request_reviewers: | |
|
44 | <li> | |
|
45 | <div class="group_member"> | |
|
46 | <div style="float:left;padding:3px"> | |
|
47 | <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/> | |
|
48 | </div> | |
|
49 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div> | |
|
50 | <div style="float:left">${user.username}</div> | |
|
52 | 51 | </div> |
|
52 | </li> | |
|
53 | 53 | %endfor |
|
54 |
</ |
|
|
55 | </div> | |
|
54 | </ul> | |
|
55 | </div> | |
|
56 | </div> | |
|
57 | </div> | |
|
58 | ##DIFF | |
|
59 | <div class="table" style="float:left;width:46%;clear:none"> | |
|
60 | <div id="body" class="diffblock"> | |
|
61 | <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div> | |
|
62 | </div> | |
|
63 | <div id="changeset_compare_view_content"> | |
|
64 | ##CS | |
|
65 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div> | |
|
66 | <%include file="/compare/compare_cs.html" /> | |
|
67 | ||
|
68 | ## FILES | |
|
69 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div> | |
|
70 | <div class="cs_files"> | |
|
71 | %for fid, change, f, stat in c.files: | |
|
72 | <div class="cs_${change}"> | |
|
73 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> | |
|
74 | <div class="changes">${h.fancy_file_stats(stat)}</div> | |
|
75 | </div> | |
|
76 | %endfor | |
|
77 | </div> | |
|
78 | </div> | |
|
79 | </div> | |
|
56 | 80 | </div> |
|
57 | 81 | <script> |
|
58 | 82 | var _USERS_AC_DATA = ${c.users_array|n}; |
General Comments 0
You need to be logged in to leave comments.
Login now