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