##// END OF EJS Templates
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
marcink -
r1084:84545e70 default
parent child Browse files
Show More
@@ -39,19 +39,20 b' from rhodecode.lib.auth import ('
39 39 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils2 import safe_int, md5
42 from rhodecode.lib.utils2 import safe_int, md5, str2bool
43 43 from rhodecode.lib.ext_json import json
44 44
45 45 from rhodecode.model.validation_schema.schemas import user_schema
46 46 from rhodecode.model.db import (
47 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
48 UserFollowing)
47 Repository, PullRequest, UserEmailMap, User, UserFollowing)
49 48 from rhodecode.model.forms import UserForm
50 49 from rhodecode.model.scm import RepoList
51 50 from rhodecode.model.user import UserModel
52 51 from rhodecode.model.repo import RepoModel
53 52 from rhodecode.model.auth_token import AuthTokenModel
54 53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel
55 from rhodecode.model.comment import ChangesetCommentsModel
55 56
56 57 log = logging.getLogger(__name__)
57 58
@@ -289,25 +290,85 b' class MyAccountController(BaseController'
289 290 category='success')
290 291 return redirect(url('my_account_emails'))
291 292
293 def _extract_ordering(self, request):
294 column_index = safe_int(request.GET.get('order[0][column]'))
295 order_dir = request.GET.get('order[0][dir]', 'desc')
296 order_by = request.GET.get(
297 'columns[%s][data][sort]' % column_index, 'name_raw')
298 return order_by, order_dir
299
300 def _get_pull_requests_list(self, statuses):
301 start = safe_int(request.GET.get('start'), 0)
302 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
303 order_by, order_dir = self._extract_ordering(request)
304
305 pull_requests = PullRequestModel().get_im_participating_in(
306 user_id=c.rhodecode_user.user_id,
307 statuses=statuses,
308 offset=start, length=length, order_by=order_by,
309 order_dir=order_dir)
310
311 pull_requests_total_count = PullRequestModel().count_im_participating_in(
312 user_id=c.rhodecode_user.user_id, statuses=statuses)
313
314 from rhodecode.lib.utils import PartialRenderer
315 _render = PartialRenderer('data_table/_dt_elements.html')
316 data = []
317 for pr in pull_requests:
318 repo_id = pr.target_repo_id
319 comments = ChangesetCommentsModel().get_all_comments(
320 repo_id, pull_request=pr)
321 owned = pr.user_id == c.rhodecode_user.user_id
322 status = pr.calculated_review_status()
323
324 data.append({
325 'target_repo': _render('pullrequest_target_repo',
326 pr.target_repo.repo_name),
327 'name': _render('pullrequest_name',
328 pr.pull_request_id, pr.target_repo.repo_name,
329 short=True),
330 'name_raw': pr.pull_request_id,
331 'status': _render('pullrequest_status', status),
332 'title': _render(
333 'pullrequest_title', pr.title, pr.description),
334 'description': h.escape(pr.description),
335 'updated_on': _render('pullrequest_updated_on',
336 h.datetime_to_time(pr.updated_on)),
337 'updated_on_raw': h.datetime_to_time(pr.updated_on),
338 'created_on': _render('pullrequest_updated_on',
339 h.datetime_to_time(pr.created_on)),
340 'created_on_raw': h.datetime_to_time(pr.created_on),
341 'author': _render('pullrequest_author',
342 pr.author.full_contact, ),
343 'author_raw': pr.author.full_name,
344 'comments': _render('pullrequest_comments', len(comments)),
345 'comments_raw': len(comments),
346 'closed': pr.is_closed(),
347 'owned': owned
348 })
349 # json used to render the grid
350 data = ({
351 'data': data,
352 'recordsTotal': pull_requests_total_count,
353 'recordsFiltered': pull_requests_total_count,
354 })
355 return data
356
292 357 def my_account_pullrequests(self):
293 358 c.active = 'pullrequests'
294 359 self.__load_data()
295 c.show_closed = request.GET.get('pr_show_closed')
296
297 def _filter(pr):
298 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
299 if not c.show_closed:
300 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
301 return s
360 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
302 361
303 c.my_pull_requests = _filter(
304 PullRequest.query().filter(
305 PullRequest.user_id == c.rhodecode_user.user_id).all())
306 my_prs = [
307 x.pull_request for x in PullRequestReviewers.query().filter(
308 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
309 c.participate_in_pull_requests = _filter(my_prs)
310 return render('admin/my_account/my_account.html')
362 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
363 if c.show_closed:
364 statuses += [PullRequest.STATUS_CLOSED]
365 data = self._get_pull_requests_list(statuses)
366 if not request.is_xhr:
367 c.data_participate = json.dumps(data['data'])
368 c.records_total_participate = data['recordsTotal']
369 return render('admin/my_account/my_account.html')
370 else:
371 return json.dumps(data)
311 372
312 373 def my_account_auth_tokens(self):
313 374 c.active = 'auth_tokens'
@@ -3203,14 +3203,10 b' class PullRequest(Base, _PullRequestBase'
3203 3203 }
3204 3204
3205 3205 def calculated_review_status(self):
3206 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3207 # because it's tricky on how to use ChangesetStatusModel from there
3208 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3209 3206 from rhodecode.model.changeset_status import ChangesetStatusModel
3210 3207 return ChangesetStatusModel().calculated_review_status(self)
3211 3208
3212 3209 def reviewers_statuses(self):
3213 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3214 3210 from rhodecode.model.changeset_status import ChangesetStatusModel
3215 3211 return ChangesetStatusModel().reviewers_statuses(self)
3216 3212
@@ -31,6 +31,7 b' import urllib'
31 31
32 32 from pylons.i18n.translation import _
33 33 from pylons.i18n.translation import lazy_ugettext
34 from sqlalchemy import or_
34 35
35 36 from rhodecode.lib import helpers as h, hooks_utils, diffs
36 37 from rhodecode.lib.compat import OrderedDict
@@ -159,12 +160,16 b' class PullRequestModel(BaseModel):'
159 160 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
160 161 opened_by=None, order_by=None,
161 162 order_dir='desc'):
162 repo = self._get_repo(repo_name)
163 repo = None
164 if repo_name:
165 repo = self._get_repo(repo_name)
166
163 167 q = PullRequest.query()
168
164 169 # source or target
165 if source:
170 if repo and source:
166 171 q = q.filter(PullRequest.source_repo == repo)
167 else:
172 elif repo:
168 173 q = q.filter(PullRequest.target_repo == repo)
169 174
170 175 # closed,opened
@@ -179,7 +184,8 b' class PullRequestModel(BaseModel):'
179 184 order_map = {
180 185 'name_raw': PullRequest.pull_request_id,
181 186 'title': PullRequest.title,
182 'updated_on_raw': PullRequest.updated_on
187 'updated_on_raw': PullRequest.updated_on,
188 'target_repo': PullRequest.target_repo_id
183 189 }
184 190 if order_dir == 'asc':
185 191 q = q.order_by(order_map[order_by].asc())
@@ -337,6 +343,59 b' class PullRequestModel(BaseModel):'
337 343 PullRequestReviewers.user_id == user_id).all()
338 344 ]
339 345
346 def _prepare_participating_query(self, user_id=None, statuses=None,
347 order_by=None, order_dir='desc'):
348 q = PullRequest.query()
349 if user_id:
350 reviewers_subquery = Session().query(
351 PullRequestReviewers.pull_request_id).filter(
352 PullRequestReviewers.user_id == user_id).subquery()
353 user_filter= or_(
354 PullRequest.user_id == user_id,
355 PullRequest.pull_request_id.in_(reviewers_subquery)
356 )
357 q = PullRequest.query().filter(user_filter)
358
359 # closed,opened
360 if statuses:
361 q = q.filter(PullRequest.status.in_(statuses))
362
363 if order_by:
364 order_map = {
365 'name_raw': PullRequest.pull_request_id,
366 'title': PullRequest.title,
367 'updated_on_raw': PullRequest.updated_on,
368 'target_repo': PullRequest.target_repo_id
369 }
370 if order_dir == 'asc':
371 q = q.order_by(order_map[order_by].asc())
372 else:
373 q = q.order_by(order_map[order_by].desc())
374
375 return q
376
377 def count_im_participating_in(self, user_id=None, statuses=None):
378 q = self._prepare_participating_query(user_id, statuses=statuses)
379 return q.count()
380
381 def get_im_participating_in(
382 self, user_id=None, statuses=None, offset=0,
383 length=None, order_by=None, order_dir='desc'):
384 """
385 Get all Pull requests that i'm participating in, or i have opened
386 """
387
388 q = self._prepare_participating_query(
389 user_id, statuses=statuses, order_by=order_by,
390 order_dir=order_dir)
391
392 if length:
393 pull_requests = q.limit(length).offset(offset).all()
394 else:
395 pull_requests = q.all()
396
397 return pull_requests
398
340 399 def get_versions(self, pull_request):
341 400 """
342 401 returns version of pull request sorted by ID descending
@@ -11,123 +11,12 b''
11 11 </div>
12 12
13 13 <div class="panel panel-default">
14 <div class="panel-heading">
15 <h3 class="panel-title">${_('Pull Requests You Opened')}: ${len(c.my_pull_requests)}</h3>
16 </div>
17 <div class="panel-body">
18 <div class="pullrequestlist">
19 %if c.my_pull_requests:
20 <table class="rctable">
21 <thead>
22 <th class="td-status"></th>
23 <th>${_('Target Repo')}</th>
24 <th>${_('Author')}</th>
25 <th></th>
26 <th>${_('Title')}</th>
27 <th class="td-time">${_('Last Update')}</th>
28 <th></th>
29 </thead>
30 %for pull_request in c.my_pull_requests:
31 <tr class="${'closed' if pull_request.is_closed() else ''} prwrapper">
32 <td class="td-status">
33 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
34 </td>
35 <td class="truncate-wrap td-componentname">
36 <div class="truncate">
37 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
38 </div>
39 </td>
40 <td class="user">
41 ${base.gravatar_with_user(pull_request.author.email, 16)}
42 </td>
43 <td class="td-message expand_commit" data-pr-id="m${pull_request.pull_request_id}" title="${_('Expand commit message')}">
44 <div class="show_more_col">
45 <i class="show_more"></i>&nbsp;
46 </div>
47 </td>
48 <td class="mid td-description">
49 <div class="log-container truncate-wrap">
50 <div class="message truncate" id="c-m${pull_request.pull_request_id}"><a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">#${pull_request.pull_request_id}: ${pull_request.title}</a>\
51 %if pull_request.is_closed():
52 &nbsp;(${_('Closed')})\
53 %endif
54 <br/>${pull_request.description}</div>
55 </div>
56 </td>
57 <td class="td-time">
58 ${h.age_component(pull_request.updated_on)}
59 </td>
60 <td class="td-action repolist_actions">
61 ${h.secure_form(url('pullrequest_delete', repo_name=pull_request.target_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')}
62 ${h.submit('remove_%s' % pull_request.pull_request_id, _('Delete'),
63 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
64 ${h.end_form()}
65 </td>
66 </tr>
67 %endfor
68 </table>
69 %else:
70 <h2><span class="empty_data">${_('You currently have no open pull requests.')}</span></h2>
71 %endif
14 <div class="panel-heading">
15 <h3 class="panel-title">${_('Pull Requests You Participate In')}: ${c.records_total_participate}</h3>
72 16 </div>
73 </div>
74 </div>
75
76 <div class="panel panel-default">
77 <div class="panel-heading">
78 <h3 class="panel-title">${_('Pull Requests You Participate In')}: ${len(c.participate_in_pull_requests)}</h3>
79 </div>
80
81 <div class="panel-body">
82 <div class="pullrequestlist">
83 %if c.participate_in_pull_requests:
84 <table class="rctable">
85 <thead>
86 <th class="td-status"></th>
87 <th>${_('Target Repo')}</th>
88 <th>${_('Author')}</th>
89 <th></th>
90 <th>${_('Title')}</th>
91 <th class="td-time">${_('Last Update')}</th>
92 </thead>
93 %for pull_request in c.participate_in_pull_requests:
94 <tr class="${'closed' if pull_request.is_closed() else ''} prwrapper">
95 <td class="td-status">
96 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
97 </td>
98 <td class="truncate-wrap td-componentname">
99 <div class="truncate">
100 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
101 </div>
102 </td>
103 <td class="user">
104 ${base.gravatar_with_user(pull_request.author.email, 16)}
105 </td>
106 <td class="td-message expand_commit" data-pr-id="p${pull_request.pull_request_id}" title="${_('Expand commit message')}">
107 <div class="show_more_col">
108 <i class="show_more"></i>&nbsp;
109 </div>
110 </td>
111 <td class="mid td-description">
112 <div class="log-container truncate-wrap">
113 <div class="message truncate" id="c-p${pull_request.pull_request_id}"><a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">#${pull_request.pull_request_id}: ${pull_request.title}</a>\
114 %if pull_request.is_closed():
115 &nbsp;(${_('Closed')})\
116 %endif
117 <br/>${pull_request.description}</div>
118 </div>
119 </td>
120 <td class="td-time">
121 ${h.age_component(pull_request.updated_on)}
122 </td>
123 </tr>
124 %endfor
125 </table>
126 %else:
127 <h2 class="empty_data">${_('There are currently no open pull requests requiring your participation.')}</h2>
128 %endif
17 <div class="panel-body">
18 <table id="pull_request_list_table_participate" class="display"></table>
129 19 </div>
130 </div>
131 20 </div>
132 21
133 22 <script>
@@ -139,17 +28,51 b''
139 28 window.location = "${h.url('my_account_pullrequests')}";
140 29 }
141 30 });
142 $('.expand_commit').on('click',function(e){
143 var target_expand = $(this);
144 var cid = target_expand.data('prId');
31 $(document).ready(function() {
32
33 var columnsDefs = [
34 { data: {"_": "status",
35 "sort": "status"}, title: "", className: "td-status", orderable: false},
36 { data: {"_": "target_repo",
37 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
38 { data: {"_": "name",
39 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
40 { data: {"_": "author",
41 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
42 { data: {"_": "title",
43 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
44 { data: {"_": "comments",
45 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
46 { data: {"_": "updated_on",
47 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
48 ];
145 49
146 if (target_expand.hasClass('open')){
147 $('#c-'+cid).css({'height': '2.75em', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
148 target_expand.removeClass('open');
149 }
150 else {
151 $('#c-'+cid).css({'height': 'auto', 'text-overflow': 'initial', 'overflow':'visible'});
152 target_expand.addClass('open');
153 }
50 // participating object list
51 $('#pull_request_list_table_participate').DataTable({
52 data: ${c.data_participate|n},
53 processing: true,
54 serverSide: true,
55 deferLoading: ${c.records_total_participate},
56 ajax: "",
57 dom: 'tp',
58 pageLength: ${c.visual.dashboard_items},
59 order: [[ 2, "desc" ]],
60 columns: columnsDefs,
61 language: {
62 paginate: DEFAULT_GRID_PAGINATION,
63 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
64 },
65 "drawCallback": function( settings, json ) {
66 timeagoActivate();
67 },
68 "createdRow": function ( row, data, index ) {
69 if (data['closed']) {
70 $(row).addClass('closed');
71 }
72 if (data['owned']) {
73 $(row).addClass('owned');
74 }
75 }
76 });
154 77 });
155 78 </script>
@@ -271,6 +271,12 b''
271 271
272 272
273 273 ## PULL REQUESTS GRID RENDERERS
274
275 <%def name="pullrequest_target_repo(repo_name)">
276 <div class="truncate">
277 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
278 </div>
279 </%def>
274 280 <%def name="pullrequest_status(status)">
275 281 <div class="${'flag_status %s' % status} pull-left"></div>
276 282 </%def>
@@ -284,9 +290,13 b''
284 290 <i class="icon-comment icon-comment-colored"></i> ${comments_nr}
285 291 </%def>
286 292
287 <%def name="pullrequest_name(pull_request_id, target_repo_name)">
293 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
288 294 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
289 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
295 % if short:
296 #${pull_request_id}
297 % else:
298 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
299 % endif
290 300 </a>
291 301 </%def>
292 302
@@ -85,14 +85,13 b' class TestMyAccountController(TestContro'
85 85 def test_my_account_my_pullrequests(self, pr_util):
86 86 self.log_user()
87 87 response = self.app.get(url('my_account_pullrequests'))
88 response.mustcontain('You currently have no open pull requests.')
88 response.mustcontain('There are currently no open pull '
89 'requests requiring your participation.')
89 90
90 91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
91 92 response = self.app.get(url('my_account_pullrequests'))
92 response.mustcontain('There are currently no open pull requests '
93 'requiring your participation')
94
95 response.mustcontain('#%s: TestMyAccountPR' % pr.pull_request_id)
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
94 response.mustcontain('TestMyAccountPR')
96 95
97 96 def test_my_account_my_emails(self):
98 97 self.log_user()
General Comments 0
You need to be logged in to leave comments. Login now