Show More
@@ -95,9 +95,10 b' class TestGetPullRequest(object):' | |||
|
95 | 95 | { |
|
96 | 96 | 'user': reviewer.get_api_data(include_secrets=False, |
|
97 | 97 | details='basic'), |
|
98 | 'reasons': reasons, | |
|
98 | 99 | 'review_status': st[0][1].status if st else 'not_reviewed', |
|
99 | 100 | } |
|
100 | for reviewer, st in pull_request.reviewers_statuses() | |
|
101 | for reviewer, reasons, st in pull_request.reviewers_statuses() | |
|
101 | 102 | ] |
|
102 | 103 | } |
|
103 | 104 | assert_ok(id_, expected, response.body) |
@@ -463,7 +463,12 b' def create_pull_request(' | |||
|
463 | 463 | :type description: Optional(str) |
|
464 | 464 | :param reviewers: Set the new pull request reviewers list. |
|
465 | 465 | :type reviewers: Optional(list) |
|
466 | Accepts username strings or objects of the format: | |
|
467 | { | |
|
468 | 'username': 'nick', 'reasons': ['original author'] | |
|
469 | } | |
|
466 | 470 | """ |
|
471 | ||
|
467 | 472 | source = get_repo_or_error(source_repo) |
|
468 | 473 | target = get_repo_or_error(target_repo) |
|
469 | 474 | if not has_superadmin_permission(apiuser): |
@@ -490,12 +495,21 b' def create_pull_request(' | |||
|
490 | 495 | if not ancestor: |
|
491 | 496 | raise JSONRPCError('no common ancestor found') |
|
492 | 497 | |
|
493 |
reviewer_ |
|
|
494 |
if not isinstance(reviewer_ |
|
|
498 | reviewer_objects = Optional.extract(reviewers) or [] | |
|
499 | if not isinstance(reviewer_objects, list): | |
|
495 | 500 | raise JSONRPCError('reviewers should be specified as a list') |
|
496 | 501 | |
|
497 | reviewer_users = [get_user_or_error(n) for n in reviewer_names] | |
|
498 | reviewer_ids = [u.user_id for u in reviewer_users] | |
|
502 | reviewers_reasons = [] | |
|
503 | for reviewer_object in reviewer_objects: | |
|
504 | reviewer_reasons = [] | |
|
505 | if isinstance(reviewer_object, (basestring, int)): | |
|
506 | reviewer_username = reviewer_object | |
|
507 | else: | |
|
508 | reviewer_username = reviewer_object['username'] | |
|
509 | reviewer_reasons = reviewer_object.get('reasons', []) | |
|
510 | ||
|
511 | user = get_user_or_error(reviewer_username) | |
|
512 | reviewers_reasons.append((user.user_id, reviewer_reasons)) | |
|
499 | 513 | |
|
500 | 514 | pull_request_model = PullRequestModel() |
|
501 | 515 | pull_request = pull_request_model.create( |
@@ -506,7 +520,7 b' def create_pull_request(' | |||
|
506 | 520 | target_ref=full_target_ref, |
|
507 | 521 | revisions=reversed( |
|
508 | 522 | [commit.raw_id for commit in reversed(commit_ranges)]), |
|
509 |
reviewers=reviewer |
|
|
523 | reviewers=reviewers_reasons, | |
|
510 | 524 | title=title, |
|
511 | 525 | description=Optional.extract(description) |
|
512 | 526 | ) |
@@ -585,12 +599,23 b' def update_pull_request(' | |||
|
585 | 599 | 'pull request `%s` update failed, pull request is closed' % ( |
|
586 | 600 | pullrequestid,)) |
|
587 | 601 | |
|
588 |
reviewer_ |
|
|
589 |
if not isinstance(reviewer_ |
|
|
602 | reviewer_objects = Optional.extract(reviewers) or [] | |
|
603 | if not isinstance(reviewer_objects, list): | |
|
590 | 604 | raise JSONRPCError('reviewers should be specified as a list') |
|
591 | 605 | |
|
592 | reviewer_users = [get_user_or_error(n) for n in reviewer_names] | |
|
593 | reviewer_ids = [u.user_id for u in reviewer_users] | |
|
606 | reviewers_reasons = [] | |
|
607 | reviewer_ids = set() | |
|
608 | for reviewer_object in reviewer_objects: | |
|
609 | reviewer_reasons = [] | |
|
610 | if isinstance(reviewer_object, (int, basestring)): | |
|
611 | reviewer_username = reviewer_object | |
|
612 | else: | |
|
613 | reviewer_username = reviewer_object['username'] | |
|
614 | reviewer_reasons = reviewer_object.get('reasons', []) | |
|
615 | ||
|
616 | user = get_user_or_error(reviewer_username) | |
|
617 | reviewer_ids.add(user.user_id) | |
|
618 | reviewers_reasons.append((user.user_id, reviewer_reasons)) | |
|
594 | 619 | |
|
595 | 620 | title = Optional.extract(title) |
|
596 | 621 | description = Optional.extract(description) |
@@ -611,7 +636,7 b' def update_pull_request(' | |||
|
611 | 636 | reviewers_changes = {"added": [], "removed": []} |
|
612 | 637 | if reviewer_ids: |
|
613 | 638 | added_reviewers, removed_reviewers = \ |
|
614 |
PullRequestModel().update_reviewers(pull_request, reviewer |
|
|
639 | PullRequestModel().update_reviewers(pull_request, reviewers_reasons) | |
|
615 | 640 | |
|
616 | 641 | reviewers_changes['added'] = sorted( |
|
617 | 642 | [get_user_or_error(n).username for n in added_reviewers]) |
@@ -22,6 +22,7 b'' | |||
|
22 | 22 | pull requests controller for rhodecode for initializing pull requests |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | import peppercorn | |
|
25 | 26 | import formencode |
|
26 | 27 | import logging |
|
27 | 28 | |
@@ -399,8 +400,10 b' class PullrequestsController(BaseRepoCon' | |||
|
399 | 400 | if not repo: |
|
400 | 401 | raise HTTPNotFound |
|
401 | 402 | |
|
403 | controls = peppercorn.parse(request.POST.items()) | |
|
404 | ||
|
402 | 405 | try: |
|
403 |
_form = PullRequestForm(repo.repo_id)().to_python( |
|
|
406 | _form = PullRequestForm(repo.repo_id)().to_python(controls) | |
|
404 | 407 | except formencode.Invalid as errors: |
|
405 | 408 | if errors.error_dict.get('revisions'): |
|
406 | 409 | msg = 'Revisions: %s' % errors.error_dict['revisions'] |
@@ -419,7 +422,8 b' class PullrequestsController(BaseRepoCon' | |||
|
419 | 422 | target_repo = _form['target_repo'] |
|
420 | 423 | target_ref = _form['target_ref'] |
|
421 | 424 | commit_ids = _form['revisions'][::-1] |
|
422 |
reviewers = |
|
|
425 | reviewers = [ | |
|
426 | (r['user_id'], r['reasons']) for r in _form['review_members']] | |
|
423 | 427 | |
|
424 | 428 | # find the ancestor for this pr |
|
425 | 429 | source_db_repo = Repository.get_by_repo_name(_form['source_repo']) |
@@ -478,8 +482,11 b' class PullrequestsController(BaseRepoCon' | |||
|
478 | 482 | allowed_to_update = PullRequestModel().check_user_update( |
|
479 | 483 | pull_request, c.rhodecode_user) |
|
480 | 484 | if allowed_to_update: |
|
481 | if 'reviewers_ids' in request.POST: | |
|
482 | self._update_reviewers(pull_request_id) | |
|
485 | controls = peppercorn.parse(request.POST.items()) | |
|
486 | ||
|
487 | if 'review_members' in controls: | |
|
488 | self._update_reviewers( | |
|
489 | pull_request_id, controls['review_members']) | |
|
483 | 490 | elif str2bool(request.POST.get('update_commits', 'false')): |
|
484 | 491 | self._update_commits(pull_request) |
|
485 | 492 | elif str2bool(request.POST.get('close_pull_request', 'false')): |
@@ -631,11 +638,10 b' class PullrequestsController(BaseRepoCon' | |||
|
631 | 638 | merge_resp.failure_reason) |
|
632 | 639 | h.flash(msg, category='error') |
|
633 | 640 | |
|
634 | def _update_reviewers(self, pull_request_id): | |
|
635 |
reviewers |
|
|
636 | lambda v: v not in [None, ''], | |
|
637 | request.POST.get('reviewers_ids', '').split(','))) | |
|
638 | PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) | |
|
641 | def _update_reviewers(self, pull_request_id, review_members): | |
|
642 | reviewers = [ | |
|
643 | (int(r['user_id']), r['reasons']) for r in review_members] | |
|
644 | PullRequestModel().update_reviewers(pull_request_id, reviewers) | |
|
639 | 645 | Session().commit() |
|
640 | 646 | |
|
641 | 647 | def _reject_close(self, pull_request): |
@@ -59,7 +59,7 b' from rhodecode.lib.utils2 import (' | |||
|
59 | 59 | str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe, |
|
60 | 60 | time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict, |
|
61 | 61 | glob2re) |
|
62 | from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict | |
|
62 | from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, JSONDict | |
|
63 | 63 | from rhodecode.lib.ext_json import json |
|
64 | 64 | from rhodecode.lib.caching_query import FromCache |
|
65 | 65 | from rhodecode.lib.encrypt import AESCipher |
@@ -3149,9 +3149,10 b' class PullRequest(Base, _PullRequestBase' | |||
|
3149 | 3149 | { |
|
3150 | 3150 | 'user': reviewer.get_api_data(include_secrets=False, |
|
3151 | 3151 | details='basic'), |
|
3152 | 'reasons': reasons, | |
|
3152 | 3153 | 'review_status': st[0][1].status if st else 'not_reviewed', |
|
3153 | 3154 | } |
|
3154 | for reviewer, st in pull_request.reviewers_statuses() | |
|
3155 | for reviewer, reasons, st in pull_request.reviewers_statuses() | |
|
3155 | 3156 | ] |
|
3156 | 3157 | } |
|
3157 | 3158 | |
@@ -3203,9 +3204,23 b' class PullRequestReviewers(Base, BaseMod' | |||
|
3203 | 3204 | 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, |
|
3204 | 3205 | ) |
|
3205 | 3206 | |
|
3206 | def __init__(self, user=None, pull_request=None): | |
|
3207 | def __init__(self, user=None, pull_request=None, reasons=None): | |
|
3207 | 3208 | self.user = user |
|
3208 | 3209 | self.pull_request = pull_request |
|
3210 | self.reasons = reasons or [] | |
|
3211 | ||
|
3212 | @hybrid_property | |
|
3213 | def reasons(self): | |
|
3214 | if not self._reasons: | |
|
3215 | return [] | |
|
3216 | return self._reasons | |
|
3217 | ||
|
3218 | @reasons.setter | |
|
3219 | def reasons(self, val): | |
|
3220 | val = val or [] | |
|
3221 | if any(not isinstance(x, basestring) for x in val): | |
|
3222 | raise Exception('invalid reasons type, must be list of strings') | |
|
3223 | self._reasons = val | |
|
3209 | 3224 | |
|
3210 | 3225 | pull_requests_reviewers_id = Column( |
|
3211 | 3226 | 'pull_requests_reviewers_id', Integer(), nullable=False, |
@@ -3215,8 +3230,9 b' class PullRequestReviewers(Base, BaseMod' | |||
|
3215 | 3230 | ForeignKey('pull_requests.pull_request_id'), nullable=False) |
|
3216 | 3231 | user_id = Column( |
|
3217 | 3232 | "user_id", Integer(), ForeignKey('users.user_id'), nullable=True) |
|
3218 |
reason = Column( |
|
|
3219 | UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
3233 | _reasons = Column( | |
|
3234 | 'reason', MutationList.as_mutable( | |
|
3235 | JsonType('list', dialect_map=dict(mysql=UnicodeText(16384))))) | |
|
3220 | 3236 | |
|
3221 | 3237 | user = relationship('User') |
|
3222 | 3238 | pull_request = relationship('PullRequest') |
@@ -26,7 +26,7 b' def upgrade(migrate_engine):' | |||
|
26 | 26 | _reset_base(migrate_engine) |
|
27 | 27 | from rhodecode.lib.dbmigrate.schema import db_4_5_0_0 |
|
28 | 28 | |
|
29 | db_4_5_0_0.PullRequestReviewers.reason.create( | |
|
29 | db_4_5_0_0.PullRequestReviewers.reasons.create( | |
|
30 | 30 | table=db_4_5_0_0.PullRequestReviewers.__table__) |
|
31 | 31 | |
|
32 | 32 | def downgrade(migrate_engine): |
@@ -80,7 +80,7 b' class ChangesetStatusModel(BaseModel):' | |||
|
80 | 80 | """ |
|
81 | 81 | votes = defaultdict(int) |
|
82 | 82 | reviewers_number = len(statuses_by_reviewers) |
|
83 | for user, statuses in statuses_by_reviewers: | |
|
83 | for user, reasons, statuses in statuses_by_reviewers: | |
|
84 | 84 | if statuses: |
|
85 | 85 | ver, latest = statuses[0] |
|
86 | 86 | votes[latest.status] += 1 |
@@ -254,7 +254,7 b' class ChangesetStatusModel(BaseModel):' | |||
|
254 | 254 | for x, y in (itertools.groupby(sorted(st, key=version), |
|
255 | 255 | version))] |
|
256 | 256 | |
|
257 |
pull_request_reviewers.append( |
|
|
257 | pull_request_reviewers.append((o.user, o.reasons, st)) | |
|
258 | 258 | return pull_request_reviewers |
|
259 | 259 | |
|
260 | 260 | def calculated_review_status(self, pull_request, reviewers_statuses=None): |
@@ -59,7 +59,7 b' from rhodecode.lib.utils2 import (' | |||
|
59 | 59 | str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe, |
|
60 | 60 | time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict, |
|
61 | 61 | glob2re) |
|
62 | from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict | |
|
62 | from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, JSONDict | |
|
63 | 63 | from rhodecode.lib.ext_json import json |
|
64 | 64 | from rhodecode.lib.caching_query import FromCache |
|
65 | 65 | from rhodecode.lib.encrypt import AESCipher |
@@ -3149,9 +3149,10 b' class PullRequest(Base, _PullRequestBase' | |||
|
3149 | 3149 | { |
|
3150 | 3150 | 'user': reviewer.get_api_data(include_secrets=False, |
|
3151 | 3151 | details='basic'), |
|
3152 | 'reasons': reasons, | |
|
3152 | 3153 | 'review_status': st[0][1].status if st else 'not_reviewed', |
|
3153 | 3154 | } |
|
3154 | for reviewer, st in pull_request.reviewers_statuses() | |
|
3155 | for reviewer, reasons, st in pull_request.reviewers_statuses() | |
|
3155 | 3156 | ] |
|
3156 | 3157 | } |
|
3157 | 3158 | |
@@ -3203,9 +3204,23 b' class PullRequestReviewers(Base, BaseMod' | |||
|
3203 | 3204 | 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, |
|
3204 | 3205 | ) |
|
3205 | 3206 | |
|
3206 | def __init__(self, user=None, pull_request=None): | |
|
3207 | def __init__(self, user=None, pull_request=None, reasons=None): | |
|
3207 | 3208 | self.user = user |
|
3208 | 3209 | self.pull_request = pull_request |
|
3210 | self.reasons = reasons or [] | |
|
3211 | ||
|
3212 | @hybrid_property | |
|
3213 | def reasons(self): | |
|
3214 | if not self._reasons: | |
|
3215 | return [] | |
|
3216 | return self._reasons | |
|
3217 | ||
|
3218 | @reasons.setter | |
|
3219 | def reasons(self, val): | |
|
3220 | val = val or [] | |
|
3221 | if any(not isinstance(x, basestring) for x in val): | |
|
3222 | raise Exception('invalid reasons type, must be list of strings') | |
|
3223 | self._reasons = val | |
|
3209 | 3224 | |
|
3210 | 3225 | pull_requests_reviewers_id = Column( |
|
3211 | 3226 | 'pull_requests_reviewers_id', Integer(), nullable=False, |
@@ -3215,8 +3230,9 b' class PullRequestReviewers(Base, BaseMod' | |||
|
3215 | 3230 | ForeignKey('pull_requests.pull_request_id'), nullable=False) |
|
3216 | 3231 | user_id = Column( |
|
3217 | 3232 | "user_id", Integer(), ForeignKey('users.user_id'), nullable=True) |
|
3218 |
reason = Column( |
|
|
3219 | UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
3233 | _reasons = Column( | |
|
3234 | 'reason', MutationList.as_mutable( | |
|
3235 | JsonType('list', dialect_map=dict(mysql=UnicodeText(16384))))) | |
|
3220 | 3236 | |
|
3221 | 3237 | user = relationship('User') |
|
3222 | 3238 | pull_request = relationship('PullRequest') |
@@ -519,7 +519,12 b' def UserExtraIpForm():' | |||
|
519 | 519 | return _UserExtraIpForm |
|
520 | 520 | |
|
521 | 521 | |
|
522 | ||
|
522 | 523 | def PullRequestForm(repo_id): |
|
524 | class ReviewerForm(formencode.Schema): | |
|
525 | user_id = v.Int(not_empty=True) | |
|
526 | reasons = All() | |
|
527 | ||
|
523 | 528 | class _PullRequestForm(formencode.Schema): |
|
524 | 529 | allow_extra_fields = True |
|
525 | 530 | filter_extra_fields = True |
@@ -531,8 +536,7 b' def PullRequestForm(repo_id):' | |||
|
531 | 536 | target_ref = v.UnicodeString(strip=True, required=True) |
|
532 | 537 | revisions = All(#v.NotReviewedRevisions(repo_id)(), |
|
533 | 538 | v.UniqueList()(not_empty=True)) |
|
534 | review_members = v.UniqueList(convert=int)(not_empty=True) | |
|
535 | ||
|
539 | review_members = formencode.ForEach(ReviewerForm()) | |
|
536 | 540 | pullrequest_title = v.UnicodeString(strip=True, required=True) |
|
537 | 541 | pullrequest_desc = v.UnicodeString(strip=True, required=False) |
|
538 | 542 |
@@ -333,10 +333,18 b' class PullRequestModel(BaseModel):' | |||
|
333 | 333 | Session().add(pull_request) |
|
334 | 334 | Session().flush() |
|
335 | 335 | |
|
336 | reviewer_ids = set() | |
|
336 | 337 | # members / reviewers |
|
337 |
for |
|
|
338 | for reviewer_object in reviewers: | |
|
339 | if isinstance(reviewer_object, tuple): | |
|
340 | user_id, reasons = reviewer_object | |
|
341 | else: | |
|
342 | user_id, reasons = reviewer_object, [] | |
|
343 | ||
|
338 | 344 | user = self._get_user(user_id) |
|
339 | reviewer = PullRequestReviewers(user, pull_request) | |
|
345 | reviewer_ids.add(user.user_id) | |
|
346 | ||
|
347 | reviewer = PullRequestReviewers(user, pull_request, reasons) | |
|
340 | 348 | Session().add(reviewer) |
|
341 | 349 | |
|
342 | 350 | # Set approval status to "Under Review" for all commits which are |
@@ -348,7 +356,7 b' class PullRequestModel(BaseModel):' | |||
|
348 | 356 | pull_request=pull_request |
|
349 | 357 | ) |
|
350 | 358 | |
|
351 | self.notify_reviewers(pull_request, reviewers) | |
|
359 | self.notify_reviewers(pull_request, reviewer_ids) | |
|
352 | 360 | self._trigger_pull_request_hook( |
|
353 | 361 | pull_request, created_by_user, 'create') |
|
354 | 362 | |
@@ -570,6 +578,7 b' class PullRequestModel(BaseModel):' | |||
|
570 | 578 | Session().commit() |
|
571 | 579 | self._trigger_pull_request_hook(pull_request, pull_request.author, |
|
572 | 580 | 'update') |
|
581 | ||
|
573 | 582 | return (pull_request_version, changes) |
|
574 | 583 | |
|
575 | 584 | def _create_version_from_snapshot(self, pull_request): |
@@ -711,8 +720,21 b' class PullRequestModel(BaseModel):' | |||
|
711 | 720 | pull_request.updated_on = datetime.datetime.now() |
|
712 | 721 | Session().add(pull_request) |
|
713 | 722 | |
|
714 |
def update_reviewers(self, pull_request, reviewer |
|
|
715 | reviewers_ids = set(reviewers_ids) | |
|
723 | def update_reviewers(self, pull_request, reviewer_data): | |
|
724 | """ | |
|
725 | Update the reviewers in the pull request | |
|
726 | ||
|
727 | :param pull_request: the pr to update | |
|
728 | :param reviewer_data: list of tuples [(user, ['reason1', 'reason2'])] | |
|
729 | """ | |
|
730 | ||
|
731 | reviewers_reasons = {} | |
|
732 | for user_id, reasons in reviewer_data: | |
|
733 | if isinstance(user_id, (int, basestring)): | |
|
734 | user_id = self._get_user(user_id).user_id | |
|
735 | reviewers_reasons[user_id] = reasons | |
|
736 | ||
|
737 | reviewers_ids = set(reviewers_reasons.keys()) | |
|
716 | 738 | pull_request = self.__get_pull_request(pull_request) |
|
717 | 739 | current_reviewers = PullRequestReviewers.query()\ |
|
718 | 740 | .filter(PullRequestReviewers.pull_request == |
@@ -728,7 +750,8 b' class PullRequestModel(BaseModel):' | |||
|
728 | 750 | for uid in ids_to_add: |
|
729 | 751 | changed = True |
|
730 | 752 | _usr = self._get_user(uid) |
|
731 | reviewer = PullRequestReviewers(_usr, pull_request) | |
|
753 | reasons = reviewers_reasons[uid] | |
|
754 | reviewer = PullRequestReviewers(_usr, pull_request, reasons) | |
|
732 | 755 | Session().add(reviewer) |
|
733 | 756 | |
|
734 | 757 | self.notify_reviewers(pull_request, ids_to_add) |
@@ -1282,6 +1282,9 b' table.integrations {' | |||
|
1282 | 1282 | width: 100%; |
|
1283 | 1283 | overflow: auto; |
|
1284 | 1284 | } |
|
1285 | .reviewer_reason { | |
|
1286 | padding-left: 20px; | |
|
1287 | } | |
|
1285 | 1288 | .reviewer_status { |
|
1286 | 1289 | display: inline-block; |
|
1287 | 1290 | vertical-align: top; |
@@ -32,7 +32,7 b' var removeReviewMember = function(review' | |||
|
32 | 32 | var obj = $('#reviewer_{0}_name'.format(reviewer_id)); |
|
33 | 33 | obj.addClass('to-delete'); |
|
34 | 34 | // now delete the input |
|
35 |
$('#reviewer_{0} |
|
|
35 | $('#reviewer_{0} input'.format(reviewer_id)).remove(); | |
|
36 | 36 | } |
|
37 | 37 | } |
|
38 | 38 | else{ |
@@ -43,29 +43,36 b' var removeReviewMember = function(review' | |||
|
43 | 43 | var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) { |
|
44 | 44 | var members = $('#review_members').get(0); |
|
45 | 45 | var reasons_html = ''; |
|
46 | var reasons_inputs = ''; | |
|
47 | var reasons = reasons || []; | |
|
46 | 48 | if (reasons) { |
|
47 | 49 | for (var i = 0; i < reasons.length; i++) { |
|
48 | reasons_html += '<div class="reviewer_reason">- {0}</div>'.format( | |
|
49 | reasons[i] | |
|
50 | ); | |
|
50 | reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]); | |
|
51 | reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">'; | |
|
51 | 52 | } |
|
52 | 53 | } |
|
53 | 54 | var tmpl = '<li id="reviewer_{2}">'+ |
|
55 | '<input type="hidden" name="__start__" value="reviewer:mapping">'+ | |
|
54 | 56 | '<div class="reviewer_status">'+ |
|
55 | 57 | '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+ |
|
56 | 58 | '</div>'+ |
|
57 | 59 | '<img alt="gravatar" class="gravatar" src="{0}"/>'+ |
|
58 | 60 | '<span class="reviewer_name user">{1}</span>'+ |
|
59 | 61 | reasons_html + |
|
60 |
'<input type="hidden" value="{2}" |
|
|
62 | '<input type="hidden" name="user_id" value="{2}">'+ | |
|
63 | '<input type="hidden" name="__start__" value="reasons:sequence">'+ | |
|
64 | '{3}'+ | |
|
65 | '<input type="hidden" name="__end__" value="reasons:sequence">'+ | |
|
61 | 66 | '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' + |
|
62 | 67 | '<i class="icon-remove-sign"></i>'+ |
|
63 | 68 | '</div>'+ |
|
64 | 69 | '</div>'+ |
|
70 | '<input type="hidden" name="__end__" value="reviewer:mapping">'+ | |
|
65 | 71 | '</li>' ; |
|
72 | ||
|
66 | 73 | var displayname = "{0} ({1} {2})".format( |
|
67 | 74 | nname, escapeHtml(fname), escapeHtml(lname)); |
|
68 | var element = tmpl.format(gravatar_link,displayname,id); | |
|
75 | var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs); | |
|
69 | 76 | // check if we don't have this ID already in |
|
70 | 77 | var ids = []; |
|
71 | 78 | var _els = $('#review_members li').toArray(); |
@@ -83,7 +90,11 b' var _updatePullRequest = function(repo_n' | |||
|
83 | 90 | var url = pyroutes.url( |
|
84 | 91 | 'pullrequest_update', |
|
85 | 92 | {"repo_name": repo_name, "pull_request_id": pull_request_id}); |
|
93 | if (typeof postData === 'string' ) { | |
|
94 | postData += '&csrf_token=' + CSRF_TOKEN; | |
|
95 | } else { | |
|
86 | 96 | postData.csrf_token = CSRF_TOKEN; |
|
97 | } | |
|
87 | 98 | var success = function(o) { |
|
88 | 99 | window.location.reload(); |
|
89 | 100 | }; |
@@ -92,17 +103,9 b' var _updatePullRequest = function(repo_n' | |||
|
92 | 103 | |
|
93 | 104 | var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){ |
|
94 | 105 | if (reviewers_ids === undefined){ |
|
95 | var reviewers_ids = []; | |
|
96 | var ids = $('#review_members input').toArray(); | |
|
97 | for(var i=0; i<ids.length;i++){ | |
|
98 | var id = ids[i].value | |
|
99 | reviewers_ids.push(id); | |
|
106 | var postData = '_method=put&' + $('#reviewers input').serialize(); | |
|
107 | _updatePullRequest(repo_name, pull_request_id, postData); | |
|
100 | 108 |
|
|
101 | } | |
|
102 | var postData = { | |
|
103 | '_method':'put', | |
|
104 | 'reviewers_ids': reviewers_ids}; | |
|
105 | _updatePullRequest(repo_name, pull_request_id, postData); | |
|
106 | 109 | }; |
|
107 | 110 | |
|
108 | 111 | /** |
@@ -20,6 +20,9 b'' | |||
|
20 | 20 | * turns objects into GET query string |
|
21 | 21 | */ |
|
22 | 22 | var toQueryString = function(o) { |
|
23 | if(typeof o === 'string') { | |
|
24 | return o; | |
|
25 | } | |
|
23 | 26 | if(typeof o !== 'object') { |
|
24 | 27 | return false; |
|
25 | 28 | } |
@@ -111,7 +111,9 b'' | |||
|
111 | 111 | </div> |
|
112 | 112 | <div id="reviewers" class="block-right pr-details-content reviewers"> |
|
113 | 113 | ## members goes here, filled via JS based on initial selection ! |
|
114 | <input type="hidden" name="__start__" value="review_members:sequence"> | |
|
114 | 115 | <ul id="review_members" class="group_members"></ul> |
|
116 | <input type="hidden" name="__end__" value="review_members:sequence"> | |
|
115 | 117 | <div id="add_reviewer_input" class='ac'> |
|
116 | 118 | <div class="reviewer_ac"> |
|
117 | 119 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))} |
@@ -188,17 +188,27 b'' | |||
|
188 | 188 | </div> |
|
189 | 189 | <div id="reviewers" class="block-right pr-details-content reviewers"> |
|
190 | 190 | ## members goes here ! |
|
191 | <input type="hidden" name="__start__" value="review_members:sequence"> | |
|
191 | 192 | <ul id="review_members" class="group_members"> |
|
192 | %for member,status in c.pull_request_reviewers: | |
|
193 | %for member,reasons,status in c.pull_request_reviewers: | |
|
193 | 194 | <li id="reviewer_${member.user_id}"> |
|
194 | 195 | <div class="reviewers_member"> |
|
195 | 196 | <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}"> |
|
196 | 197 | <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div> |
|
197 | 198 | </div> |
|
198 | 199 | <div id="reviewer_${member.user_id}_name" class="reviewer_name"> |
|
199 | ${self.gravatar_with_user(member.email, 16)} <div class="reviewer">(${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div> | |
|
200 | ${self.gravatar_with_user(member.email, 16)} | |
|
200 | 201 | </div> |
|
201 | <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="review_members" /> | |
|
202 | <input type="hidden" name="__start__" value="reviewer:mapping"> | |
|
203 | <input type="hidden" name="__start__" value="reasons:sequence"> | |
|
204 | %for reason in reasons: | |
|
205 | <div class="reviewer_reason">- ${reason}</div> | |
|
206 | <input type="hidden" name="reason" value="${reason}"> | |
|
207 | ||
|
208 | %endfor | |
|
209 | <input type="hidden" name="__end__" value="reasons:sequence"> | |
|
210 | <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" /> | |
|
211 | <input type="hidden" name="__end__" value="reviewer:mapping"> | |
|
202 | 212 | %if c.allowed_to_update: |
|
203 | 213 | <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;"> |
|
204 | 214 | <i class="icon-remove-sign" ></i> |
@@ -208,6 +218,7 b'' | |||
|
208 | 218 | </li> |
|
209 | 219 | %endfor |
|
210 | 220 | </ul> |
|
221 | <input type="hidden" name="__end__" value="review_members:sequence"> | |
|
211 | 222 | %if not c.pull_request.is_closed(): |
|
212 | 223 | <div id="add_reviewer_input" class='ac' style="display: none;"> |
|
213 | 224 | %if c.allowed_to_update: |
@@ -256,8 +256,8 b' class TestPullrequestsController:' | |||
|
256 | 256 | def test_comment_force_close_pull_request(self, pr_util, csrf_token): |
|
257 | 257 | pull_request = pr_util.create_pull_request() |
|
258 | 258 | pull_request_id = pull_request.pull_request_id |
|
259 |
reviewers_ |
|
|
260 |
PullRequestModel().update_reviewers(pull_request_id, reviewers_ |
|
|
259 | reviewers_data = [(1, ['reason']), (2, ['reason2'])] | |
|
260 | PullRequestModel().update_reviewers(pull_request_id, reviewers_data) | |
|
261 | 261 | author = pull_request.user_id |
|
262 | 262 | repo = pull_request.target_repo.repo_id |
|
263 | 263 | self.app.post( |
@@ -297,19 +297,27 b' class TestPullrequestsController:' | |||
|
297 | 297 | url( |
|
298 | 298 | controller='pullrequests', |
|
299 | 299 | action='create', |
|
300 |
repo_name=source.repo_name |
|
|
301 |
|
|
|
302 | 'source_repo': source.repo_name, | |
|
303 | 'source_ref': 'branch:default:' + commit_ids['change'], | |
|
304 | 'target_repo': target.repo_name, | |
|
305 | 'target_ref': 'branch:default:' + commit_ids['ancestor'], | |
|
306 | 'pullrequest_desc': 'Description', | |
|
307 |
'pullrequest_ |
|
|
308 |
' |
|
|
309 | 'revisions': commit_ids['change'], | |
|
310 | 'user': '', | |
|
311 | 'csrf_token': csrf_token, | |
|
312 | }, | |
|
300 | repo_name=source.repo_name | |
|
301 | ), | |
|
302 | [ | |
|
303 | ('source_repo', source.repo_name), | |
|
304 | ('source_ref', 'branch:default:' + commit_ids['change']), | |
|
305 | ('target_repo', target.repo_name), | |
|
306 | ('target_ref', 'branch:default:' + commit_ids['ancestor']), | |
|
307 | ('pullrequest_desc', 'Description'), | |
|
308 | ('pullrequest_title', 'Title'), | |
|
309 | ('__start__', 'review_members:sequence'), | |
|
310 | ('__start__', 'reviewer:mapping'), | |
|
311 | ('user_id', '1'), | |
|
312 | ('__start__', 'reasons:sequence'), | |
|
313 | ('reason', 'Some reason'), | |
|
314 | ('__end__', 'reasons:sequence'), | |
|
315 | ('__end__', 'reviewer:mapping'), | |
|
316 | ('__end__', 'review_members:sequence'), | |
|
317 | ('revisions', commit_ids['change']), | |
|
318 | ('user', ''), | |
|
319 | ('csrf_token', csrf_token), | |
|
320 | ], | |
|
313 | 321 | status=302) |
|
314 | 322 | |
|
315 | 323 | location = response.headers['Location'] |
@@ -344,19 +352,27 b' class TestPullrequestsController:' | |||
|
344 | 352 | url( |
|
345 | 353 | controller='pullrequests', |
|
346 | 354 | action='create', |
|
347 |
repo_name=source.repo_name |
|
|
348 |
|
|
|
349 | 'source_repo': source.repo_name, | |
|
350 | 'source_ref': 'branch:default:' + commit_ids['change'], | |
|
351 | 'target_repo': target.repo_name, | |
|
352 | 'target_ref': 'branch:default:' + commit_ids['ancestor-child'], | |
|
353 | 'pullrequest_desc': 'Description', | |
|
354 |
'pullrequest_ |
|
|
355 |
' |
|
|
356 | 'revisions': commit_ids['change'], | |
|
357 | 'user': '', | |
|
358 | 'csrf_token': csrf_token, | |
|
359 | }, | |
|
355 | repo_name=source.repo_name | |
|
356 | ), | |
|
357 | [ | |
|
358 | ('source_repo', source.repo_name), | |
|
359 | ('source_ref', 'branch:default:' + commit_ids['change']), | |
|
360 | ('target_repo', target.repo_name), | |
|
361 | ('target_ref', 'branch:default:' + commit_ids['ancestor-child']), | |
|
362 | ('pullrequest_desc', 'Description'), | |
|
363 | ('pullrequest_title', 'Title'), | |
|
364 | ('__start__', 'review_members:sequence'), | |
|
365 | ('__start__', 'reviewer:mapping'), | |
|
366 | ('user_id', '2'), | |
|
367 | ('__start__', 'reasons:sequence'), | |
|
368 | ('reason', 'Some reason'), | |
|
369 | ('__end__', 'reasons:sequence'), | |
|
370 | ('__end__', 'reviewer:mapping'), | |
|
371 | ('__end__', 'review_members:sequence'), | |
|
372 | ('revisions', commit_ids['change']), | |
|
373 | ('user', ''), | |
|
374 | ('csrf_token', csrf_token), | |
|
375 | ], | |
|
360 | 376 | status=302) |
|
361 | 377 | |
|
362 | 378 | location = response.headers['Location'] |
@@ -373,7 +389,8 b' class TestPullrequestsController:' | |||
|
373 | 389 | assert len(notifications.all()) == 1 |
|
374 | 390 | |
|
375 | 391 | # Change reviewers and check that a notification was made |
|
376 |
PullRequestModel().update_reviewers( |
|
|
392 | PullRequestModel().update_reviewers( | |
|
393 | pull_request.pull_request_id, [(1, [])]) | |
|
377 | 394 | assert len(notifications.all()) == 2 |
|
378 | 395 | |
|
379 | 396 | def test_create_pull_request_stores_ancestor_commit_id(self, backend, |
@@ -397,19 +414,27 b' class TestPullrequestsController:' | |||
|
397 | 414 | url( |
|
398 | 415 | controller='pullrequests', |
|
399 | 416 | action='create', |
|
400 |
repo_name=source.repo_name |
|
|
401 |
|
|
|
402 | 'source_repo': source.repo_name, | |
|
403 | 'source_ref': 'branch:default:' + commit_ids['change'], | |
|
404 | 'target_repo': target.repo_name, | |
|
405 | 'target_ref': 'branch:default:' + commit_ids['ancestor-child'], | |
|
406 | 'pullrequest_desc': 'Description', | |
|
407 |
'pullrequest_ |
|
|
408 |
' |
|
|
409 | 'revisions': commit_ids['change'], | |
|
410 | 'user': '', | |
|
411 | 'csrf_token': csrf_token, | |
|
412 | }, | |
|
417 | repo_name=source.repo_name | |
|
418 | ), | |
|
419 | [ | |
|
420 | ('source_repo', source.repo_name), | |
|
421 | ('source_ref', 'branch:default:' + commit_ids['change']), | |
|
422 | ('target_repo', target.repo_name), | |
|
423 | ('target_ref', 'branch:default:' + commit_ids['ancestor-child']), | |
|
424 | ('pullrequest_desc', 'Description'), | |
|
425 | ('pullrequest_title', 'Title'), | |
|
426 | ('__start__', 'review_members:sequence'), | |
|
427 | ('__start__', 'reviewer:mapping'), | |
|
428 | ('user_id', '1'), | |
|
429 | ('__start__', 'reasons:sequence'), | |
|
430 | ('reason', 'Some reason'), | |
|
431 | ('__end__', 'reasons:sequence'), | |
|
432 | ('__end__', 'reviewer:mapping'), | |
|
433 | ('__end__', 'review_members:sequence'), | |
|
434 | ('revisions', commit_ids['change']), | |
|
435 | ('user', ''), | |
|
436 | ('csrf_token', csrf_token), | |
|
437 | ], | |
|
413 | 438 | status=302) |
|
414 | 439 | |
|
415 | 440 | location = response.headers['Location'] |
@@ -80,7 +80,7 b' def test_pull_request_stays_if_update_wi' | |||
|
80 | 80 | voted_status, *pull_request.reviewers) |
|
81 | 81 | |
|
82 | 82 | # Update, without change |
|
83 | PullRequestModel().update_commits(pull_request) | |
|
83 | version, changes = PullRequestModel().update_commits(pull_request) | |
|
84 | 84 | |
|
85 | 85 | # Expect that review status is the voted_status |
|
86 | 86 | expected_review_status = voted_status |
@@ -99,7 +99,7 b' def test_pull_request_under_review_if_up' | |||
|
99 | 99 | |
|
100 | 100 | # Update, with change |
|
101 | 101 | pr_util.update_source_repository() |
|
102 | PullRequestModel().update_commits(pull_request) | |
|
102 | version, changes = PullRequestModel().update_commits(pull_request) | |
|
103 | 103 | |
|
104 | 104 | # Expect that review status is the voted_status |
|
105 | 105 | expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW |
@@ -115,7 +115,7 b' class TestPullRequestModel:' | |||
|
115 | 115 | |
|
116 | 116 | def test_get_awaiting_my_review(self, pull_request): |
|
117 | 117 | PullRequestModel().update_reviewers( |
|
118 | pull_request, [pull_request.author]) | |
|
118 | pull_request, [(pull_request.author, ['author'])]) | |
|
119 | 119 | prs = PullRequestModel().get_awaiting_my_review( |
|
120 | 120 | pull_request.target_repo, user_id=pull_request.author.user_id) |
|
121 | 121 | assert isinstance(prs, list) |
@@ -123,7 +123,7 b' class TestPullRequestModel:' | |||
|
123 | 123 | |
|
124 | 124 | def test_count_awaiting_my_review(self, pull_request): |
|
125 | 125 | PullRequestModel().update_reviewers( |
|
126 | pull_request, [pull_request.author]) | |
|
126 | pull_request, [(pull_request.author, ['author'])]) | |
|
127 | 127 | pr_count = PullRequestModel().count_awaiting_my_review( |
|
128 | 128 | pull_request.target_repo, user_id=pull_request.author.user_id) |
|
129 | 129 | assert pr_count == 1 |
General Comments 0
You need to be logged in to leave comments.
Login now