Show More
@@ -0,0 +1,89 b'' | |||
|
1 | |RCE| 4.23.0 |RNS| | |
|
2 | ------------------ | |
|
3 | ||
|
4 | Release Date | |
|
5 | ^^^^^^^^^^^^ | |
|
6 | ||
|
7 | - 2020-11-20 | |
|
8 | ||
|
9 | ||
|
10 | New Features | |
|
11 | ^^^^^^^^^^^^ | |
|
12 | ||
|
13 | - Comments: introduced new draft comments. | |
|
14 | ||
|
15 | * drafts are private to author | |
|
16 | * not triggering any notifications | |
|
17 | * sidebar doesn't display draft comments | |
|
18 | * They are just placeholders for longer review. | |
|
19 | ||
|
20 | - Comments: when channelstream is enabled, comments are pushed live, so there's no | |
|
21 | need to refresh page to see other participant comments. | |
|
22 | New comments are marker in the sidebar. | |
|
23 | ||
|
24 | - Comments: multiple changes on comments navigation/display logic. | |
|
25 | ||
|
26 | * toggle icon is smarter, open/hide windows according to actions. E.g commenting opens threads | |
|
27 | * toggle are mor explicit | |
|
28 | * possible to hide/show only single threads using the toggle icon. | |
|
29 | * new UI for showing thread comments | |
|
30 | ||
|
31 | - Reviewers: new logic for author/commit-author rules. | |
|
32 | It's not possible to define if author or commit author should be excluded, or always included in a review. | |
|
33 | - Reviewers: no reviewers would now allow a PR to be merged, unless review rules require some. | |
|
34 | Use case is that pr can be created without review needed, maybe just for sharing, or CI checks | |
|
35 | - Pull requests: save permanently the state if sorting columns for pull-request grids. | |
|
36 | - Commit ranges: enable combined diff compare directly from range selector. | |
|
37 | ||
|
38 | ||
|
39 | General | |
|
40 | ^^^^^^^ | |
|
41 | ||
|
42 | - Authentication: enable custom names for auth plugins. It's possible to name the authentication | |
|
43 | buttons now for SAML plugins. | |
|
44 | - Login: optimized UI for login/register/password reset windows. | |
|
45 | - Repo mapper: make it more resilient to errors, it's better it executes and skip certain | |
|
46 | repositories, rather then crash whole mapper. | |
|
47 | - Markdown: improved styling, and fixed nl2br extensions to only do br on new elements not inline. | |
|
48 | - Pull requests: show pr version in the my-account and repo pr listing grids. | |
|
49 | - Archives: allowing to obtain archives without the commit short id in the name for | |
|
50 | better automation of obtained artifacts. | |
|
51 | New url flag called `?=with_hash=1` controls this | |
|
52 | - Error document: update info about stored exception retrieval. | |
|
53 | - Range diff: enable hovercards for commits in range-diff. | |
|
54 | ||
|
55 | ||
|
56 | Security | |
|
57 | ^^^^^^^^ | |
|
58 | ||
|
59 | ||
|
60 | ||
|
61 | Performance | |
|
62 | ^^^^^^^^^^^ | |
|
63 | ||
|
64 | - Improved logic of repo archive, now it's much faster to run archiver as VCSServer | |
|
65 | communication was removed, and job is delegated to VCSServer itself. | |
|
66 | - Improved VCSServer startup times. | |
|
67 | - Notifications: skip double rendering just to generate email title/desc. | |
|
68 | We'll re-use those now for better performance of creating notifications. | |
|
69 | - App: improve logging, and remove DB calls on app startup. | |
|
70 | ||
|
71 | ||
|
72 | Fixes | |
|
73 | ^^^^^ | |
|
74 | ||
|
75 | - Login/register: fixed header width problem on mobile devices | |
|
76 | - Exception tracker: don't fail on empty request in context of celery app for example. | |
|
77 | - Exceptions: improved reporting of unhandled vcsserver exceptions. | |
|
78 | - Sidebar: fixed refresh of TODOs url. | |
|
79 | - Remap-rescan: fixes #5636 initial rescan problem. | |
|
80 | - API: fixed SVN raw diff export. The API method was inconsistent, and used different logic. | |
|
81 | Now it shares the same code as raw-diff from web-ui. | |
|
82 | ||
|
83 | ||
|
84 | Upgrade notes | |
|
85 | ^^^^^^^^^^^^^ | |
|
86 | ||
|
87 | - Scheduled feature release. | |
|
88 | Please note that now the reviewers logic changed a bit, it's possible to create a pull request | |
|
89 | Without any reviewers initially, and such pull request doesn't need to have an approval for merging. |
@@ -0,0 +1,53 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | import logging | |
|
4 | from sqlalchemy import * | |
|
5 | ||
|
6 | from alembic.migration import MigrationContext | |
|
7 | from alembic.operations import Operations | |
|
8 | ||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |
|
10 | from rhodecode.model import meta, init_model_encryption | |
|
11 | ||
|
12 | ||
|
13 | log = logging.getLogger(__name__) | |
|
14 | ||
|
15 | ||
|
16 | def upgrade(migrate_engine): | |
|
17 | """ | |
|
18 | Upgrade operations go here. | |
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |
|
20 | """ | |
|
21 | _reset_base(migrate_engine) | |
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |
|
23 | ||
|
24 | init_model_encryption(db) | |
|
25 | ||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |
|
27 | op = Operations(context) | |
|
28 | ||
|
29 | table = db.ChangesetComment.__table__ | |
|
30 | with op.batch_alter_table(table.name) as batch_op: | |
|
31 | new_column = Column('draft', Boolean(), nullable=True) | |
|
32 | batch_op.add_column(new_column) | |
|
33 | ||
|
34 | _set_default_as_non_draft(op, meta.Session) | |
|
35 | ||
|
36 | ||
|
37 | def downgrade(migrate_engine): | |
|
38 | meta = MetaData() | |
|
39 | meta.bind = migrate_engine | |
|
40 | ||
|
41 | ||
|
42 | def fixups(models, _SESSION): | |
|
43 | pass | |
|
44 | ||
|
45 | ||
|
46 | def _set_default_as_non_draft(op, session): | |
|
47 | params = {'draft': False} | |
|
48 | query = text( | |
|
49 | 'UPDATE changeset_comments SET draft = :draft' | |
|
50 | ).bindparams(**params) | |
|
51 | op.execute(query) | |
|
52 | session().commit() | |
|
53 |
@@ -0,0 +1,78 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | import logging | |
|
4 | from sqlalchemy import * | |
|
5 | ||
|
6 | from alembic.migration import MigrationContext | |
|
7 | from alembic.operations import Operations | |
|
8 | ||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |
|
10 | from rhodecode.model import meta, init_model_encryption | |
|
11 | ||
|
12 | ||
|
13 | log = logging.getLogger(__name__) | |
|
14 | ||
|
15 | ||
|
16 | def upgrade(migrate_engine): | |
|
17 | """ | |
|
18 | Upgrade operations go here. | |
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |
|
20 | """ | |
|
21 | _reset_base(migrate_engine) | |
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |
|
23 | ||
|
24 | init_model_encryption(db) | |
|
25 | ||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |
|
27 | op = Operations(context) | |
|
28 | ||
|
29 | table = db.RepoReviewRule.__table__ | |
|
30 | with op.batch_alter_table(table.name) as batch_op: | |
|
31 | ||
|
32 | new_column = Column('pr_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
33 | batch_op.add_column(new_column) | |
|
34 | ||
|
35 | new_column = Column('commit_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
36 | batch_op.add_column(new_column) | |
|
37 | ||
|
38 | _migrate_review_flags_to_new_cols(op, meta.Session) | |
|
39 | ||
|
40 | ||
|
41 | def downgrade(migrate_engine): | |
|
42 | meta = MetaData() | |
|
43 | meta.bind = migrate_engine | |
|
44 | ||
|
45 | ||
|
46 | def fixups(models, _SESSION): | |
|
47 | pass | |
|
48 | ||
|
49 | ||
|
50 | def _migrate_review_flags_to_new_cols(op, session): | |
|
51 | ||
|
52 | # set defaults for pr_author | |
|
53 | query = text( | |
|
54 | 'UPDATE repo_review_rules SET pr_author = :val' | |
|
55 | ).bindparams(val='no_rule') | |
|
56 | op.execute(query) | |
|
57 | ||
|
58 | # set defaults for commit_author | |
|
59 | query = text( | |
|
60 | 'UPDATE repo_review_rules SET commit_author = :val' | |
|
61 | ).bindparams(val='no_rule') | |
|
62 | op.execute(query) | |
|
63 | ||
|
64 | session().commit() | |
|
65 | ||
|
66 | # now change the flags to forbid based on | |
|
67 | # forbid_author_to_review, forbid_commit_author_to_review | |
|
68 | query = text( | |
|
69 | 'UPDATE repo_review_rules SET pr_author = :val WHERE forbid_author_to_review = TRUE' | |
|
70 | ).bindparams(val='forbid_pr_author') | |
|
71 | op.execute(query) | |
|
72 | ||
|
73 | query = text( | |
|
74 | 'UPDATE repo_review_rules SET commit_author = :val WHERE forbid_commit_author_to_review = TRUE' | |
|
75 | ).bindparams(val='forbid_commit_author') | |
|
76 | op.execute(query) | |
|
77 | ||
|
78 | session().commit() |
@@ -1,5 +1,5 b'' | |||
|
1 | 1 | [bumpversion] |
|
2 |
current_version = 4.2 |
|
|
2 | current_version = 4.23.0 | |
|
3 | 3 | message = release: Bump version {current_version} to {new_version} |
|
4 | 4 | |
|
5 | 5 | [bumpversion:file:rhodecode/VERSION] |
@@ -5,25 +5,20 b' done = false' | |||
|
5 | 5 | done = true |
|
6 | 6 | |
|
7 | 7 | [task:rc_tools_pinned] |
|
8 | done = true | |
|
9 | 8 | |
|
10 | 9 | [task:fixes_on_stable] |
|
11 | done = true | |
|
12 | 10 | |
|
13 | 11 | [task:pip2nix_generated] |
|
14 | done = true | |
|
15 | 12 | |
|
16 | 13 | [task:changelog_updated] |
|
17 | done = true | |
|
18 | 14 | |
|
19 | 15 | [task:generate_api_docs] |
|
20 | done = true | |
|
16 | ||
|
17 | [task:updated_translation] | |
|
21 | 18 | |
|
22 | 19 | [release] |
|
23 |
state = |
|
|
24 |
version = 4.2 |
|
|
25 | ||
|
26 | [task:updated_translation] | |
|
20 | state = in_progress | |
|
21 | version = 4.23.0 | |
|
27 | 22 | |
|
28 | 23 | [task:generate_js_routes] |
|
29 | 24 |
@@ -9,6 +9,7 b' Release Notes' | |||
|
9 | 9 | .. toctree:: |
|
10 | 10 | :maxdepth: 1 |
|
11 | 11 | |
|
12 | release-notes-4.23.0.rst | |
|
12 | 13 | release-notes-4.22.0.rst |
|
13 | 14 | release-notes-4.21.0.rst |
|
14 | 15 | release-notes-4.20.1.rst |
@@ -1883,7 +1883,7 b' self: super: {' | |||
|
1883 | 1883 | }; |
|
1884 | 1884 | }; |
|
1885 | 1885 | "rhodecode-enterprise-ce" = super.buildPythonPackage { |
|
1886 |
name = "rhodecode-enterprise-ce-4.2 |
|
|
1886 | name = "rhodecode-enterprise-ce-4.23.0"; | |
|
1887 | 1887 | buildInputs = [ |
|
1888 | 1888 | self."pytest" |
|
1889 | 1889 | self."py" |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||
|
48 | 48 | EXTENSIONS = {} |
|
49 | 49 | |
|
50 | 50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
51 |
__dbversion__ = 11 |
|
|
51 | __dbversion__ = 112 # defines current db version for migrations | |
|
52 | 52 | __platform__ = platform.system() |
|
53 | 53 | __license__ = 'AGPLv3, and Commercial License' |
|
54 | 54 | __author__ = 'RhodeCode GmbH' |
@@ -351,7 +351,10 b' def get_pull_request_or_error(pullreques' | |||
|
351 | 351 | return pull_request |
|
352 | 352 | |
|
353 | 353 | |
|
354 | def build_commit_data(commit, detail_level): | |
|
354 | def build_commit_data(rhodecode_vcs_repo, commit, detail_level): | |
|
355 | commit2 = commit | |
|
356 | commit1 = commit.first_parent | |
|
357 | ||
|
355 | 358 | parsed_diff = [] |
|
356 | 359 | if detail_level == 'extended': |
|
357 | 360 | for f_path in commit.added_paths: |
@@ -362,8 +365,11 b' def build_commit_data(commit, detail_lev' | |||
|
362 | 365 | parsed_diff.append(_get_commit_dict(filename=f_path, op='D')) |
|
363 | 366 | |
|
364 | 367 | elif detail_level == 'full': |
|
365 |
from rhodecode.lib |
|
|
366 | diff_processor = DiffProcessor(commit.diff()) | |
|
368 | from rhodecode.lib import diffs | |
|
369 | ||
|
370 | _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,) | |
|
371 | diff_processor = diffs.DiffProcessor(_diff, format='newdiff', show_full_diff=True) | |
|
372 | ||
|
367 | 373 | for dp in diff_processor.prepare(): |
|
368 | 374 | del dp['stats']['ops'] |
|
369 | 375 | _stats = dp['stats'] |
@@ -317,17 +317,18 b' def get_repo_changeset(request, apiuser,' | |||
|
317 | 317 | 'ret_type must be one of %s' % ( |
|
318 | 318 | ','.join(_changes_details_types))) |
|
319 | 319 | |
|
320 | vcs_repo = repo.scm_instance() | |
|
320 | 321 | pre_load = ['author', 'branch', 'date', 'message', 'parents', |
|
321 | 322 | 'status', '_commit', '_file_paths'] |
|
322 | 323 | |
|
323 | 324 | try: |
|
324 |
c |
|
|
325 | commit = repo.get_commit(commit_id=revision, pre_load=pre_load) | |
|
325 | 326 | except TypeError as e: |
|
326 | 327 | raise JSONRPCError(safe_str(e)) |
|
327 |
_cs_json = c |
|
|
328 | _cs_json['diff'] = build_commit_data(cs, changes_details) | |
|
328 | _cs_json = commit.__json__() | |
|
329 | _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details) | |
|
329 | 330 | if changes_details == 'full': |
|
330 |
_cs_json['refs'] = c |
|
|
331 | _cs_json['refs'] = commit._get_refs() | |
|
331 | 332 | return _cs_json |
|
332 | 333 | |
|
333 | 334 | |
@@ -398,7 +399,7 b' def get_repo_changesets(request, apiuser' | |||
|
398 | 399 | if cnt >= limit != -1: |
|
399 | 400 | break |
|
400 | 401 | _cs_json = commit.__json__() |
|
401 | _cs_json['diff'] = build_commit_data(commit, changes_details) | |
|
402 | _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details) | |
|
402 | 403 | if changes_details == 'full': |
|
403 | 404 | _cs_json['refs'] = { |
|
404 | 405 | 'branches': [commit.branch], |
@@ -36,7 +36,7 b' from rhodecode.authentication.plugins im' | |||
|
36 | 36 | from rhodecode.events import trigger |
|
37 | 37 | from rhodecode.model.db import true, UserNotice |
|
38 | 38 | |
|
39 | from rhodecode.lib import audit_logger, rc_cache | |
|
39 | from rhodecode.lib import audit_logger, rc_cache, auth | |
|
40 | 40 | from rhodecode.lib.exceptions import ( |
|
41 | 41 | UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException, |
|
42 | 42 | UserOwnsUserGroupsException, UserOwnsPullRequestsException, |
@@ -295,6 +295,10 b' class UsersView(UserAppView):' | |||
|
295 | 295 | c.allowed_extern_types = [ |
|
296 | 296 | (x.uid, x.get_display_name()) for x in self.get_auth_plugins() |
|
297 | 297 | ] |
|
298 | perms = req.registry.settings.get('available_permissions') | |
|
299 | if not perms: | |
|
300 | # inject info about available permissions | |
|
301 | auth.set_available_permissions(req.registry.settings) | |
|
298 | 302 | |
|
299 | 303 | c.available_permissions = req.registry.settings['available_permissions'] |
|
300 | 304 | PermissionModel().set_global_permission_choices( |
@@ -252,7 +252,7 b' But please check this code' | |||
|
252 | 252 | var comment = $('#comment-'+commentId); |
|
253 | 253 | var commentData = comment.data(); |
|
254 | 254 | if (commentData.commentInline) { |
|
255 | this.createComment(comment, commentId) | |
|
255 | this.createComment(comment, f_path, line_no, commentId) | |
|
256 | 256 | } else { |
|
257 | 257 | Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId) |
|
258 | 258 | } |
@@ -702,7 +702,9 b' class MyAccountView(BaseAppView, DataGri' | |||
|
702 | 702 | **valid_data) |
|
703 | 703 | if old_email != valid_data['email']: |
|
704 | 704 | old = UserEmailMap.query() \ |
|
705 |
.filter(UserEmailMap.user == c.user) |
|
|
705 | .filter(UserEmailMap.user == c.user)\ | |
|
706 | .filter(UserEmailMap.email == valid_data['email'])\ | |
|
707 | .first() | |
|
706 | 708 | old.email = old_email |
|
707 | 709 | h.flash(_('Your account was updated successfully'), category='success') |
|
708 | 710 | Session().commit() |
@@ -718,6 +720,7 b' class MyAccountView(BaseAppView, DataGri' | |||
|
718 | 720 | def _get_pull_requests_list(self, statuses): |
|
719 | 721 | draw, start, limit = self._extract_chunk(self.request) |
|
720 | 722 | search_q, order_by, order_dir = self._extract_ordering(self.request) |
|
723 | ||
|
721 | 724 | _render = self.request.get_partial_renderer( |
|
722 | 725 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
723 | 726 | |
@@ -735,7 +738,7 b' class MyAccountView(BaseAppView, DataGri' | |||
|
735 | 738 | for pr in pull_requests: |
|
736 | 739 | repo_id = pr.target_repo_id |
|
737 | 740 | comments_count = comments_model.get_all_comments( |
|
738 | repo_id, pull_request=pr, count_only=True) | |
|
741 | repo_id, pull_request=pr, include_drafts=False, count_only=True) | |
|
739 | 742 | owned = pr.user_id == self._rhodecode_user.user_id |
|
740 | 743 | |
|
741 | 744 | data.append({ |
@@ -751,7 +754,8 b' class MyAccountView(BaseAppView, DataGri' | |||
|
751 | 754 | 'title': _render('pullrequest_title', pr.title, pr.description), |
|
752 | 755 | 'description': h.escape(pr.description), |
|
753 | 756 | 'updated_on': _render('pullrequest_updated_on', |
|
754 |
h.datetime_to_time(pr.updated_on) |
|
|
757 | h.datetime_to_time(pr.updated_on), | |
|
758 | pr.versions_count), | |
|
755 | 759 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
756 | 760 | 'created_on': _render('pullrequest_updated_on', |
|
757 | 761 | h.datetime_to_time(pr.created_on)), |
@@ -355,6 +355,11 b' def includeme(config):' | |||
|
355 | 355 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos', |
|
356 | 356 | repo_route=True) |
|
357 | 357 | |
|
358 | config.add_route( | |
|
359 | name='pullrequest_drafts', | |
|
360 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts', | |
|
361 | repo_route=True) | |
|
362 | ||
|
358 | 363 | # Artifacts, (EE feature) |
|
359 | 364 | config.add_route( |
|
360 | 365 | name='repo_artifacts_list', |
@@ -608,23 +608,23 b' class TestPullrequestsView(object):' | |||
|
608 | 608 | pull_request.source_repo, pull_request=pull_request) |
|
609 | 609 | assert status == ChangesetStatus.STATUS_REJECTED |
|
610 | 610 | |
|
611 |
comment_id |
|
|
612 | test_text = 'test' | |
|
613 | response = self.app.post( | |
|
614 | route_path( | |
|
615 | 'pullrequest_comment_edit', | |
|
616 | repo_name=target_scm_name, | |
|
617 | pull_request_id=pull_request_id, | |
|
618 | comment_id=comment_id, | |
|
619 | ), | |
|
620 | extra_environ=xhr_header, | |
|
621 | params={ | |
|
622 | 'csrf_token': csrf_token, | |
|
623 | 'text': test_text, | |
|
624 | }, | |
|
625 | status=403, | |
|
626 | ) | |
|
627 | assert response.status_int == 403 | |
|
611 | for comment_id in response.json.keys(): | |
|
612 | test_text = 'test' | |
|
613 | response = self.app.post( | |
|
614 | route_path( | |
|
615 | 'pullrequest_comment_edit', | |
|
616 | repo_name=target_scm_name, | |
|
617 | pull_request_id=pull_request_id, | |
|
618 | comment_id=comment_id, | |
|
619 | ), | |
|
620 | extra_environ=xhr_header, | |
|
621 | params={ | |
|
622 | 'csrf_token': csrf_token, | |
|
623 | 'text': test_text, | |
|
624 | }, | |
|
625 | status=403, | |
|
626 | ) | |
|
627 | assert response.status_int == 403 | |
|
628 | 628 | |
|
629 | 629 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): |
|
630 | 630 | pull_request = pr_util.create_pull_request() |
@@ -644,27 +644,27 b' class TestPullrequestsView(object):' | |||
|
644 | 644 | ) |
|
645 | 645 | assert response.json |
|
646 | 646 | |
|
647 |
comment_id |
|
|
648 | assert comment_id | |
|
649 | test_text = 'test' | |
|
650 | self.app.post( | |
|
651 | route_path( | |
|
652 | 'pullrequest_comment_edit', | |
|
653 | repo_name=target_scm_name, | |
|
654 | pull_request_id=pull_request.pull_request_id, | |
|
655 | comment_id=comment_id, | |
|
656 | ), | |
|
657 | extra_environ=xhr_header, | |
|
658 | params={ | |
|
659 | 'csrf_token': csrf_token, | |
|
660 | 'text': test_text, | |
|
661 | 'version': '0', | |
|
662 | }, | |
|
647 | for comment_id in response.json.keys(): | |
|
648 | assert comment_id | |
|
649 | test_text = 'test' | |
|
650 | self.app.post( | |
|
651 | route_path( | |
|
652 | 'pullrequest_comment_edit', | |
|
653 | repo_name=target_scm_name, | |
|
654 | pull_request_id=pull_request.pull_request_id, | |
|
655 | comment_id=comment_id, | |
|
656 | ), | |
|
657 | extra_environ=xhr_header, | |
|
658 | params={ | |
|
659 | 'csrf_token': csrf_token, | |
|
660 | 'text': test_text, | |
|
661 | 'version': '0', | |
|
662 | }, | |
|
663 | 663 | |
|
664 | ) | |
|
665 | text_form_db = ChangesetComment.query().filter( | |
|
666 | ChangesetComment.comment_id == comment_id).first().text | |
|
667 | assert test_text == text_form_db | |
|
664 | ) | |
|
665 | text_form_db = ChangesetComment.query().filter( | |
|
666 | ChangesetComment.comment_id == comment_id).first().text | |
|
667 | assert test_text == text_form_db | |
|
668 | 668 | |
|
669 | 669 | def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header): |
|
670 | 670 | pull_request = pr_util.create_pull_request() |
@@ -684,26 +684,25 b' class TestPullrequestsView(object):' | |||
|
684 | 684 | ) |
|
685 | 685 | assert response.json |
|
686 | 686 | |
|
687 |
comment_id |
|
|
688 | assert comment_id | |
|
689 | test_text = 'init' | |
|
690 | response = self.app.post( | |
|
691 | route_path( | |
|
692 | 'pullrequest_comment_edit', | |
|
693 | repo_name=target_scm_name, | |
|
694 | pull_request_id=pull_request.pull_request_id, | |
|
695 |
|
|
|
696 | ), | |
|
697 | extra_environ=xhr_header, | |
|
698 | params={ | |
|
699 |
' |
|
|
700 |
' |
|
|
701 |
|
|
|
702 |
|
|
|
703 | status=404, | |
|
687 | for comment_id in response.json.keys(): | |
|
688 | test_text = 'init' | |
|
689 | response = self.app.post( | |
|
690 | route_path( | |
|
691 | 'pullrequest_comment_edit', | |
|
692 | repo_name=target_scm_name, | |
|
693 | pull_request_id=pull_request.pull_request_id, | |
|
694 | comment_id=comment_id, | |
|
695 | ), | |
|
696 | extra_environ=xhr_header, | |
|
697 | params={ | |
|
698 | 'csrf_token': csrf_token, | |
|
699 | 'text': test_text, | |
|
700 | 'version': '0', | |
|
701 | }, | |
|
702 | status=404, | |
|
704 | 703 | |
|
705 | ) | |
|
706 | assert response.status_int == 404 | |
|
704 | ) | |
|
705 | assert response.status_int == 404 | |
|
707 | 706 | |
|
708 | 707 | def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header): |
|
709 | 708 | pull_request = pr_util.create_pull_request() |
@@ -722,48 +721,46 b' class TestPullrequestsView(object):' | |||
|
722 | 721 | extra_environ=xhr_header, |
|
723 | 722 | ) |
|
724 | 723 | assert response.json |
|
725 |
comment_id |
|
|
726 | assert comment_id | |
|
727 | ||
|
728 | test_text = 'test' | |
|
729 | self.app.post( | |
|
730 | route_path( | |
|
731 |
|
|
|
732 |
|
|
|
733 | pull_request_id=pull_request.pull_request_id, | |
|
734 | comment_id=comment_id, | |
|
735 |
|
|
|
736 | extra_environ=xhr_header, | |
|
737 | params={ | |
|
738 |
' |
|
|
739 |
|
|
|
740 | 'version': '0', | |
|
741 | }, | |
|
724 | for comment_id in response.json.keys(): | |
|
725 | test_text = 'test' | |
|
726 | self.app.post( | |
|
727 | route_path( | |
|
728 | 'pullrequest_comment_edit', | |
|
729 | repo_name=target_scm_name, | |
|
730 | pull_request_id=pull_request.pull_request_id, | |
|
731 | comment_id=comment_id, | |
|
732 | ), | |
|
733 | extra_environ=xhr_header, | |
|
734 | params={ | |
|
735 | 'csrf_token': csrf_token, | |
|
736 | 'text': test_text, | |
|
737 | 'version': '0', | |
|
738 | }, | |
|
742 | 739 | |
|
743 | ) | |
|
744 | test_text_v2 = 'test_v2' | |
|
745 | response = self.app.post( | |
|
746 | route_path( | |
|
747 | 'pullrequest_comment_edit', | |
|
748 | repo_name=target_scm_name, | |
|
749 | pull_request_id=pull_request.pull_request_id, | |
|
750 | comment_id=comment_id, | |
|
751 | ), | |
|
752 | extra_environ=xhr_header, | |
|
753 | params={ | |
|
754 | 'csrf_token': csrf_token, | |
|
755 | 'text': test_text_v2, | |
|
756 | 'version': '0', | |
|
757 | }, | |
|
758 | status=409, | |
|
759 | ) | |
|
760 | assert response.status_int == 409 | |
|
740 | ) | |
|
741 | test_text_v2 = 'test_v2' | |
|
742 | response = self.app.post( | |
|
743 | route_path( | |
|
744 | 'pullrequest_comment_edit', | |
|
745 | repo_name=target_scm_name, | |
|
746 | pull_request_id=pull_request.pull_request_id, | |
|
747 | comment_id=comment_id, | |
|
748 | ), | |
|
749 | extra_environ=xhr_header, | |
|
750 | params={ | |
|
751 | 'csrf_token': csrf_token, | |
|
752 | 'text': test_text_v2, | |
|
753 | 'version': '0', | |
|
754 | }, | |
|
755 | status=409, | |
|
756 | ) | |
|
757 | assert response.status_int == 409 | |
|
761 | 758 | |
|
762 | text_form_db = ChangesetComment.query().filter( | |
|
763 | ChangesetComment.comment_id == comment_id).first().text | |
|
759 | text_form_db = ChangesetComment.query().filter( | |
|
760 | ChangesetComment.comment_id == comment_id).first().text | |
|
764 | 761 | |
|
765 | assert test_text == text_form_db | |
|
766 | assert test_text_v2 != text_form_db | |
|
762 | assert test_text == text_form_db | |
|
763 | assert test_text_v2 != text_form_db | |
|
767 | 764 | |
|
768 | 765 | def test_comment_and_comment_edit_permissions_forbidden( |
|
769 | 766 | self, autologin_regular_user, user_regular, user_admin, pr_util, |
@@ -24,7 +24,8 b' from rhodecode.model.pull_request import' | |||
|
24 | 24 | from rhodecode.model.db import PullRequestReviewers |
|
25 | 25 | # V3 - Reviewers, with default rules data |
|
26 | 26 | # v4 - Added observers metadata |
|
27 | REVIEWER_API_VERSION = 'V4' | |
|
27 | # v5 - pr_author/commit_author include/exclude logic | |
|
28 | REVIEWER_API_VERSION = 'V5' | |
|
28 | 29 | |
|
29 | 30 | |
|
30 | 31 | def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None): |
@@ -88,6 +89,7 b' def get_default_reviewers_data(current_u' | |||
|
88 | 89 | 'reviewers': json_reviewers, |
|
89 | 90 | 'rules': {}, |
|
90 | 91 | 'rules_data': {}, |
|
92 | 'rules_humanized': [], | |
|
91 | 93 | } |
|
92 | 94 | |
|
93 | 95 |
@@ -77,6 +77,7 b' class RepoCommitsView(RepoAppView):' | |||
|
77 | 77 | _ = self.request.translate |
|
78 | 78 | c = self.load_default_context() |
|
79 | 79 | c.fulldiff = self.request.GET.get('fulldiff') |
|
80 | redirect_to_combined = str2bool(self.request.GET.get('redirect_combined')) | |
|
80 | 81 | |
|
81 | 82 | # fetch global flags of ignore ws or context lines |
|
82 | 83 | diff_context = get_diff_context(self.request) |
@@ -117,6 +118,19 b' class RepoCommitsView(RepoAppView):' | |||
|
117 | 118 | raise HTTPNotFound() |
|
118 | 119 | single_commit = len(c.commit_ranges) == 1 |
|
119 | 120 | |
|
121 | if redirect_to_combined and not single_commit: | |
|
122 | source_ref = getattr(c.commit_ranges[0].parents[0] | |
|
123 | if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id') | |
|
124 | target_ref = c.commit_ranges[-1].raw_id | |
|
125 | next_url = h.route_path( | |
|
126 | 'repo_compare', | |
|
127 | repo_name=c.repo_name, | |
|
128 | source_ref_type='rev', | |
|
129 | source_ref=source_ref, | |
|
130 | target_ref_type='rev', | |
|
131 | target_ref=target_ref) | |
|
132 | raise HTTPFound(next_url) | |
|
133 | ||
|
120 | 134 | c.changes = OrderedDict() |
|
121 | 135 | c.lines_added = 0 |
|
122 | 136 | c.lines_deleted = 0 |
@@ -366,6 +380,121 b' class RepoCommitsView(RepoAppView):' | |||
|
366 | 380 | commit_id = self.request.matchdict['commit_id'] |
|
367 | 381 | return self._commit(commit_id, method='download') |
|
368 | 382 | |
|
383 | def _commit_comments_create(self, commit_id, comments): | |
|
384 | _ = self.request.translate | |
|
385 | data = {} | |
|
386 | if not comments: | |
|
387 | return | |
|
388 | ||
|
389 | commit = self.db_repo.get_commit(commit_id) | |
|
390 | ||
|
391 | all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments) | |
|
392 | for entry in comments: | |
|
393 | c = self.load_default_context() | |
|
394 | comment_type = entry['comment_type'] | |
|
395 | text = entry['text'] | |
|
396 | status = entry['status'] | |
|
397 | is_draft = str2bool(entry['is_draft']) | |
|
398 | resolves_comment_id = entry['resolves_comment_id'] | |
|
399 | f_path = entry['f_path'] | |
|
400 | line_no = entry['line'] | |
|
401 | target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path))) | |
|
402 | ||
|
403 | if status: | |
|
404 | text = text or (_('Status change %(transition_icon)s %(status)s') | |
|
405 | % {'transition_icon': '>', | |
|
406 | 'status': ChangesetStatus.get_status_lbl(status)}) | |
|
407 | ||
|
408 | comment = CommentsModel().create( | |
|
409 | text=text, | |
|
410 | repo=self.db_repo.repo_id, | |
|
411 | user=self._rhodecode_db_user.user_id, | |
|
412 | commit_id=commit_id, | |
|
413 | f_path=f_path, | |
|
414 | line_no=line_no, | |
|
415 | status_change=(ChangesetStatus.get_status_lbl(status) | |
|
416 | if status else None), | |
|
417 | status_change_type=status, | |
|
418 | comment_type=comment_type, | |
|
419 | is_draft=is_draft, | |
|
420 | resolves_comment_id=resolves_comment_id, | |
|
421 | auth_user=self._rhodecode_user, | |
|
422 | send_email=not is_draft, # skip notification for draft comments | |
|
423 | ) | |
|
424 | is_inline = comment.is_inline | |
|
425 | ||
|
426 | # get status if set ! | |
|
427 | if status: | |
|
428 | # `dont_allow_on_closed_pull_request = True` means | |
|
429 | # if latest status was from pull request and it's closed | |
|
430 | # disallow changing status ! | |
|
431 | ||
|
432 | try: | |
|
433 | ChangesetStatusModel().set_status( | |
|
434 | self.db_repo.repo_id, | |
|
435 | status, | |
|
436 | self._rhodecode_db_user.user_id, | |
|
437 | comment, | |
|
438 | revision=commit_id, | |
|
439 | dont_allow_on_closed_pull_request=True | |
|
440 | ) | |
|
441 | except StatusChangeOnClosedPullRequestError: | |
|
442 | msg = _('Changing the status of a commit associated with ' | |
|
443 | 'a closed pull request is not allowed') | |
|
444 | log.exception(msg) | |
|
445 | h.flash(msg, category='warning') | |
|
446 | raise HTTPFound(h.route_path( | |
|
447 | 'repo_commit', repo_name=self.db_repo_name, | |
|
448 | commit_id=commit_id)) | |
|
449 | ||
|
450 | Session().flush() | |
|
451 | # this is somehow required to get access to some relationship | |
|
452 | # loaded on comment | |
|
453 | Session().refresh(comment) | |
|
454 | ||
|
455 | # skip notifications for drafts | |
|
456 | if not is_draft: | |
|
457 | CommentsModel().trigger_commit_comment_hook( | |
|
458 | self.db_repo, self._rhodecode_user, 'create', | |
|
459 | data={'comment': comment, 'commit': commit}) | |
|
460 | ||
|
461 | comment_id = comment.comment_id | |
|
462 | data[comment_id] = { | |
|
463 | 'target_id': target_elem_id | |
|
464 | } | |
|
465 | Session().flush() | |
|
466 | ||
|
467 | c.co = comment | |
|
468 | c.at_version_num = 0 | |
|
469 | c.is_new = True | |
|
470 | rendered_comment = render( | |
|
471 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
|
472 | self._get_template_context(c), self.request) | |
|
473 | ||
|
474 | data[comment_id].update(comment.get_dict()) | |
|
475 | data[comment_id].update({'rendered_text': rendered_comment}) | |
|
476 | ||
|
477 | # finalize, commit and redirect | |
|
478 | Session().commit() | |
|
479 | ||
|
480 | # skip channelstream for draft comments | |
|
481 | if not all_drafts: | |
|
482 | comment_broadcast_channel = channelstream.comment_channel( | |
|
483 | self.db_repo_name, commit_obj=commit) | |
|
484 | ||
|
485 | comment_data = data | |
|
486 | posted_comment_type = 'inline' if is_inline else 'general' | |
|
487 | if len(data) == 1: | |
|
488 | msg = _('posted {} new {} comment').format(len(data), posted_comment_type) | |
|
489 | else: | |
|
490 | msg = _('posted {} new {} comments').format(len(data), posted_comment_type) | |
|
491 | ||
|
492 | channelstream.comment_channelstream_push( | |
|
493 | self.request, comment_broadcast_channel, self._rhodecode_user, msg, | |
|
494 | comment_data=comment_data) | |
|
495 | ||
|
496 | return data | |
|
497 | ||
|
369 | 498 | @LoginRequired() |
|
370 | 499 | @NotAnonymous() |
|
371 | 500 | @HasRepoPermissionAnyDecorator( |
@@ -378,17 +507,6 b' class RepoCommitsView(RepoAppView):' | |||
|
378 | 507 | _ = self.request.translate |
|
379 | 508 | commit_id = self.request.matchdict['commit_id'] |
|
380 | 509 | |
|
381 | c = self.load_default_context() | |
|
382 | status = self.request.POST.get('changeset_status', None) | |
|
383 | text = self.request.POST.get('text') | |
|
384 | comment_type = self.request.POST.get('comment_type') | |
|
385 | resolves_comment_id = self.request.POST.get('resolves_comment_id', None) | |
|
386 | ||
|
387 | if status: | |
|
388 | text = text or (_('Status change %(transition_icon)s %(status)s') | |
|
389 | % {'transition_icon': '>', | |
|
390 | 'status': ChangesetStatus.get_status_lbl(status)}) | |
|
391 | ||
|
392 | 510 | multi_commit_ids = [] |
|
393 | 511 | for _commit_id in self.request.POST.get('commit_ids', '').split(','): |
|
394 | 512 | if _commit_id not in ['', None, EmptyCommit.raw_id]: |
@@ -397,81 +515,23 b' class RepoCommitsView(RepoAppView):' | |||
|
397 | 515 | |
|
398 | 516 | commit_ids = multi_commit_ids or [commit_id] |
|
399 | 517 | |
|
400 |
|
|
|
518 | data = [] | |
|
519 | # Multiple comments for each passed commit id | |
|
401 | 520 | for current_id in filter(None, commit_ids): |
|
402 |
comment = |
|
|
403 | text=text, | |
|
404 | repo=self.db_repo.repo_id, | |
|
405 | user=self._rhodecode_db_user.user_id, | |
|
406 | commit_id=current_id, | |
|
407 |
|
|
|
408 |
|
|
|
409 | status_change=(ChangesetStatus.get_status_lbl(status) | |
|
410 | if status else None), | |
|
411 | status_change_type=status, | |
|
412 | comment_type=comment_type, | |
|
413 | resolves_comment_id=resolves_comment_id, | |
|
414 | auth_user=self._rhodecode_user | |
|
415 | ) | |
|
416 | is_inline = comment.is_inline | |
|
417 | ||
|
418 | # get status if set ! | |
|
419 | if status: | |
|
420 | # if latest status was from pull request and it's closed | |
|
421 | # disallow changing status ! | |
|
422 | # dont_allow_on_closed_pull_request = True ! | |
|
521 | comment_data = { | |
|
522 | 'comment_type': self.request.POST.get('comment_type'), | |
|
523 | 'text': self.request.POST.get('text'), | |
|
524 | 'status': self.request.POST.get('changeset_status', None), | |
|
525 | 'is_draft': self.request.POST.get('draft'), | |
|
526 | 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None), | |
|
527 | 'close_pull_request': self.request.POST.get('close_pull_request'), | |
|
528 | 'f_path': self.request.POST.get('f_path'), | |
|
529 | 'line': self.request.POST.get('line'), | |
|
530 | } | |
|
531 | comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data]) | |
|
532 | data.append(comment) | |
|
423 | 533 | |
|
424 | try: | |
|
425 | ChangesetStatusModel().set_status( | |
|
426 | self.db_repo.repo_id, | |
|
427 | status, | |
|
428 | self._rhodecode_db_user.user_id, | |
|
429 | comment, | |
|
430 | revision=current_id, | |
|
431 | dont_allow_on_closed_pull_request=True | |
|
432 | ) | |
|
433 | except StatusChangeOnClosedPullRequestError: | |
|
434 | msg = _('Changing the status of a commit associated with ' | |
|
435 | 'a closed pull request is not allowed') | |
|
436 | log.exception(msg) | |
|
437 | h.flash(msg, category='warning') | |
|
438 | raise HTTPFound(h.route_path( | |
|
439 | 'repo_commit', repo_name=self.db_repo_name, | |
|
440 | commit_id=current_id)) | |
|
441 | ||
|
442 | commit = self.db_repo.get_commit(current_id) | |
|
443 | CommentsModel().trigger_commit_comment_hook( | |
|
444 | self.db_repo, self._rhodecode_user, 'create', | |
|
445 | data={'comment': comment, 'commit': commit}) | |
|
446 | ||
|
447 | # finalize, commit and redirect | |
|
448 | Session().commit() | |
|
449 | ||
|
450 | data = { | |
|
451 | 'target_id': h.safeid(h.safe_unicode( | |
|
452 | self.request.POST.get('f_path'))), | |
|
453 | } | |
|
454 | if comment: | |
|
455 | c.co = comment | |
|
456 | c.at_version_num = 0 | |
|
457 | rendered_comment = render( | |
|
458 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
|
459 | self._get_template_context(c), self.request) | |
|
460 | ||
|
461 | data.update(comment.get_dict()) | |
|
462 | data.update({'rendered_text': rendered_comment}) | |
|
463 | ||
|
464 | comment_broadcast_channel = channelstream.comment_channel( | |
|
465 | self.db_repo_name, commit_obj=commit) | |
|
466 | ||
|
467 | comment_data = data | |
|
468 | comment_type = 'inline' if is_inline else 'general' | |
|
469 | channelstream.comment_channelstream_push( | |
|
470 | self.request, comment_broadcast_channel, self._rhodecode_user, | |
|
471 | _('posted a new {} comment').format(comment_type), | |
|
472 | comment_data=comment_data) | |
|
473 | ||
|
474 | return data | |
|
534 | return data if len(data) > 1 else data[0] | |
|
475 | 535 | |
|
476 | 536 | @LoginRequired() |
|
477 | 537 | @NotAnonymous() |
@@ -665,6 +725,7 b' class RepoCommitsView(RepoAppView):' | |||
|
665 | 725 | def repo_commit_comment_edit(self): |
|
666 | 726 | self.load_default_context() |
|
667 | 727 | |
|
728 | commit_id = self.request.matchdict['commit_id'] | |
|
668 | 729 | comment_id = self.request.matchdict['comment_id'] |
|
669 | 730 | comment = ChangesetComment.get_or_404(comment_id) |
|
670 | 731 | |
@@ -717,11 +778,11 b' class RepoCommitsView(RepoAppView):' | |||
|
717 | 778 | if not comment_history: |
|
718 | 779 | raise HTTPNotFound() |
|
719 | 780 | |
|
720 | commit_id = self.request.matchdict['commit_id'] | |
|
721 | commit = self.db_repo.get_commit(commit_id) | |
|
722 | CommentsModel().trigger_commit_comment_hook( | |
|
723 | self.db_repo, self._rhodecode_user, 'edit', | |
|
724 | data={'comment': comment, 'commit': commit}) | |
|
781 | if not comment.draft: | |
|
782 | commit = self.db_repo.get_commit(commit_id) | |
|
783 | CommentsModel().trigger_commit_comment_hook( | |
|
784 | self.db_repo, self._rhodecode_user, 'edit', | |
|
785 | data={'comment': comment, 'commit': commit}) | |
|
725 | 786 | |
|
726 | 787 | Session().commit() |
|
727 | 788 | return { |
@@ -325,6 +325,21 b' class RepoFilesView(RepoAppView):' | |||
|
325 | 325 | |
|
326 | 326 | return lf_enabled |
|
327 | 327 | |
|
328 | def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha=''): | |
|
329 | # original backward compat name of archive | |
|
330 | clean_name = safe_str(db_repo_name.replace('/', '_')) | |
|
331 | ||
|
332 | # e.g vcsserver.zip | |
|
333 | # e.g vcsserver-abcdefgh.zip | |
|
334 | # e.g vcsserver-abcdefgh-defghijk.zip | |
|
335 | archive_name = '{}{}{}{}{}'.format( | |
|
336 | clean_name, | |
|
337 | '-sub' if subrepos else '', | |
|
338 | commit_sha, | |
|
339 | '-{}'.format(path_sha) if path_sha else '', | |
|
340 | ext) | |
|
341 | return archive_name | |
|
342 | ||
|
328 | 343 | @LoginRequired() |
|
329 | 344 | @HasRepoPermissionAnyDecorator( |
|
330 | 345 | 'repository.read', 'repository.write', 'repository.admin') |
@@ -339,6 +354,7 b' class RepoFilesView(RepoAppView):' | |||
|
339 | 354 | default_at_path = '/' |
|
340 | 355 | fname = self.request.matchdict['fname'] |
|
341 | 356 | subrepos = self.request.GET.get('subrepos') == 'true' |
|
357 | with_hash = str2bool(self.request.GET.get('with_hash', '1')) | |
|
342 | 358 | at_path = self.request.GET.get('at_path') or default_at_path |
|
343 | 359 | |
|
344 | 360 | if not self.db_repo.enable_downloads: |
@@ -364,30 +380,30 b' class RepoFilesView(RepoAppView):' | |||
|
364 | 380 | except Exception: |
|
365 | 381 | return Response(_('No node at path {} for this repository').format(at_path)) |
|
366 | 382 | |
|
367 | path_sha = sha1(at_path)[:8] | |
|
368 | ||
|
369 | # original backward compat name of archive | |
|
370 | clean_name = safe_str(self.db_repo_name.replace('/', '_')) | |
|
371 | short_sha = safe_str(commit.short_id) | |
|
383 | # path sha is part of subdir | |
|
384 | path_sha = '' | |
|
385 | if at_path != default_at_path: | |
|
386 | path_sha = sha1(at_path)[:8] | |
|
387 | short_sha = '-{}'.format(safe_str(commit.short_id)) | |
|
388 | # used for cache etc | |
|
389 | archive_name = self._get_archive_name( | |
|
390 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |
|
391 | path_sha=path_sha) | |
|
372 | 392 | |
|
373 | if at_path == default_at_path: | |
|
374 | archive_name = '{}-{}{}{}'.format( | |
|
375 | clean_name, | |
|
376 | '-sub' if subrepos else '', | |
|
377 | short_sha, | |
|
378 | ext) | |
|
379 | # custom path and new name | |
|
380 | else: | |
|
381 | archive_name = '{}-{}{}-{}{}'.format( | |
|
382 | clean_name, | |
|
383 | '-sub' if subrepos else '', | |
|
384 | short_sha, | |
|
385 | path_sha, | |
|
386 | ext) | |
|
393 | if not with_hash: | |
|
394 | short_sha = '' | |
|
395 | path_sha = '' | |
|
396 | ||
|
397 | # what end client gets served | |
|
398 | response_archive_name = self._get_archive_name( | |
|
399 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |
|
400 | path_sha=path_sha) | |
|
401 | # remove extension from our archive directory name | |
|
402 | archive_dir_name = response_archive_name[:-len(ext)] | |
|
387 | 403 | |
|
388 | 404 | use_cached_archive = False |
|
389 |
archive_cache_ |
|
|
390 |
|
|
|
405 | archive_cache_dir = CONFIG.get('archive_cache_dir') | |
|
406 | archive_cache_enabled = archive_cache_dir and not self.request.GET.get('no_cache') | |
|
391 | 407 | cached_archive_path = None |
|
392 | 408 | |
|
393 | 409 | if archive_cache_enabled: |
@@ -403,12 +419,14 b' class RepoFilesView(RepoAppView):' | |||
|
403 | 419 | else: |
|
404 | 420 | log.debug('Archive %s is not yet cached', archive_name) |
|
405 | 421 | |
|
422 | # generate new archive, as previous was not found in the cache | |
|
406 | 423 | if not use_cached_archive: |
|
407 | # generate new archive | |
|
408 | fd, archive = tempfile.mkstemp() | |
|
424 | _dir = os.path.abspath(archive_cache_dir) if archive_cache_dir else None | |
|
425 | fd, archive = tempfile.mkstemp(dir=_dir) | |
|
409 | 426 | log.debug('Creating new temp archive in %s', archive) |
|
410 | 427 | try: |
|
411 |
commit.archive_repo(archive, |
|
|
428 | commit.archive_repo(archive, archive_dir_name=archive_dir_name, | |
|
429 | kind=fileformat, subrepos=subrepos, | |
|
412 | 430 | archive_at_path=at_path) |
|
413 | 431 | except ImproperArchiveTypeError: |
|
414 | 432 | return _('Unknown archive type') |
@@ -445,8 +463,7 b' class RepoFilesView(RepoAppView):' | |||
|
445 | 463 | yield data |
|
446 | 464 | |
|
447 | 465 | response = Response(app_iter=get_chunked_archive(archive)) |
|
448 | response.content_disposition = str( | |
|
449 | 'attachment; filename=%s' % archive_name) | |
|
466 | response.content_disposition = str('attachment; filename=%s' % response_archive_name) | |
|
450 | 467 | response.content_type = str(content_type) |
|
451 | 468 | |
|
452 | 469 | return response |
@@ -47,7 +47,7 b' from rhodecode.lib.vcs.exceptions import' | |||
|
47 | 47 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
48 | 48 | from rhodecode.model.comment import CommentsModel |
|
49 | 49 | from rhodecode.model.db import ( |
|
50 | func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, | |
|
50 | func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, | |
|
51 | 51 | PullRequestReviewers) |
|
52 | 52 | from rhodecode.model.forms import PullRequestForm |
|
53 | 53 | from rhodecode.model.meta import Session |
@@ -107,7 +107,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
107 | 107 | comments_model = CommentsModel() |
|
108 | 108 | for pr in pull_requests: |
|
109 | 109 | comments_count = comments_model.get_all_comments( |
|
110 |
self.db_repo.repo_id, pull_request=pr, |
|
|
110 | self.db_repo.repo_id, pull_request=pr, | |
|
111 | include_drafts=False, count_only=True) | |
|
111 | 112 | |
|
112 | 113 | data.append({ |
|
113 | 114 | 'name': _render('pullrequest_name', |
@@ -120,7 +121,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
120 | 121 | 'title': _render('pullrequest_title', pr.title, pr.description), |
|
121 | 122 | 'description': h.escape(pr.description), |
|
122 | 123 | 'updated_on': _render('pullrequest_updated_on', |
|
123 |
h.datetime_to_time(pr.updated_on) |
|
|
124 | h.datetime_to_time(pr.updated_on), | |
|
125 | pr.versions_count), | |
|
124 | 126 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
125 | 127 | 'created_on': _render('pullrequest_updated_on', |
|
126 | 128 | h.datetime_to_time(pr.created_on)), |
@@ -268,12 +270,14 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
268 | 270 | |
|
269 | 271 | return diffset |
|
270 | 272 | |
|
271 | def register_comments_vars(self, c, pull_request, versions): | |
|
273 | def register_comments_vars(self, c, pull_request, versions, include_drafts=True): | |
|
272 | 274 | comments_model = CommentsModel() |
|
273 | 275 | |
|
274 | 276 | # GENERAL COMMENTS with versions # |
|
275 | 277 | q = comments_model._all_general_comments_of_pull_request(pull_request) |
|
276 | 278 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
279 | if not include_drafts: | |
|
280 | q = q.filter(ChangesetComment.draft == false()) | |
|
277 | 281 | general_comments = q |
|
278 | 282 | |
|
279 | 283 | # pick comments we want to render at current version |
@@ -283,6 +287,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
283 | 287 | # INLINE COMMENTS with versions # |
|
284 | 288 | q = comments_model._all_inline_comments_of_pull_request(pull_request) |
|
285 | 289 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
290 | if not include_drafts: | |
|
291 | q = q.filter(ChangesetComment.draft == false()) | |
|
286 | 292 | inline_comments = q |
|
287 | 293 | |
|
288 | 294 | c.inline_versions = comments_model.aggregate_comments( |
@@ -422,16 +428,12 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
422 | 428 | c.allowed_to_close = c.allowed_to_merge and not pr_closed |
|
423 | 429 | |
|
424 | 430 | c.forbid_adding_reviewers = False |
|
425 | c.forbid_author_to_review = False | |
|
426 | c.forbid_commit_author_to_review = False | |
|
427 | 431 | |
|
428 | 432 | if pull_request_latest.reviewer_data and \ |
|
429 | 433 | 'rules' in pull_request_latest.reviewer_data: |
|
430 | 434 | rules = pull_request_latest.reviewer_data['rules'] or {} |
|
431 | 435 | try: |
|
432 | 436 | c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers') |
|
433 | c.forbid_author_to_review = rules.get('forbid_author_to_review') | |
|
434 | c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review') | |
|
435 | 437 | except Exception: |
|
436 | 438 | pass |
|
437 | 439 | |
@@ -499,6 +501,11 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
499 | 501 | c.resolved_comments = CommentsModel() \ |
|
500 | 502 | .get_pull_request_resolved_todos(pull_request_latest) |
|
501 | 503 | |
|
504 | # Drafts | |
|
505 | c.draft_comments = CommentsModel().get_pull_request_drafts( | |
|
506 | self._rhodecode_db_user.user_id, | |
|
507 | pull_request_latest) | |
|
508 | ||
|
502 | 509 | # if we use version, then do not show later comments |
|
503 | 510 | # than current version |
|
504 | 511 | display_inline_comments = collections.defaultdict( |
@@ -979,8 +986,9 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
979 | 986 | } |
|
980 | 987 | return data |
|
981 | 988 | |
|
982 | def _get_existing_ids(self, post_data): | |
|
983 | return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ','))) | |
|
989 | @classmethod | |
|
990 | def get_comment_ids(cls, post_data): | |
|
991 | return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ','))) | |
|
984 | 992 | |
|
985 | 993 | @LoginRequired() |
|
986 | 994 | @NotAnonymous() |
@@ -1015,10 +1023,10 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1015 | 1023 | if at_version and at_version != PullRequest.LATEST_VER |
|
1016 | 1024 | else None) |
|
1017 | 1025 | |
|
1018 | self.register_comments_vars(c, pull_request_latest, versions) | |
|
1026 | self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False) | |
|
1019 | 1027 | all_comments = c.inline_comments_flat + c.comments |
|
1020 | 1028 | |
|
1021 |
existing_ids = self. |
|
|
1029 | existing_ids = self.get_comment_ids(self.request.POST) | |
|
1022 | 1030 | return _render('comments_table', all_comments, len(all_comments), |
|
1023 | 1031 | existing_ids=existing_ids) |
|
1024 | 1032 | |
@@ -1055,12 +1063,12 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1055 | 1063 | else None) |
|
1056 | 1064 | |
|
1057 | 1065 | c.unresolved_comments = CommentsModel() \ |
|
1058 | .get_pull_request_unresolved_todos(pull_request) | |
|
1066 | .get_pull_request_unresolved_todos(pull_request, include_drafts=False) | |
|
1059 | 1067 | c.resolved_comments = CommentsModel() \ |
|
1060 | .get_pull_request_resolved_todos(pull_request) | |
|
1068 | .get_pull_request_resolved_todos(pull_request, include_drafts=False) | |
|
1061 | 1069 | |
|
1062 | 1070 | all_comments = c.unresolved_comments + c.resolved_comments |
|
1063 |
existing_ids = self. |
|
|
1071 | existing_ids = self.get_comment_ids(self.request.POST) | |
|
1064 | 1072 | return _render('comments_table', all_comments, len(c.unresolved_comments), |
|
1065 | 1073 | todo_comments=True, existing_ids=existing_ids) |
|
1066 | 1074 | |
@@ -1068,6 +1076,48 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1068 | 1076 | @NotAnonymous() |
|
1069 | 1077 | @HasRepoPermissionAnyDecorator( |
|
1070 | 1078 | 'repository.read', 'repository.write', 'repository.admin') |
|
1079 | @view_config( | |
|
1080 | route_name='pullrequest_drafts', request_method='POST', | |
|
1081 | renderer='string_html', xhr=True) | |
|
1082 | def pullrequest_drafts(self): | |
|
1083 | self.load_default_context() | |
|
1084 | ||
|
1085 | pull_request = PullRequest.get_or_404( | |
|
1086 | self.request.matchdict['pull_request_id']) | |
|
1087 | pull_request_id = pull_request.pull_request_id | |
|
1088 | version = self.request.GET.get('version') | |
|
1089 | ||
|
1090 | _render = self.request.get_partial_renderer( | |
|
1091 | 'rhodecode:templates/base/sidebar.mako') | |
|
1092 | c = _render.get_call_context() | |
|
1093 | ||
|
1094 | (pull_request_latest, | |
|
1095 | pull_request_at_ver, | |
|
1096 | pull_request_display_obj, | |
|
1097 | at_version) = PullRequestModel().get_pr_version( | |
|
1098 | pull_request_id, version=version) | |
|
1099 | versions = pull_request_display_obj.versions() | |
|
1100 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |
|
1101 | c.versions = versions + [latest_ver] | |
|
1102 | ||
|
1103 | c.at_version = at_version | |
|
1104 | c.at_version_num = (at_version | |
|
1105 | if at_version and at_version != PullRequest.LATEST_VER | |
|
1106 | else None) | |
|
1107 | ||
|
1108 | c.draft_comments = CommentsModel() \ | |
|
1109 | .get_pull_request_drafts(self._rhodecode_db_user.user_id, pull_request) | |
|
1110 | ||
|
1111 | all_comments = c.draft_comments | |
|
1112 | ||
|
1113 | existing_ids = self.get_comment_ids(self.request.POST) | |
|
1114 | return _render('comments_table', all_comments, len(all_comments), | |
|
1115 | existing_ids=existing_ids, draft_comments=True) | |
|
1116 | ||
|
1117 | @LoginRequired() | |
|
1118 | @NotAnonymous() | |
|
1119 | @HasRepoPermissionAnyDecorator( | |
|
1120 | 'repository.read', 'repository.write', 'repository.admin') | |
|
1071 | 1121 | @CSRFRequired() |
|
1072 | 1122 | @view_config( |
|
1073 | 1123 | route_name='pullrequest_create', request_method='POST', |
@@ -1514,6 +1564,152 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1514 | 1564 | self._rhodecode_user) |
|
1515 | 1565 | raise HTTPNotFound() |
|
1516 | 1566 | |
|
1567 | def _pull_request_comments_create(self, pull_request, comments): | |
|
1568 | _ = self.request.translate | |
|
1569 | data = {} | |
|
1570 | if not comments: | |
|
1571 | return | |
|
1572 | pull_request_id = pull_request.pull_request_id | |
|
1573 | ||
|
1574 | all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments) | |
|
1575 | ||
|
1576 | for entry in comments: | |
|
1577 | c = self.load_default_context() | |
|
1578 | comment_type = entry['comment_type'] | |
|
1579 | text = entry['text'] | |
|
1580 | status = entry['status'] | |
|
1581 | is_draft = str2bool(entry['is_draft']) | |
|
1582 | resolves_comment_id = entry['resolves_comment_id'] | |
|
1583 | close_pull_request = entry['close_pull_request'] | |
|
1584 | f_path = entry['f_path'] | |
|
1585 | line_no = entry['line'] | |
|
1586 | target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path))) | |
|
1587 | ||
|
1588 | # the logic here should work like following, if we submit close | |
|
1589 | # pr comment, use `close_pull_request_with_comment` function | |
|
1590 | # else handle regular comment logic | |
|
1591 | ||
|
1592 | if close_pull_request: | |
|
1593 | # only owner or admin or person with write permissions | |
|
1594 | allowed_to_close = PullRequestModel().check_user_update( | |
|
1595 | pull_request, self._rhodecode_user) | |
|
1596 | if not allowed_to_close: | |
|
1597 | log.debug('comment: forbidden because not allowed to close ' | |
|
1598 | 'pull request %s', pull_request_id) | |
|
1599 | raise HTTPForbidden() | |
|
1600 | ||
|
1601 | # This also triggers `review_status_change` | |
|
1602 | comment, status = PullRequestModel().close_pull_request_with_comment( | |
|
1603 | pull_request, self._rhodecode_user, self.db_repo, message=text, | |
|
1604 | auth_user=self._rhodecode_user) | |
|
1605 | Session().flush() | |
|
1606 | is_inline = comment.is_inline | |
|
1607 | ||
|
1608 | PullRequestModel().trigger_pull_request_hook( | |
|
1609 | pull_request, self._rhodecode_user, 'comment', | |
|
1610 | data={'comment': comment}) | |
|
1611 | ||
|
1612 | else: | |
|
1613 | # regular comment case, could be inline, or one with status. | |
|
1614 | # for that one we check also permissions | |
|
1615 | # Additionally ENSURE if somehow draft is sent we're then unable to change status | |
|
1616 | allowed_to_change_status = PullRequestModel().check_user_change_status( | |
|
1617 | pull_request, self._rhodecode_user) and not is_draft | |
|
1618 | ||
|
1619 | if status and allowed_to_change_status: | |
|
1620 | message = (_('Status change %(transition_icon)s %(status)s') | |
|
1621 | % {'transition_icon': '>', | |
|
1622 | 'status': ChangesetStatus.get_status_lbl(status)}) | |
|
1623 | text = text or message | |
|
1624 | ||
|
1625 | comment = CommentsModel().create( | |
|
1626 | text=text, | |
|
1627 | repo=self.db_repo.repo_id, | |
|
1628 | user=self._rhodecode_user.user_id, | |
|
1629 | pull_request=pull_request, | |
|
1630 | f_path=f_path, | |
|
1631 | line_no=line_no, | |
|
1632 | status_change=(ChangesetStatus.get_status_lbl(status) | |
|
1633 | if status and allowed_to_change_status else None), | |
|
1634 | status_change_type=(status | |
|
1635 | if status and allowed_to_change_status else None), | |
|
1636 | comment_type=comment_type, | |
|
1637 | is_draft=is_draft, | |
|
1638 | resolves_comment_id=resolves_comment_id, | |
|
1639 | auth_user=self._rhodecode_user, | |
|
1640 | send_email=not is_draft, # skip notification for draft comments | |
|
1641 | ) | |
|
1642 | is_inline = comment.is_inline | |
|
1643 | ||
|
1644 | if allowed_to_change_status: | |
|
1645 | # calculate old status before we change it | |
|
1646 | old_calculated_status = pull_request.calculated_review_status() | |
|
1647 | ||
|
1648 | # get status if set ! | |
|
1649 | if status: | |
|
1650 | ChangesetStatusModel().set_status( | |
|
1651 | self.db_repo.repo_id, | |
|
1652 | status, | |
|
1653 | self._rhodecode_user.user_id, | |
|
1654 | comment, | |
|
1655 | pull_request=pull_request | |
|
1656 | ) | |
|
1657 | ||
|
1658 | Session().flush() | |
|
1659 | # this is somehow required to get access to some relationship | |
|
1660 | # loaded on comment | |
|
1661 | Session().refresh(comment) | |
|
1662 | ||
|
1663 | # skip notifications for drafts | |
|
1664 | if not is_draft: | |
|
1665 | PullRequestModel().trigger_pull_request_hook( | |
|
1666 | pull_request, self._rhodecode_user, 'comment', | |
|
1667 | data={'comment': comment}) | |
|
1668 | ||
|
1669 | # we now calculate the status of pull request, and based on that | |
|
1670 | # calculation we set the commits status | |
|
1671 | calculated_status = pull_request.calculated_review_status() | |
|
1672 | if old_calculated_status != calculated_status: | |
|
1673 | PullRequestModel().trigger_pull_request_hook( | |
|
1674 | pull_request, self._rhodecode_user, 'review_status_change', | |
|
1675 | data={'status': calculated_status}) | |
|
1676 | ||
|
1677 | comment_id = comment.comment_id | |
|
1678 | data[comment_id] = { | |
|
1679 | 'target_id': target_elem_id | |
|
1680 | } | |
|
1681 | Session().flush() | |
|
1682 | ||
|
1683 | c.co = comment | |
|
1684 | c.at_version_num = None | |
|
1685 | c.is_new = True | |
|
1686 | rendered_comment = render( | |
|
1687 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
|
1688 | self._get_template_context(c), self.request) | |
|
1689 | ||
|
1690 | data[comment_id].update(comment.get_dict()) | |
|
1691 | data[comment_id].update({'rendered_text': rendered_comment}) | |
|
1692 | ||
|
1693 | Session().commit() | |
|
1694 | ||
|
1695 | # skip channelstream for draft comments | |
|
1696 | if not all_drafts: | |
|
1697 | comment_broadcast_channel = channelstream.comment_channel( | |
|
1698 | self.db_repo_name, pull_request_obj=pull_request) | |
|
1699 | ||
|
1700 | comment_data = data | |
|
1701 | posted_comment_type = 'inline' if is_inline else 'general' | |
|
1702 | if len(data) == 1: | |
|
1703 | msg = _('posted {} new {} comment').format(len(data), posted_comment_type) | |
|
1704 | else: | |
|
1705 | msg = _('posted {} new {} comments').format(len(data), posted_comment_type) | |
|
1706 | ||
|
1707 | channelstream.comment_channelstream_push( | |
|
1708 | self.request, comment_broadcast_channel, self._rhodecode_user, msg, | |
|
1709 | comment_data=comment_data) | |
|
1710 | ||
|
1711 | return data | |
|
1712 | ||
|
1517 | 1713 | @LoginRequired() |
|
1518 | 1714 | @NotAnonymous() |
|
1519 | 1715 | @HasRepoPermissionAnyDecorator( |
@@ -1525,9 +1721,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1525 | 1721 | def pull_request_comment_create(self): |
|
1526 | 1722 | _ = self.request.translate |
|
1527 | 1723 | |
|
1528 | pull_request = PullRequest.get_or_404( | |
|
1529 | self.request.matchdict['pull_request_id']) | |
|
1530 | pull_request_id = pull_request.pull_request_id | |
|
1724 | pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id']) | |
|
1531 | 1725 | |
|
1532 | 1726 | if pull_request.is_closed(): |
|
1533 | 1727 | log.debug('comment: forbidden because pull request is closed') |
@@ -1539,124 +1733,17 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1539 | 1733 | log.debug('comment: forbidden because pull request is from forbidden repo') |
|
1540 | 1734 | raise HTTPForbidden() |
|
1541 | 1735 | |
|
1542 | c = self.load_default_context() | |
|
1543 | ||
|
1544 |
|
|
|
1545 |
|
|
|
1546 |
|
|
|
1547 |
resolves_comment_id |
|
|
1548 |
close_pull_request |
|
|
1549 | ||
|
1550 | # the logic here should work like following, if we submit close | |
|
1551 | # pr comment, use `close_pull_request_with_comment` function | |
|
1552 | # else handle regular comment logic | |
|
1553 | ||
|
1554 | if close_pull_request: | |
|
1555 | # only owner or admin or person with write permissions | |
|
1556 | allowed_to_close = PullRequestModel().check_user_update( | |
|
1557 | pull_request, self._rhodecode_user) | |
|
1558 | if not allowed_to_close: | |
|
1559 | log.debug('comment: forbidden because not allowed to close ' | |
|
1560 | 'pull request %s', pull_request_id) | |
|
1561 | raise HTTPForbidden() | |
|
1562 | ||
|
1563 | # This also triggers `review_status_change` | |
|
1564 | comment, status = PullRequestModel().close_pull_request_with_comment( | |
|
1565 | pull_request, self._rhodecode_user, self.db_repo, message=text, | |
|
1566 | auth_user=self._rhodecode_user) | |
|
1567 | Session().flush() | |
|
1568 | is_inline = comment.is_inline | |
|
1569 | ||
|
1570 | PullRequestModel().trigger_pull_request_hook( | |
|
1571 | pull_request, self._rhodecode_user, 'comment', | |
|
1572 | data={'comment': comment}) | |
|
1573 | ||
|
1574 | else: | |
|
1575 | # regular comment case, could be inline, or one with status. | |
|
1576 | # for that one we check also permissions | |
|
1577 | ||
|
1578 | allowed_to_change_status = PullRequestModel().check_user_change_status( | |
|
1579 | pull_request, self._rhodecode_user) | |
|
1580 | ||
|
1581 | if status and allowed_to_change_status: | |
|
1582 | message = (_('Status change %(transition_icon)s %(status)s') | |
|
1583 | % {'transition_icon': '>', | |
|
1584 | 'status': ChangesetStatus.get_status_lbl(status)}) | |
|
1585 | text = text or message | |
|
1586 | ||
|
1587 | comment = CommentsModel().create( | |
|
1588 | text=text, | |
|
1589 | repo=self.db_repo.repo_id, | |
|
1590 | user=self._rhodecode_user.user_id, | |
|
1591 | pull_request=pull_request, | |
|
1592 | f_path=self.request.POST.get('f_path'), | |
|
1593 | line_no=self.request.POST.get('line'), | |
|
1594 | status_change=(ChangesetStatus.get_status_lbl(status) | |
|
1595 | if status and allowed_to_change_status else None), | |
|
1596 | status_change_type=(status | |
|
1597 | if status and allowed_to_change_status else None), | |
|
1598 | comment_type=comment_type, | |
|
1599 | resolves_comment_id=resolves_comment_id, | |
|
1600 | auth_user=self._rhodecode_user | |
|
1601 | ) | |
|
1602 | is_inline = comment.is_inline | |
|
1603 | ||
|
1604 | if allowed_to_change_status: | |
|
1605 | # calculate old status before we change it | |
|
1606 | old_calculated_status = pull_request.calculated_review_status() | |
|
1607 | ||
|
1608 | # get status if set ! | |
|
1609 | if status: | |
|
1610 | ChangesetStatusModel().set_status( | |
|
1611 | self.db_repo.repo_id, | |
|
1612 | status, | |
|
1613 | self._rhodecode_user.user_id, | |
|
1614 | comment, | |
|
1615 | pull_request=pull_request | |
|
1616 | ) | |
|
1617 | ||
|
1618 | Session().flush() | |
|
1619 | # this is somehow required to get access to some relationship | |
|
1620 | # loaded on comment | |
|
1621 | Session().refresh(comment) | |
|
1622 | ||
|
1623 | PullRequestModel().trigger_pull_request_hook( | |
|
1624 | pull_request, self._rhodecode_user, 'comment', | |
|
1625 | data={'comment': comment}) | |
|
1626 | ||
|
1627 | # we now calculate the status of pull request, and based on that | |
|
1628 | # calculation we set the commits status | |
|
1629 | calculated_status = pull_request.calculated_review_status() | |
|
1630 | if old_calculated_status != calculated_status: | |
|
1631 | PullRequestModel().trigger_pull_request_hook( | |
|
1632 | pull_request, self._rhodecode_user, 'review_status_change', | |
|
1633 | data={'status': calculated_status}) | |
|
1634 | ||
|
1635 | Session().commit() | |
|
1636 | ||
|
1637 | data = { | |
|
1638 | 'target_id': h.safeid(h.safe_unicode( | |
|
1639 | self.request.POST.get('f_path'))), | |
|
1736 | comment_data = { | |
|
1737 | 'comment_type': self.request.POST.get('comment_type'), | |
|
1738 | 'text': self.request.POST.get('text'), | |
|
1739 | 'status': self.request.POST.get('changeset_status', None), | |
|
1740 | 'is_draft': self.request.POST.get('draft'), | |
|
1741 | 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None), | |
|
1742 | 'close_pull_request': self.request.POST.get('close_pull_request'), | |
|
1743 | 'f_path': self.request.POST.get('f_path'), | |
|
1744 | 'line': self.request.POST.get('line'), | |
|
1640 | 1745 | } |
|
1641 | if comment: | |
|
1642 | c.co = comment | |
|
1643 | c.at_version_num = None | |
|
1644 | rendered_comment = render( | |
|
1645 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
|
1646 | self._get_template_context(c), self.request) | |
|
1647 | ||
|
1648 | data.update(comment.get_dict()) | |
|
1649 | data.update({'rendered_text': rendered_comment}) | |
|
1650 | ||
|
1651 | comment_broadcast_channel = channelstream.comment_channel( | |
|
1652 | self.db_repo_name, pull_request_obj=pull_request) | |
|
1653 | ||
|
1654 | comment_data = data | |
|
1655 | comment_type = 'inline' if is_inline else 'general' | |
|
1656 | channelstream.comment_channelstream_push( | |
|
1657 | self.request, comment_broadcast_channel, self._rhodecode_user, | |
|
1658 | _('posted a new {} comment').format(comment_type), | |
|
1659 | comment_data=comment_data) | |
|
1746 | data = self._pull_request_comments_create(pull_request, [comment_data]) | |
|
1660 | 1747 | |
|
1661 | 1748 | return data |
|
1662 | 1749 | |
@@ -1741,11 +1828,6 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1741 | 1828 | log.debug('comment: forbidden because pull request is closed') |
|
1742 | 1829 | raise HTTPForbidden() |
|
1743 | 1830 | |
|
1744 | if not comment: | |
|
1745 | log.debug('Comment with id:%s not found, skipping', comment_id) | |
|
1746 | # comment already deleted in another call probably | |
|
1747 | return True | |
|
1748 | ||
|
1749 | 1831 | if comment.pull_request.is_closed(): |
|
1750 | 1832 | # don't allow deleting comments on closed pull request |
|
1751 | 1833 | raise HTTPForbidden() |
@@ -1796,10 +1878,10 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1796 | 1878 | raise HTTPNotFound() |
|
1797 | 1879 | |
|
1798 | 1880 | Session().commit() |
|
1799 | ||
|
1800 | PullRequestModel().trigger_pull_request_hook( | |
|
1801 | pull_request, self._rhodecode_user, 'comment_edit', | |
|
1802 | data={'comment': comment}) | |
|
1881 | if not comment.draft: | |
|
1882 | PullRequestModel().trigger_pull_request_hook( | |
|
1883 | pull_request, self._rhodecode_user, 'comment_edit', | |
|
1884 | data={'comment': comment}) | |
|
1803 | 1885 | |
|
1804 | 1886 | return { |
|
1805 | 1887 | 'comment_history_id': comment_history.comment_history_id, |
@@ -215,9 +215,10 b' class RhodeCodeAuthPluginBase(object):' | |||
|
215 | 215 | """ |
|
216 | 216 | return self._plugin_id |
|
217 | 217 | |
|
218 | def get_display_name(self): | |
|
218 | def get_display_name(self, load_from_settings=False): | |
|
219 | 219 | """ |
|
220 | 220 | Returns a translation string for displaying purposes. |
|
221 | if load_from_settings is set, plugin settings can override the display name | |
|
221 | 222 | """ |
|
222 | 223 | raise NotImplementedError('Not implemented in base class') |
|
223 | 224 |
@@ -213,7 +213,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||
|
213 | 213 | def get_settings_schema(self): |
|
214 | 214 | return CrowdSettingsSchema() |
|
215 | 215 | |
|
216 | def get_display_name(self): | |
|
216 | def get_display_name(self, load_from_settings=False): | |
|
217 | 217 | return _('CROWD') |
|
218 | 218 | |
|
219 | 219 | @classmethod |
@@ -95,7 +95,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||
|
95 | 95 | route_name='auth_home', |
|
96 | 96 | context=HeadersAuthnResource) |
|
97 | 97 | |
|
98 | def get_display_name(self): | |
|
98 | def get_display_name(self, load_from_settings=False): | |
|
99 | 99 | return _('Headers') |
|
100 | 100 | |
|
101 | 101 | def get_settings_schema(self): |
@@ -89,7 +89,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||
|
89 | 89 | def get_settings_schema(self): |
|
90 | 90 | return JasigCasSettingsSchema() |
|
91 | 91 | |
|
92 | def get_display_name(self): | |
|
92 | def get_display_name(self, load_from_settings=False): | |
|
93 | 93 | return _('Jasig-CAS') |
|
94 | 94 | |
|
95 | 95 | @hybrid_property |
@@ -421,7 +421,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||
|
421 | 421 | def get_settings_schema(self): |
|
422 | 422 | return LdapSettingsSchema() |
|
423 | 423 | |
|
424 | def get_display_name(self): | |
|
424 | def get_display_name(self, load_from_settings=False): | |
|
425 | 425 | return _('LDAP') |
|
426 | 426 | |
|
427 | 427 | @classmethod |
@@ -95,7 +95,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter' | |||
|
95 | 95 | route_name='auth_home', |
|
96 | 96 | context=PamAuthnResource) |
|
97 | 97 | |
|
98 | def get_display_name(self): | |
|
98 | def get_display_name(self, load_from_settings=False): | |
|
99 | 99 | return _('PAM') |
|
100 | 100 | |
|
101 | 101 | @classmethod |
@@ -75,7 +75,7 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP' | |||
|
75 | 75 | def get_settings_schema(self): |
|
76 | 76 | return RhodeCodeSettingsSchema() |
|
77 | 77 | |
|
78 | def get_display_name(self): | |
|
78 | def get_display_name(self, load_from_settings=False): | |
|
79 | 79 | return _('RhodeCode Internal') |
|
80 | 80 | |
|
81 | 81 | @classmethod |
@@ -73,7 +73,7 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP' | |||
|
73 | 73 | def get_settings_schema(self): |
|
74 | 74 | return RhodeCodeSettingsSchema() |
|
75 | 75 | |
|
76 | def get_display_name(self): | |
|
76 | def get_display_name(self, load_from_settings=False): | |
|
77 | 77 | return _('Rhodecode Token') |
|
78 | 78 | |
|
79 | 79 | @classmethod |
@@ -53,7 +53,7 b' from rhodecode.lib.utils2 import aslist ' | |||
|
53 | 53 | from rhodecode.lib.exc_tracking import store_exception |
|
54 | 54 | from rhodecode.subscribers import ( |
|
55 | 55 | scan_repositories_if_enabled, write_js_routes_if_enabled, |
|
56 |
write_metadata_if_needed, write_usage_data |
|
|
56 | write_metadata_if_needed, write_usage_data) | |
|
57 | 57 | |
|
58 | 58 | |
|
59 | 59 | log = logging.getLogger(__name__) |
@@ -310,8 +310,6 b' def includeme(config):' | |||
|
310 | 310 | |
|
311 | 311 | # Add subscribers. |
|
312 | 312 | if load_all: |
|
313 | config.add_subscriber(inject_app_settings, | |
|
314 | pyramid.events.ApplicationCreated) | |
|
315 | 313 | config.add_subscriber(scan_repositories_if_enabled, |
|
316 | 314 | pyramid.events.ApplicationCreated) |
|
317 | 315 | config.add_subscriber(write_metadata_if_needed, |
@@ -67,7 +67,7 b' markdown_tags = [' | |||
|
67 | 67 | |
|
68 | 68 | markdown_attrs = { |
|
69 | 69 | "*": ["class", "style", "align"], |
|
70 | "img": ["src", "alt", "title"], | |
|
70 | "img": ["src", "alt", "title", "width", "height", "hspace", "align"], | |
|
71 | 71 | "a": ["href", "alt", "title", "name", "data-hovercard-alt", "data-hovercard-url"], |
|
72 | 72 | "abbr": ["title"], |
|
73 | 73 | "acronym": ["title"], |
@@ -339,13 +339,12 b' def comment_channelstream_push(request, ' | |||
|
339 | 339 | |
|
340 | 340 | comment_data = kwargs.pop('comment_data', {}) |
|
341 | 341 | user_data = kwargs.pop('user_data', {}) |
|
342 |
comment_id = comment_data. |
|
|
342 | comment_id = comment_data.keys()[0] if comment_data else '' | |
|
343 | 343 | |
|
344 |
message = '<strong>{}</strong> {} #{} |
|
|
344 | message = '<strong>{}</strong> {} #{}'.format( | |
|
345 | 345 | user.username, |
|
346 | 346 | msg, |
|
347 | 347 | comment_id, |
|
348 | _reload_link(_('Reload page to see new comments')), | |
|
349 | 348 | ) |
|
350 | 349 | |
|
351 | 350 | message_obj = { |
@@ -1148,7 +1148,7 b' class DiffLimitExceeded(Exception):' | |||
|
1148 | 1148 | |
|
1149 | 1149 | # NOTE(marcink): if diffs.mako change, probably this |
|
1150 | 1150 | # needs a bump to next version |
|
1151 |
CURRENT_DIFF_VERSION = 'v |
|
|
1151 | CURRENT_DIFF_VERSION = 'v5' | |
|
1152 | 1152 | |
|
1153 | 1153 | |
|
1154 | 1154 | def _cleanup_cache_file(cached_diff_file): |
@@ -110,7 +110,7 b' def _store_exception(exc_id, exc_type_na' | |||
|
110 | 110 | |
|
111 | 111 | mail_server = app.CONFIG.get('smtp_server') or None |
|
112 | 112 | send_email = send_email and mail_server |
|
113 | if send_email: | |
|
113 | if send_email and request: | |
|
114 | 114 | try: |
|
115 | 115 | send_exc_email(request, exc_id, exc_type_name) |
|
116 | 116 | except Exception: |
@@ -18,17 +18,85 b'' | |||
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | import re | |
|
21 | 22 | import markdown |
|
23 | import xml.etree.ElementTree as etree | |
|
22 | 24 | |
|
23 | 25 | from markdown.extensions import Extension |
|
24 | 26 | from markdown.extensions.fenced_code import FencedCodeExtension |
|
25 | 27 | from markdown.extensions.smart_strong import SmartEmphasisExtension |
|
26 | 28 | from markdown.extensions.tables import TableExtension |
|
27 |
from markdown. |
|
|
29 | from markdown.inlinepatterns import Pattern | |
|
28 | 30 | |
|
29 | 31 | import gfm |
|
30 | 32 | |
|
31 | 33 | |
|
34 | class InlineProcessor(Pattern): | |
|
35 | """ | |
|
36 | Base class that inline patterns subclass. | |
|
37 | This is the newer style inline processor that uses a more | |
|
38 | efficient and flexible search approach. | |
|
39 | """ | |
|
40 | ||
|
41 | def __init__(self, pattern, md=None): | |
|
42 | """ | |
|
43 | Create an instant of an inline pattern. | |
|
44 | Keyword arguments: | |
|
45 | * pattern: A regular expression that matches a pattern | |
|
46 | """ | |
|
47 | self.pattern = pattern | |
|
48 | self.compiled_re = re.compile(pattern, re.DOTALL | re.UNICODE) | |
|
49 | ||
|
50 | # Api for Markdown to pass safe_mode into instance | |
|
51 | self.safe_mode = False | |
|
52 | self.md = md | |
|
53 | ||
|
54 | def handleMatch(self, m, data): | |
|
55 | """Return a ElementTree element from the given match and the | |
|
56 | start and end index of the matched text. | |
|
57 | If `start` and/or `end` are returned as `None`, it will be | |
|
58 | assumed that the processor did not find a valid region of text. | |
|
59 | Subclasses should override this method. | |
|
60 | Keyword arguments: | |
|
61 | * m: A re match object containing a match of the pattern. | |
|
62 | * data: The buffer current under analysis | |
|
63 | Returns: | |
|
64 | * el: The ElementTree element, text or None. | |
|
65 | * start: The start of the region that has been matched or None. | |
|
66 | * end: The end of the region that has been matched or None. | |
|
67 | """ | |
|
68 | pass # pragma: no cover | |
|
69 | ||
|
70 | ||
|
71 | class SimpleTagInlineProcessor(InlineProcessor): | |
|
72 | """ | |
|
73 | Return element of type `tag` with a text attribute of group(2) | |
|
74 | of a Pattern. | |
|
75 | """ | |
|
76 | def __init__(self, pattern, tag): | |
|
77 | InlineProcessor.__init__(self, pattern) | |
|
78 | self.tag = tag | |
|
79 | ||
|
80 | def handleMatch(self, m, data): # pragma: no cover | |
|
81 | el = etree.Element(self.tag) | |
|
82 | el.text = m.group(2) | |
|
83 | return el, m.start(0), m.end(0) | |
|
84 | ||
|
85 | ||
|
86 | class SubstituteTagInlineProcessor(SimpleTagInlineProcessor): | |
|
87 | """ Return an element of type `tag` with no children. """ | |
|
88 | def handleMatch(self, m, data): | |
|
89 | return etree.Element(self.tag), m.start(0), m.end(0) | |
|
90 | ||
|
91 | ||
|
92 | class Nl2BrExtension(Extension): | |
|
93 | BR_RE = r'\n' | |
|
94 | ||
|
95 | def extendMarkdown(self, md, md_globals): | |
|
96 | br_tag = SubstituteTagInlineProcessor(self.BR_RE, 'br') | |
|
97 | md.inlinePatterns.add('nl', br_tag, '_end') | |
|
98 | ||
|
99 | ||
|
32 | 100 | class GithubFlavoredMarkdownExtension(Extension): |
|
33 | 101 | """ |
|
34 | 102 | An extension that is as compatible as possible with GitHub-flavored |
@@ -51,6 +119,7 b' class GithubFlavoredMarkdownExtension(Ex' | |||
|
51 | 119 | |
|
52 | 120 | def extendMarkdown(self, md, md_globals): |
|
53 | 121 | # Built-in extensions |
|
122 | Nl2BrExtension().extendMarkdown(md, md_globals) | |
|
54 | 123 | FencedCodeExtension().extendMarkdown(md, md_globals) |
|
55 | 124 | SmartEmphasisExtension().extendMarkdown(md, md_globals) |
|
56 | 125 | TableExtension().extendMarkdown(md, md_globals) |
@@ -68,7 +137,6 b' class GithubFlavoredMarkdownExtension(Ex' | |||
|
68 | 137 | gfm.TaskListExtension([ |
|
69 | 138 | ('list_attrs', {'class': 'checkbox'}) |
|
70 | 139 | ]).extendMarkdown(md, md_globals) |
|
71 | Nl2BrExtension().extendMarkdown(md, md_globals) | |
|
72 | 140 | |
|
73 | 141 | |
|
74 | 142 | # Global Vars |
@@ -74,7 +74,11 b' def configure_dogpile_cache(settings):' | |||
|
74 | 74 | |
|
75 | 75 | new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name)) |
|
76 | 76 | new_region.function_key_generator = backend_key_generator(new_region.actual_backend) |
|
77 | log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__) | |
|
77 | if log.isEnabledFor(logging.DEBUG): | |
|
78 | region_args = dict(backend=new_region.actual_backend.__class__, | |
|
79 | region_invalidator=new_region.region_invalidator.__class__) | |
|
80 | log.debug('dogpile: registering a new region `%s` %s', region_name, region_args) | |
|
81 | ||
|
78 | 82 | region_meta.dogpile_cache_regions[region_name] = new_region |
|
79 | 83 | |
|
80 | 84 |
@@ -915,8 +915,9 b' class BaseCommit(object):' | |||
|
915 | 915 | list of parent commits |
|
916 | 916 | |
|
917 | 917 | """ |
|
918 | repository = None | |
|
919 | branch = None | |
|
918 | 920 | |
|
919 | branch = None | |
|
920 | 921 | """ |
|
921 | 922 | Depending on the backend this should be set to the branch name of the |
|
922 | 923 | commit. Backends not supporting branches on commits should leave this |
@@ -1192,13 +1193,14 b' class BaseCommit(object):' | |||
|
1192 | 1193 | return None |
|
1193 | 1194 | |
|
1194 | 1195 | def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None, |
|
1195 |
|
|
|
1196 | archive_dir_name=None, write_metadata=False, mtime=None, | |
|
1197 | archive_at_path='/'): | |
|
1196 | 1198 | """ |
|
1197 | 1199 | Creates an archive containing the contents of the repository. |
|
1198 | 1200 | |
|
1199 | 1201 | :param archive_dest_path: path to the file which to create the archive. |
|
1200 | 1202 | :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``. |
|
1201 |
:param |
|
|
1203 | :param archive_dir_name: name of root directory in archive. | |
|
1202 | 1204 | Default is repository name and commit's short_id joined with dash: |
|
1203 | 1205 | ``"{repo_name}-{short_id}"``. |
|
1204 | 1206 | :param write_metadata: write a metadata file into archive. |
@@ -1214,43 +1216,26 b' class BaseCommit(object):' | |||
|
1214 | 1216 | 'Archive kind (%s) not supported use one of %s' % |
|
1215 | 1217 | (kind, allowed_kinds)) |
|
1216 | 1218 | |
|
1217 |
|
|
|
1218 | ||
|
1219 | archive_dir_name = self._validate_archive_prefix(archive_dir_name) | |
|
1219 | 1220 | mtime = mtime is not None or time.mktime(self.date.timetuple()) |
|
1220 | ||
|
1221 | file_info = [] | |
|
1222 | cur_rev = self.repository.get_commit(commit_id=self.raw_id) | |
|
1223 | for _r, _d, files in cur_rev.walk(archive_at_path): | |
|
1224 | for f in files: | |
|
1225 | f_path = os.path.join(prefix, f.path) | |
|
1226 | file_info.append( | |
|
1227 | (f_path, f.mode, f.is_link(), f.raw_bytes)) | |
|
1221 | commit_id = self.raw_id | |
|
1228 | 1222 | |
|
1229 | if write_metadata: | |
|
1230 | metadata = [ | |
|
1231 | ('repo_name', self.repository.name), | |
|
1232 | ('commit_id', self.raw_id), | |
|
1233 | ('mtime', mtime), | |
|
1234 | ('branch', self.branch), | |
|
1235 | ('tags', ','.join(self.tags)), | |
|
1236 | ] | |
|
1237 | meta = ["%s:%s" % (f_name, value) for f_name, value in metadata] | |
|
1238 | file_info.append(('.archival.txt', 0o644, False, '\n'.join(meta))) | |
|
1223 | return self.repository._remote.archive_repo( | |
|
1224 | archive_dest_path, kind, mtime, archive_at_path, | |
|
1225 | archive_dir_name, commit_id) | |
|
1239 | 1226 | |
|
1240 | connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind) | |
|
1241 | ||
|
1242 | def _validate_archive_prefix(self, prefix): | |
|
1243 | if prefix is None: | |
|
1244 | prefix = self._ARCHIVE_PREFIX_TEMPLATE.format( | |
|
1227 | def _validate_archive_prefix(self, archive_dir_name): | |
|
1228 | if archive_dir_name is None: | |
|
1229 | archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format( | |
|
1245 | 1230 | repo_name=safe_str(self.repository.name), |
|
1246 | 1231 | short_id=self.short_id) |
|
1247 |
elif not isinstance( |
|
|
1248 |
raise ValueError("prefix not a bytes object: %s" % repr( |
|
|
1249 |
elif |
|
|
1232 | elif not isinstance(archive_dir_name, str): | |
|
1233 | raise ValueError("prefix not a bytes object: %s" % repr(archive_dir_name)) | |
|
1234 | elif archive_dir_name.startswith('/'): | |
|
1250 | 1235 | raise VCSError("Prefix cannot start with leading slash") |
|
1251 |
elif |
|
|
1236 | elif archive_dir_name.strip() == '': | |
|
1252 | 1237 | raise VCSError("Prefix cannot be empty") |
|
1253 |
return |
|
|
1238 | return archive_dir_name | |
|
1254 | 1239 | |
|
1255 | 1240 | @LazyProperty |
|
1256 | 1241 | def root(self): |
@@ -214,16 +214,19 b' def map_vcs_exceptions(func):' | |||
|
214 | 214 | # to translate them to the proper exception class in the vcs |
|
215 | 215 | # client layer. |
|
216 | 216 | kind = getattr(e, '_vcs_kind', None) |
|
217 | exc_name = getattr(e, '_vcs_server_org_exc_name', None) | |
|
217 | 218 | |
|
218 | 219 | if kind: |
|
219 | 220 | if any(e.args): |
|
220 | args = e.args | |
|
221 | args = [a for a in e.args] | |
|
222 | args[0] = '{}:'.format(exc_name) # prefix first arg with org exc name | |
|
221 | 223 | else: |
|
222 |
args = [__traceback_info__ or ' |
|
|
224 | args = [__traceback_info__ or '{}: UnhandledException'.format(exc_name)] | |
|
223 | 225 | if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']: |
|
224 | 226 | # for other than unhandled errors also log the traceback |
|
225 | 227 | # can be useful for debugging |
|
226 | 228 | log.error(__traceback_info__) |
|
229 | ||
|
227 | 230 | raise _EXCEPTION_MAP[kind](*args) |
|
228 | 231 | else: |
|
229 | 232 | raise |
@@ -37,6 +37,7 b' from rhodecode.lib.exceptions import Com' | |||
|
37 | 37 | from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int |
|
38 | 38 | from rhodecode.model import BaseModel |
|
39 | 39 | from rhodecode.model.db import ( |
|
40 | false, true, | |
|
40 | 41 | ChangesetComment, |
|
41 | 42 | User, |
|
42 | 43 | Notification, |
@@ -160,7 +161,7 b' class CommentsModel(BaseModel):' | |||
|
160 | 161 | |
|
161 | 162 | return todos |
|
162 | 163 | |
|
163 | def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True): | |
|
164 | def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True): | |
|
164 | 165 | |
|
165 | 166 | todos = Session().query(ChangesetComment) \ |
|
166 | 167 | .filter(ChangesetComment.pull_request == pull_request) \ |
@@ -168,6 +169,9 b' class CommentsModel(BaseModel):' | |||
|
168 | 169 | .filter(ChangesetComment.comment_type |
|
169 | 170 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
170 | 171 | |
|
172 | if not include_drafts: | |
|
173 | todos = todos.filter(ChangesetComment.draft == false()) | |
|
174 | ||
|
171 | 175 | if not show_outdated: |
|
172 | 176 | todos = todos.filter( |
|
173 | 177 | coalesce(ChangesetComment.display_state, '') != |
@@ -177,7 +181,7 b' class CommentsModel(BaseModel):' | |||
|
177 | 181 | |
|
178 | 182 | return todos |
|
179 | 183 | |
|
180 | def get_pull_request_resolved_todos(self, pull_request, show_outdated=True): | |
|
184 | def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True): | |
|
181 | 185 | |
|
182 | 186 | todos = Session().query(ChangesetComment) \ |
|
183 | 187 | .filter(ChangesetComment.pull_request == pull_request) \ |
@@ -185,6 +189,9 b' class CommentsModel(BaseModel):' | |||
|
185 | 189 | .filter(ChangesetComment.comment_type |
|
186 | 190 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
187 | 191 | |
|
192 | if not include_drafts: | |
|
193 | todos = todos.filter(ChangesetComment.draft == false()) | |
|
194 | ||
|
188 | 195 | if not show_outdated: |
|
189 | 196 | todos = todos.filter( |
|
190 | 197 | coalesce(ChangesetComment.display_state, '') != |
@@ -194,7 +201,14 b' class CommentsModel(BaseModel):' | |||
|
194 | 201 | |
|
195 | 202 | return todos |
|
196 | 203 | |
|
197 | def get_commit_unresolved_todos(self, commit_id, show_outdated=True): | |
|
204 | def get_pull_request_drafts(self, user_id, pull_request): | |
|
205 | drafts = Session().query(ChangesetComment) \ | |
|
206 | .filter(ChangesetComment.pull_request == pull_request) \ | |
|
207 | .filter(ChangesetComment.user_id == user_id) \ | |
|
208 | .filter(ChangesetComment.draft == true()) | |
|
209 | return drafts.all() | |
|
210 | ||
|
211 | def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True): | |
|
198 | 212 | |
|
199 | 213 | todos = Session().query(ChangesetComment) \ |
|
200 | 214 | .filter(ChangesetComment.revision == commit_id) \ |
@@ -202,6 +216,9 b' class CommentsModel(BaseModel):' | |||
|
202 | 216 | .filter(ChangesetComment.comment_type |
|
203 | 217 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
204 | 218 | |
|
219 | if not include_drafts: | |
|
220 | todos = todos.filter(ChangesetComment.draft == false()) | |
|
221 | ||
|
205 | 222 | if not show_outdated: |
|
206 | 223 | todos = todos.filter( |
|
207 | 224 | coalesce(ChangesetComment.display_state, '') != |
@@ -211,7 +228,7 b' class CommentsModel(BaseModel):' | |||
|
211 | 228 | |
|
212 | 229 | return todos |
|
213 | 230 | |
|
214 | def get_commit_resolved_todos(self, commit_id, show_outdated=True): | |
|
231 | def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True): | |
|
215 | 232 | |
|
216 | 233 | todos = Session().query(ChangesetComment) \ |
|
217 | 234 | .filter(ChangesetComment.revision == commit_id) \ |
@@ -219,6 +236,9 b' class CommentsModel(BaseModel):' | |||
|
219 | 236 | .filter(ChangesetComment.comment_type |
|
220 | 237 | == ChangesetComment.COMMENT_TYPE_TODO) |
|
221 | 238 | |
|
239 | if not include_drafts: | |
|
240 | todos = todos.filter(ChangesetComment.draft == false()) | |
|
241 | ||
|
222 | 242 | if not show_outdated: |
|
223 | 243 | todos = todos.filter( |
|
224 | 244 | coalesce(ChangesetComment.display_state, '') != |
@@ -228,11 +248,15 b' class CommentsModel(BaseModel):' | |||
|
228 | 248 | |
|
229 | 249 | return todos |
|
230 | 250 | |
|
231 | def get_commit_inline_comments(self, commit_id): | |
|
251 | def get_commit_inline_comments(self, commit_id, include_drafts=True): | |
|
232 | 252 | inline_comments = Session().query(ChangesetComment) \ |
|
233 | 253 | .filter(ChangesetComment.line_no != None) \ |
|
234 | 254 | .filter(ChangesetComment.f_path != None) \ |
|
235 | 255 | .filter(ChangesetComment.revision == commit_id) |
|
256 | ||
|
257 | if not include_drafts: | |
|
258 | inline_comments = inline_comments.filter(ChangesetComment.draft == false()) | |
|
259 | ||
|
236 | 260 | inline_comments = inline_comments.all() |
|
237 | 261 | return inline_comments |
|
238 | 262 | |
@@ -245,7 +269,7 b' class CommentsModel(BaseModel):' | |||
|
245 | 269 | |
|
246 | 270 | def create(self, text, repo, user, commit_id=None, pull_request=None, |
|
247 | 271 | f_path=None, line_no=None, status_change=None, |
|
248 | status_change_type=None, comment_type=None, | |
|
272 | status_change_type=None, comment_type=None, is_draft=False, | |
|
249 | 273 | resolves_comment_id=None, closing_pr=False, send_email=True, |
|
250 | 274 | renderer=None, auth_user=None, extra_recipients=None): |
|
251 | 275 | """ |
@@ -262,6 +286,7 b' class CommentsModel(BaseModel):' | |||
|
262 | 286 | :param line_no: |
|
263 | 287 | :param status_change: Label for status change |
|
264 | 288 | :param comment_type: Type of comment |
|
289 | :param is_draft: is comment a draft only | |
|
265 | 290 | :param resolves_comment_id: id of comment which this one will resolve |
|
266 | 291 | :param status_change_type: type of status change |
|
267 | 292 | :param closing_pr: |
@@ -288,6 +313,7 b' class CommentsModel(BaseModel):' | |||
|
288 | 313 | validated_kwargs = schema.deserialize(dict( |
|
289 | 314 | comment_body=text, |
|
290 | 315 | comment_type=comment_type, |
|
316 | is_draft=is_draft, | |
|
291 | 317 | comment_file=f_path, |
|
292 | 318 | comment_line=line_no, |
|
293 | 319 | renderer_type=renderer, |
@@ -296,6 +322,7 b' class CommentsModel(BaseModel):' | |||
|
296 | 322 | repo=repo.repo_id, |
|
297 | 323 | user=user.user_id, |
|
298 | 324 | )) |
|
325 | is_draft = validated_kwargs['is_draft'] | |
|
299 | 326 | |
|
300 | 327 | comment = ChangesetComment() |
|
301 | 328 | comment.renderer = validated_kwargs['renderer_type'] |
@@ -303,6 +330,7 b' class CommentsModel(BaseModel):' | |||
|
303 | 330 | comment.f_path = validated_kwargs['comment_file'] |
|
304 | 331 | comment.line_no = validated_kwargs['comment_line'] |
|
305 | 332 | comment.comment_type = validated_kwargs['comment_type'] |
|
333 | comment.draft = is_draft | |
|
306 | 334 | |
|
307 | 335 | comment.repo = repo |
|
308 | 336 | comment.author = user |
@@ -438,9 +466,6 b' class CommentsModel(BaseModel):' | |||
|
438 | 466 | |
|
439 | 467 | if send_email: |
|
440 | 468 | recipients += [self._get_user(u) for u in (extra_recipients or [])] |
|
441 | # pre-generate the subject for notification itself | |
|
442 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( | |
|
443 | notification_type, **kwargs) | |
|
444 | 469 | |
|
445 | 470 | mention_recipients = set( |
|
446 | 471 | self._extract_mentions(text)).difference(recipients) |
@@ -448,8 +473,8 b' class CommentsModel(BaseModel):' | |||
|
448 | 473 | # create notification objects, and emails |
|
449 | 474 | NotificationModel().create( |
|
450 | 475 | created_by=user, |
|
451 |
notification_subject= |
|
|
452 |
notification_body= |
|
|
476 | notification_subject='', # Filled in based on the notification_type | |
|
477 | notification_body='', # Filled in based on the notification_type | |
|
453 | 478 | notification_type=notification_type, |
|
454 | 479 | recipients=recipients, |
|
455 | 480 | mention_recipients=mention_recipients, |
@@ -462,10 +487,11 b' class CommentsModel(BaseModel):' | |||
|
462 | 487 | else: |
|
463 | 488 | action = 'repo.commit.comment.create' |
|
464 | 489 | |
|
465 | comment_data = comment.get_api_data() | |
|
490 | if not is_draft: | |
|
491 | comment_data = comment.get_api_data() | |
|
466 | 492 | |
|
467 | self._log_audit_action( | |
|
468 | action, {'data': comment_data}, auth_user, comment) | |
|
493 | self._log_audit_action( | |
|
494 | action, {'data': comment_data}, auth_user, comment) | |
|
469 | 495 | |
|
470 | 496 | return comment |
|
471 | 497 | |
@@ -541,7 +567,8 b' class CommentsModel(BaseModel):' | |||
|
541 | 567 | |
|
542 | 568 | return comment |
|
543 | 569 | |
|
544 |
def get_all_comments(self, repo_id, revision=None, pull_request=None, |
|
|
570 | def get_all_comments(self, repo_id, revision=None, pull_request=None, | |
|
571 | include_drafts=True, count_only=False): | |
|
545 | 572 | q = ChangesetComment.query()\ |
|
546 | 573 | .filter(ChangesetComment.repo_id == repo_id) |
|
547 | 574 | if revision: |
@@ -551,6 +578,8 b' class CommentsModel(BaseModel):' | |||
|
551 | 578 | q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id) |
|
552 | 579 | else: |
|
553 | 580 | raise Exception('Please specify commit or pull_request') |
|
581 | if not include_drafts: | |
|
582 | q = q.filter(ChangesetComment.draft == false()) | |
|
554 | 583 | q = q.order_by(ChangesetComment.created_on) |
|
555 | 584 | if count_only: |
|
556 | 585 | return q.count() |
@@ -697,7 +726,8 b' class CommentsModel(BaseModel):' | |||
|
697 | 726 | path=comment.f_path, diff_line=diff_line) |
|
698 | 727 | except (diffs.LineNotInDiffException, |
|
699 | 728 | diffs.FileNotInDiffException): |
|
700 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |
|
729 | if not comment.draft: | |
|
730 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |
|
701 | 731 | return |
|
702 | 732 | |
|
703 | 733 | if old_context == new_context: |
@@ -707,14 +737,15 b' class CommentsModel(BaseModel):' | |||
|
707 | 737 | new_diff_lines = new_diff_proc.find_context( |
|
708 | 738 | path=comment.f_path, context=old_context, |
|
709 | 739 | offset=self.DIFF_CONTEXT_BEFORE) |
|
710 | if not new_diff_lines: | |
|
740 | if not new_diff_lines and not comment.draft: | |
|
711 | 741 | comment.display_state = ChangesetComment.COMMENT_OUTDATED |
|
712 | 742 | else: |
|
713 | 743 | new_diff_line = self._choose_closest_diff_line( |
|
714 | 744 | diff_line, new_diff_lines) |
|
715 | 745 | comment.line_no = _diff_to_comment_line_number(new_diff_line) |
|
716 | 746 | else: |
|
717 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |
|
747 | if not comment.draft: | |
|
748 | comment.display_state = ChangesetComment.COMMENT_OUTDATED | |
|
718 | 749 | |
|
719 | 750 | def _should_relocate_diff_line(self, diff_line): |
|
720 | 751 | """ |
@@ -3767,6 +3767,7 b' class ChangesetComment(Base, BaseModel):' | |||
|
3767 | 3767 | renderer = Column('renderer', Unicode(64), nullable=True) |
|
3768 | 3768 | display_state = Column('display_state', Unicode(128), nullable=True) |
|
3769 | 3769 | immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE) |
|
3770 | draft = Column('draft', Boolean(), nullable=True, default=False) | |
|
3770 | 3771 | |
|
3771 | 3772 | comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) |
|
3772 | 3773 | resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) |
@@ -5057,8 +5058,14 b' class RepoReviewRule(Base, BaseModel):' | |||
|
5057 | 5058 | _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob |
|
5058 | 5059 | |
|
5059 | 5060 | use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False) |
|
5060 | forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False) | |
|
5061 | forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False) | |
|
5061 | ||
|
5062 | # Legacy fields, just for backward compat | |
|
5063 | _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False) | |
|
5064 | _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False) | |
|
5065 | ||
|
5066 | pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
5067 | commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True) | |
|
5068 | ||
|
5062 | 5069 | forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False) |
|
5063 | 5070 | |
|
5064 | 5071 | rule_users = relationship('RepoReviewRuleUser') |
@@ -5094,6 +5101,22 b' class RepoReviewRule(Base, BaseModel):' | |||
|
5094 | 5101 | self._validate_pattern(value) |
|
5095 | 5102 | self._file_pattern = value or '*' |
|
5096 | 5103 | |
|
5104 | @hybrid_property | |
|
5105 | def forbid_pr_author_to_review(self): | |
|
5106 | return self.pr_author == 'forbid_pr_author' | |
|
5107 | ||
|
5108 | @hybrid_property | |
|
5109 | def include_pr_author_to_review(self): | |
|
5110 | return self.pr_author == 'include_pr_author' | |
|
5111 | ||
|
5112 | @hybrid_property | |
|
5113 | def forbid_commit_author_to_review(self): | |
|
5114 | return self.commit_author == 'forbid_commit_author' | |
|
5115 | ||
|
5116 | @hybrid_property | |
|
5117 | def include_commit_author_to_review(self): | |
|
5118 | return self.commit_author == 'include_commit_author' | |
|
5119 | ||
|
5097 | 5120 | def matches(self, source_branch, target_branch, files_changed): |
|
5098 | 5121 | """ |
|
5099 | 5122 | Check if this review rule matches a branch/files in a pull request |
@@ -55,7 +55,7 b' class NotificationModel(BaseModel):' | |||
|
55 | 55 | ' of Notification got %s' % type(notification)) |
|
56 | 56 | |
|
57 | 57 | def create( |
|
58 | self, created_by, notification_subject, notification_body, | |
|
58 | self, created_by, notification_subject='', notification_body='', | |
|
59 | 59 | notification_type=Notification.TYPE_MESSAGE, recipients=None, |
|
60 | 60 | mention_recipients=None, with_email=True, email_kwargs=None): |
|
61 | 61 | """ |
@@ -64,11 +64,12 b' class NotificationModel(BaseModel):' | |||
|
64 | 64 | |
|
65 | 65 | :param created_by: int, str or User instance. User who created this |
|
66 | 66 | notification |
|
67 | :param notification_subject: subject of notification itself | |
|
67 | :param notification_subject: subject of notification itself, | |
|
68 | it will be generated automatically from notification_type if not specified | |
|
68 | 69 | :param notification_body: body of notification text |
|
70 | it will be generated automatically from notification_type if not specified | |
|
69 | 71 | :param notification_type: type of notification, based on that we |
|
70 | 72 | pick templates |
|
71 | ||
|
72 | 73 | :param recipients: list of int, str or User objects, when None |
|
73 | 74 | is given send to all admins |
|
74 | 75 | :param mention_recipients: list of int, str or User objects, |
@@ -82,14 +83,19 b' class NotificationModel(BaseModel):' | |||
|
82 | 83 | if recipients and not getattr(recipients, '__iter__', False): |
|
83 | 84 | raise Exception('recipients must be an iterable object') |
|
84 | 85 | |
|
86 | if not (notification_subject and notification_body) and not notification_type: | |
|
87 | raise ValueError('notification_subject, and notification_body ' | |
|
88 | 'cannot be empty when notification_type is not specified') | |
|
89 | ||
|
85 | 90 | created_by_obj = self._get_user(created_by) |
|
91 | ||
|
92 | if not created_by_obj: | |
|
93 | raise Exception('unknown user %s' % created_by) | |
|
94 | ||
|
86 | 95 | # default MAIN body if not given |
|
87 | 96 | email_kwargs = email_kwargs or {'body': notification_body} |
|
88 | 97 | mention_recipients = mention_recipients or set() |
|
89 | 98 | |
|
90 | if not created_by_obj: | |
|
91 | raise Exception('unknown user %s' % created_by) | |
|
92 | ||
|
93 | 99 | if recipients is None: |
|
94 | 100 | # recipients is None means to all admins |
|
95 | 101 | recipients_objs = User.query().filter(User.admin == true()).all() |
@@ -113,6 +119,15 b' class NotificationModel(BaseModel):' | |||
|
113 | 119 | # add mentioned users into recipients |
|
114 | 120 | final_recipients = set(recipients_objs).union(mention_recipients) |
|
115 | 121 | |
|
122 | (subject, email_body, email_body_plaintext) = \ | |
|
123 | EmailNotificationModel().render_email(notification_type, **email_kwargs) | |
|
124 | ||
|
125 | if not notification_subject: | |
|
126 | notification_subject = subject | |
|
127 | ||
|
128 | if not notification_body: | |
|
129 | notification_body = email_body_plaintext | |
|
130 | ||
|
116 | 131 | notification = Notification.create( |
|
117 | 132 | created_by=created_by_obj, subject=notification_subject, |
|
118 | 133 | body=notification_body, recipients=final_recipients, |
@@ -578,7 +578,7 b' class PermissionModel(BaseModel):' | |||
|
578 | 578 | return user_group_write_permissions |
|
579 | 579 | |
|
580 | 580 | def trigger_permission_flush(self, affected_user_ids=None): |
|
581 | affected_user_ids or User.get_all_user_ids() | |
|
581 | affected_user_ids = affected_user_ids or User.get_all_user_ids() | |
|
582 | 582 | events.trigger(events.UserPermissionsChange(affected_user_ids)) |
|
583 | 583 | |
|
584 | 584 | def flush_user_permission_caches(self, changes, affected_user_ids=None): |
@@ -1502,15 +1502,11 b' class PullRequestModel(BaseModel):' | |||
|
1502 | 1502 | 'user_role': role |
|
1503 | 1503 | } |
|
1504 | 1504 | |
|
1505 | # pre-generate the subject for notification itself | |
|
1506 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( | |
|
1507 | notification_type, **kwargs) | |
|
1508 | ||
|
1509 | 1505 | # create notification objects, and emails |
|
1510 | 1506 | NotificationModel().create( |
|
1511 | 1507 | created_by=current_rhodecode_user, |
|
1512 |
notification_subject= |
|
|
1513 | notification_body=body_plaintext, | |
|
1508 | notification_subject='', # Filled in based on the notification_type | |
|
1509 | notification_body='', # Filled in based on the notification_type | |
|
1514 | 1510 | notification_type=notification_type, |
|
1515 | 1511 | recipients=recipients, |
|
1516 | 1512 | email_kwargs=kwargs, |
@@ -1579,14 +1575,11 b' class PullRequestModel(BaseModel):' | |||
|
1579 | 1575 | 'thread_ids': [pr_url], |
|
1580 | 1576 | } |
|
1581 | 1577 | |
|
1582 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( | |
|
1583 | EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **email_kwargs) | |
|
1584 | ||
|
1585 | 1578 | # create notification objects, and emails |
|
1586 | 1579 | NotificationModel().create( |
|
1587 | 1580 | created_by=updating_user, |
|
1588 |
notification_subject= |
|
|
1589 | notification_body=body_plaintext, | |
|
1581 | notification_subject='', # Filled in based on the notification_type | |
|
1582 | notification_body='', # Filled in based on the notification_type | |
|
1590 | 1583 | notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, |
|
1591 | 1584 | recipients=recipients, |
|
1592 | 1585 | email_kwargs=email_kwargs, |
@@ -2067,6 +2060,8 b' class MergeCheck(object):' | |||
|
2067 | 2060 | self.error_details = OrderedDict() |
|
2068 | 2061 | self.source_commit = AttributeDict() |
|
2069 | 2062 | self.target_commit = AttributeDict() |
|
2063 | self.reviewers_count = 0 | |
|
2064 | self.observers_count = 0 | |
|
2070 | 2065 | |
|
2071 | 2066 | def __repr__(self): |
|
2072 | 2067 | return '<MergeCheck(possible:{}, failed:{}, errors:{})>'.format( |
@@ -2128,11 +2123,12 b' class MergeCheck(object):' | |||
|
2128 | 2123 | # review status, must be always present |
|
2129 | 2124 | review_status = pull_request.calculated_review_status() |
|
2130 | 2125 | merge_check.review_status = review_status |
|
2126 | merge_check.reviewers_count = pull_request.reviewers_count | |
|
2127 | merge_check.observers_count = pull_request.observers_count | |
|
2131 | 2128 | |
|
2132 | 2129 | status_approved = review_status == ChangesetStatus.STATUS_APPROVED |
|
2133 | if not status_approved: | |
|
2130 | if not status_approved and merge_check.reviewers_count: | |
|
2134 | 2131 | log.debug("MergeCheck: cannot merge, approval is pending.") |
|
2135 | ||
|
2136 | 2132 | msg = _('Pull request reviewer approval is pending.') |
|
2137 | 2133 | |
|
2138 | 2134 | merge_check.push_error('warning', msg, cls.REVIEW_CHECK, review_status) |
@@ -231,6 +231,10 b' class ScmModel(BaseModel):' | |||
|
231 | 231 | with_wire={"cache": False}) |
|
232 | 232 | except OSError: |
|
233 | 233 | continue |
|
234 | except RepositoryError: | |
|
235 | log.exception('Failed to create a repo') | |
|
236 | continue | |
|
237 | ||
|
234 | 238 | log.debug('found %s paths with repositories', len(repos)) |
|
235 | 239 | return repos |
|
236 | 240 |
@@ -425,15 +425,12 b' class UserModel(BaseModel):' | |||
|
425 | 425 | 'date': datetime.datetime.now() |
|
426 | 426 | } |
|
427 | 427 | notification_type = EmailNotificationModel.TYPE_REGISTRATION |
|
428 | # pre-generate the subject for notification itself | |
|
429 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( | |
|
430 | notification_type, **kwargs) | |
|
431 | 428 | |
|
432 | 429 | # create notification objects, and emails |
|
433 | 430 | NotificationModel().create( |
|
434 | 431 | created_by=new_user, |
|
435 |
notification_subject= |
|
|
436 |
notification_body= |
|
|
432 | notification_subject='', # Filled in based on the notification_type | |
|
433 | notification_body='', # Filled in based on the notification_type | |
|
437 | 434 | notification_type=notification_type, |
|
438 | 435 | recipients=None, # all admins |
|
439 | 436 | email_kwargs=kwargs, |
@@ -60,7 +60,7 b' class CommentSchema(colander.MappingSche' | |||
|
60 | 60 | colander.String(), |
|
61 | 61 | validator=colander.OneOf(ChangesetComment.COMMENT_TYPES), |
|
62 | 62 | missing=ChangesetComment.COMMENT_TYPE_NOTE) |
|
63 | ||
|
63 | is_draft = colander.SchemaNode(colander.Boolean(),missing=False) | |
|
64 | 64 | comment_file = colander.SchemaNode(colander.String(), missing=None) |
|
65 | 65 | comment_line = colander.SchemaNode(colander.String(), missing=None) |
|
66 | 66 | status_change = colander.SchemaNode( |
@@ -162,7 +162,6 b' input[type="button"] {' | |||
|
162 | 162 | } |
|
163 | 163 | } |
|
164 | 164 | |
|
165 | .btn-warning, | |
|
166 | 165 | .btn-danger, |
|
167 | 166 | .revoke_perm, |
|
168 | 167 | .btn-x, |
@@ -196,6 +195,36 b' input[type="button"] {' | |||
|
196 | 195 | } |
|
197 | 196 | } |
|
198 | 197 | |
|
198 | .btn-warning { | |
|
199 | .border ( @border-thickness, @alert3 ); | |
|
200 | background-color: white; | |
|
201 | color: @alert3; | |
|
202 | ||
|
203 | a { | |
|
204 | color: @alert3; | |
|
205 | } | |
|
206 | ||
|
207 | &:hover, | |
|
208 | &.active { | |
|
209 | .border ( @border-thickness, @alert3 ); | |
|
210 | color: white; | |
|
211 | background-color: @alert3; | |
|
212 | ||
|
213 | a { | |
|
214 | color: white; | |
|
215 | } | |
|
216 | } | |
|
217 | ||
|
218 | i { | |
|
219 | display:none; | |
|
220 | } | |
|
221 | ||
|
222 | &:disabled { | |
|
223 | background-color: white; | |
|
224 | color: @alert3; | |
|
225 | } | |
|
226 | } | |
|
227 | ||
|
199 | 228 | .btn-approved-status { |
|
200 | 229 | .border ( @border-thickness, @alert1 ); |
|
201 | 230 | background-color: white; |
@@ -264,7 +293,6 b' input[type="button"] {' | |||
|
264 | 293 | margin-left: -1px; |
|
265 | 294 | padding-left: 2px; |
|
266 | 295 | padding-right: 2px; |
|
267 | border-left: 1px solid @grey3; | |
|
268 | 296 | } |
|
269 | 297 | } |
|
270 | 298 | |
@@ -342,7 +370,7 b' input[type="button"] {' | |||
|
342 | 370 | color: @alert2; |
|
343 | 371 | |
|
344 | 372 | &:hover { |
|
345 | color: darken(@alert2,30%); | |
|
373 | color: darken(@alert2, 30%); | |
|
346 | 374 | } |
|
347 | 375 | |
|
348 | 376 | &:disabled { |
@@ -402,6 +430,37 b' input[type="button"] {' | |||
|
402 | 430 | } |
|
403 | 431 | |
|
404 | 432 | |
|
433 | input[type="submit"].btn-warning { | |
|
434 | &:extend(.btn-warning); | |
|
435 | ||
|
436 | &:focus { | |
|
437 | outline: 0; | |
|
438 | } | |
|
439 | ||
|
440 | &:hover { | |
|
441 | &:extend(.btn-warning:hover); | |
|
442 | } | |
|
443 | ||
|
444 | &.btn-link { | |
|
445 | &:extend(.btn-link); | |
|
446 | color: @alert3; | |
|
447 | ||
|
448 | &:disabled { | |
|
449 | color: @alert3; | |
|
450 | background-color: transparent; | |
|
451 | } | |
|
452 | } | |
|
453 | ||
|
454 | &:disabled { | |
|
455 | .border ( @border-thickness-buttons, @alert3 ); | |
|
456 | background-color: white; | |
|
457 | color: @alert3; | |
|
458 | opacity: 0.5; | |
|
459 | } | |
|
460 | } | |
|
461 | ||
|
462 | ||
|
463 | ||
|
405 | 464 | // TODO: johbo: Form button tweaks, check if we can use the classes instead |
|
406 | 465 | input[type="submit"] { |
|
407 | 466 | &:extend(.btn-primary); |
@@ -1002,7 +1002,7 b' input.filediff-collapse-state {' | |||
|
1002 | 1002 | .nav-chunk { |
|
1003 | 1003 | position: absolute; |
|
1004 | 1004 | right: 20px; |
|
1005 |
margin-top: -1 |
|
|
1005 | margin-top: -15px; | |
|
1006 | 1006 | } |
|
1007 | 1007 | |
|
1008 | 1008 | .nav-chunk.selected { |
@@ -4,7 +4,7 b'' | |||
|
4 | 4 | |
|
5 | 5 | |
|
6 | 6 | // Comments |
|
7 |
@comment-outdated-opacity: 0 |
|
|
7 | @comment-outdated-opacity: 1.0; | |
|
8 | 8 | |
|
9 | 9 | .comments { |
|
10 | 10 | width: 100%; |
@@ -61,28 +61,37 b' tr.inline-comments div {' | |||
|
61 | 61 | visibility: hidden; |
|
62 | 62 | } |
|
63 | 63 | |
|
64 | .comment-draft { | |
|
65 | float: left; | |
|
66 | margin-right: 10px; | |
|
67 | font-weight: 400; | |
|
68 | color: @color-draft; | |
|
69 | } | |
|
70 | ||
|
71 | .comment-new { | |
|
72 | float: left; | |
|
73 | margin-right: 10px; | |
|
74 | font-weight: 400; | |
|
75 | color: @color-new; | |
|
76 | } | |
|
77 | ||
|
64 | 78 | .comment-label { |
|
65 | 79 | float: left; |
|
66 | 80 | |
|
67 |
padding: 0 |
|
|
68 | margin: 2px 4px 0px 0px; | |
|
69 | display: inline-block; | |
|
81 | padding: 0 8px 0 0; | |
|
70 | 82 | min-height: 0; |
|
71 | 83 | |
|
72 | 84 | text-align: center; |
|
73 | 85 | font-size: 10px; |
|
74 | line-height: .8em; | |
|
75 | 86 | |
|
76 | 87 | font-family: @text-italic; |
|
77 | 88 | font-style: italic; |
|
78 | 89 | background: #fff none; |
|
79 | 90 | color: @grey3; |
|
80 | border: 1px solid @grey4; | |
|
81 | 91 | white-space: nowrap; |
|
82 | 92 | |
|
83 | 93 | text-transform: uppercase; |
|
84 | 94 | min-width: 50px; |
|
85 | border-radius: 4px; | |
|
86 | 95 | |
|
87 | 96 | &.todo { |
|
88 | 97 | color: @color5; |
@@ -270,64 +279,165 b' tr.inline-comments div {' | |||
|
270 | 279 | .comment-outdated { |
|
271 | 280 | opacity: @comment-outdated-opacity; |
|
272 | 281 | } |
|
282 | ||
|
283 | .comment-outdated-label { | |
|
284 | color: @grey3; | |
|
285 | padding-right: 4px; | |
|
286 | } | |
|
273 | 287 | } |
|
274 | 288 | |
|
275 | 289 | .inline-comments { |
|
276 | border-radius: @border-radius; | |
|
290 | ||
|
277 | 291 | .comment { |
|
278 | 292 | margin: 0; |
|
279 | border-radius: @border-radius; | |
|
280 | 293 | } |
|
294 | ||
|
281 | 295 | .comment-outdated { |
|
282 | 296 | opacity: @comment-outdated-opacity; |
|
283 | 297 | } |
|
284 | 298 | |
|
299 | .comment-outdated-label { | |
|
300 | color: @grey3; | |
|
301 | padding-right: 4px; | |
|
302 | } | |
|
303 | ||
|
285 | 304 | .comment-inline { |
|
305 | ||
|
306 | &:first-child { | |
|
307 | margin: 4px 4px 0 4px; | |
|
308 | border-top: 1px solid @grey5; | |
|
309 | border-bottom: 0 solid @grey5; | |
|
310 | border-left: 1px solid @grey5; | |
|
311 | border-right: 1px solid @grey5; | |
|
312 | .border-radius-top(4px); | |
|
313 | } | |
|
314 | ||
|
315 | &:only-child { | |
|
316 | margin: 4px 4px 0 4px; | |
|
317 | border-top: 1px solid @grey5; | |
|
318 | border-bottom: 0 solid @grey5; | |
|
319 | border-left: 1px solid @grey5; | |
|
320 | border-right: 1px solid @grey5; | |
|
321 | .border-radius-top(4px); | |
|
322 | } | |
|
323 | ||
|
286 | 324 | background: white; |
|
287 | 325 | padding: @comment-padding @comment-padding; |
|
288 | border: @comment-padding solid @grey6; | |
|
326 | margin: 0 4px 0 4px; | |
|
327 | border-top: 0 solid @grey5; | |
|
328 | border-bottom: 0 solid @grey5; | |
|
329 | border-left: 1px solid @grey5; | |
|
330 | border-right: 1px solid @grey5; | |
|
289 | 331 | |
|
290 | 332 | .text { |
|
291 | 333 | border: none; |
|
292 | 334 | } |
|
335 | ||
|
293 | 336 | .meta { |
|
294 | 337 | border-bottom: 1px solid @grey6; |
|
295 | 338 | margin: -5px 0px; |
|
296 | 339 | line-height: 24px; |
|
297 | 340 | } |
|
341 | ||
|
298 | 342 | } |
|
299 | 343 | .comment-selected { |
|
300 | 344 | border-left: 6px solid @comment-highlight-color; |
|
301 | 345 | } |
|
346 | ||
|
347 | .comment-inline-form-open { | |
|
348 | display: block !important; | |
|
349 | } | |
|
350 | ||
|
302 | 351 | .comment-inline-form { |
|
303 | padding: @comment-padding; | |
|
304 | 352 | display: none; |
|
305 | 353 | } |
|
306 | .cb-comment-add-button { | |
|
307 | margin: @comment-padding; | |
|
354 | ||
|
355 | .comment-inline-form-edit { | |
|
356 | padding: 0; | |
|
357 | margin: 0px 4px 2px 4px; | |
|
358 | } | |
|
359 | ||
|
360 | .reply-thread-container { | |
|
361 | display: table; | |
|
362 | width: 100%; | |
|
363 | padding: 0px 4px 4px 4px; | |
|
364 | } | |
|
365 | ||
|
366 | .reply-thread-container-wrapper { | |
|
367 | margin: 0 4px 4px 4px; | |
|
368 | border-top: 0 solid @grey5; | |
|
369 | border-bottom: 1px solid @grey5; | |
|
370 | border-left: 1px solid @grey5; | |
|
371 | border-right: 1px solid @grey5; | |
|
372 | .border-radius-bottom(4px); | |
|
373 | } | |
|
374 | ||
|
375 | .reply-thread-gravatar { | |
|
376 | display: table-cell; | |
|
377 | width: 24px; | |
|
378 | height: 24px; | |
|
379 | padding-top: 10px; | |
|
380 | padding-left: 10px; | |
|
381 | background-color: #eeeeee; | |
|
382 | vertical-align: top; | |
|
308 | 383 | } |
|
309 | /* hide add comment button when form is open */ | |
|
384 | ||
|
385 | .reply-thread-reply-button { | |
|
386 | display: table-cell; | |
|
387 | width: 100%; | |
|
388 | height: 33px; | |
|
389 | padding: 3px 8px; | |
|
390 | margin-left: 8px; | |
|
391 | background-color: #eeeeee; | |
|
392 | } | |
|
393 | ||
|
394 | .reply-thread-reply-button .cb-comment-add-button { | |
|
395 | border-radius: 4px; | |
|
396 | width: 100%; | |
|
397 | padding: 6px 2px; | |
|
398 | text-align: left; | |
|
399 | cursor: text; | |
|
400 | color: @grey3; | |
|
401 | } | |
|
402 | .reply-thread-reply-button .cb-comment-add-button:hover { | |
|
403 | background-color: white; | |
|
404 | color: @grey2; | |
|
405 | } | |
|
406 | ||
|
407 | .reply-thread-last { | |
|
408 | display: table-cell; | |
|
409 | width: 10px; | |
|
410 | } | |
|
411 | ||
|
412 | /* Hide reply box when it's a first element, | |
|
413 | can happen when drafts are saved but not shown to specific user, | |
|
414 | or there are outdated comments hidden | |
|
415 | */ | |
|
416 | .reply-thread-container-wrapper:first-child:not(.comment-form-active) { | |
|
417 | display: none; | |
|
418 | } | |
|
419 | ||
|
420 | .reply-thread-container-wrapper.comment-outdated { | |
|
421 | display: none | |
|
422 | } | |
|
423 | ||
|
424 | /* hide add comment button when form is open */ | |
|
310 | 425 | .comment-inline-form-open ~ .cb-comment-add-button { |
|
311 | 426 | display: none; |
|
312 | 427 | } |
|
313 | .comment-inline-form-open { | |
|
314 | display: block; | |
|
315 | } | |
|
316 | /* hide add comment button when form but no comments */ | |
|
317 | .comment-inline-form:first-child + .cb-comment-add-button { | |
|
318 | display: none; | |
|
319 | } | |
|
320 | /* hide add comment button when no comments or form */ | |
|
321 | .cb-comment-add-button:first-child { | |
|
322 | display: none; | |
|
323 | } | |
|
428 | ||
|
324 | 429 | /* hide add comment button when only comment is being deleted */ |
|
325 | 430 | .comment-deleting:first-child + .cb-comment-add-button { |
|
326 | 431 | display: none; |
|
327 | 432 | } |
|
433 | ||
|
434 | /* hide add comment button when form but no comments */ | |
|
435 | .comment-inline-form:first-child + .cb-comment-add-button { | |
|
436 | display: none; | |
|
437 | } | |
|
438 | ||
|
328 | 439 | } |
|
329 | 440 | |
|
330 | ||
|
331 | 441 | .show-outdated-comments { |
|
332 | 442 | display: inline; |
|
333 | 443 | color: @rcblue; |
@@ -380,23 +490,36 b' form.comment-form {' | |||
|
380 | 490 | } |
|
381 | 491 | |
|
382 | 492 | .comment-footer { |
|
383 | position: relative; | |
|
493 | display: table; | |
|
384 | 494 | width: 100%; |
|
385 |
|
|
|
495 | height: 42px; | |
|
386 | 496 | |
|
387 |
.status |
|
|
497 | .comment-status-box, | |
|
388 | 498 | .cancel-button { |
|
389 | float: left; | |
|
390 | 499 | display: inline-block; |
|
391 | 500 | } |
|
392 | 501 | |
|
393 |
.status |
|
|
502 | .comment-status-box { | |
|
394 | 503 | margin-left: 10px; |
|
395 | 504 | } |
|
396 | 505 | |
|
397 | 506 | .action-buttons { |
|
398 |
|
|
|
399 | display: inline-block; | |
|
507 | display: table-cell; | |
|
508 | padding: 5px 0 5px 2px; | |
|
509 | } | |
|
510 | ||
|
511 | .toolbar-text { | |
|
512 | height: 28px; | |
|
513 | display: table-cell; | |
|
514 | vertical-align: baseline; | |
|
515 | font-size: 11px; | |
|
516 | color: @grey4; | |
|
517 | text-align: right; | |
|
518 | ||
|
519 | a { | |
|
520 | color: @grey4; | |
|
521 | } | |
|
522 | ||
|
400 | 523 | } |
|
401 | 524 | |
|
402 | 525 | .action-buttons-extra { |
@@ -427,10 +550,10 b' form.comment-form {' | |||
|
427 | 550 | margin-right: 0; |
|
428 | 551 | } |
|
429 | 552 | |
|
430 | .comment-footer { | |
|
431 |
margin- |
|
|
432 | margin-top: 10px; | |
|
553 | #save_general { | |
|
554 | margin-left: -6px; | |
|
433 | 555 | } |
|
556 | ||
|
434 | 557 | } |
|
435 | 558 | |
|
436 | 559 | |
@@ -482,8 +605,8 b' form.comment-form {' | |||
|
482 | 605 | .injected_diff .comment-inline-form, |
|
483 | 606 | .comment-inline-form { |
|
484 | 607 | background-color: white; |
|
485 |
margin-top: |
|
|
486 |
margin-bottom: |
|
|
608 | margin-top: 4px; | |
|
609 | margin-bottom: 10px; | |
|
487 | 610 | } |
|
488 | 611 | |
|
489 | 612 | .inline-form { |
@@ -519,9 +642,6 b' form.comment-form {' | |||
|
519 | 642 | margin: 0px; |
|
520 | 643 | } |
|
521 | 644 | |
|
522 | .comment-inline-form .comment-footer { | |
|
523 | margin: 10px 0px 0px 0px; | |
|
524 | } | |
|
525 | 645 | |
|
526 | 646 | .hide-inline-form-button { |
|
527 | 647 | margin-left: 5px; |
@@ -547,6 +667,7 b' comment-area-text {' | |||
|
547 | 667 | |
|
548 | 668 | .comment-area-header { |
|
549 | 669 | height: 35px; |
|
670 | border-bottom: 1px solid @grey5; | |
|
550 | 671 | } |
|
551 | 672 | |
|
552 | 673 | .comment-area-header .nav-links { |
@@ -554,6 +675,7 b' comment-area-text {' | |||
|
554 | 675 | flex-flow: row wrap; |
|
555 | 676 | -webkit-flex-flow: row wrap; |
|
556 | 677 | width: 100%; |
|
678 | border: none; | |
|
557 | 679 | } |
|
558 | 680 | |
|
559 | 681 | .comment-area-footer { |
@@ -622,14 +744,3 b' comment-area-text {' | |||
|
622 | 744 | border-bottom: 2px solid transparent; |
|
623 | 745 | } |
|
624 | 746 | |
|
625 | .toolbar-text { | |
|
626 | float: right; | |
|
627 | font-size: 11px; | |
|
628 | color: @grey4; | |
|
629 | text-align: right; | |
|
630 | ||
|
631 | a { | |
|
632 | color: @grey4; | |
|
633 | } | |
|
634 | } | |
|
635 |
@@ -213,7 +213,6 b' div.markdown-block pre {' | |||
|
213 | 213 | div.markdown-block img { |
|
214 | 214 | border-style: none; |
|
215 | 215 | background-color: #fff; |
|
216 | padding-right: 20px; | |
|
217 | 216 | max-width: 100%; |
|
218 | 217 | } |
|
219 | 218 | |
@@ -274,6 +273,13 b' div.markdown-block #ws {' | |||
|
274 | 273 | background-color: @grey6; |
|
275 | 274 | } |
|
276 | 275 | |
|
276 | div.markdown-block p { | |
|
277 | margin-top: 0; | |
|
278 | margin-bottom: 16px; | |
|
279 | padding: 0; | |
|
280 | line-height: unset; | |
|
281 | } | |
|
282 | ||
|
277 | 283 | div.markdown-block code, |
|
278 | 284 | div.markdown-block pre, |
|
279 | 285 | div.markdown-block #ws, |
@@ -2,7 +2,7 b'' | |||
|
2 | 2 | |
|
3 | 3 | |
|
4 | 4 | .loginbox { |
|
5 |
max-width: |
|
|
5 | max-width: 960px; | |
|
6 | 6 | margin: @pagepadding auto; |
|
7 | 7 | font-family: @text-light; |
|
8 | 8 | border: @border-thickness solid @grey5; |
@@ -22,13 +22,27 b'' | |||
|
22 | 22 | float: none; |
|
23 | 23 | } |
|
24 | 24 | |
|
25 | .header { | |
|
25 | .header-account { | |
|
26 | min-height: 49px; | |
|
26 | 27 | width: 100%; |
|
27 |
padding: 0 |
|
|
28 | padding: 0 @header-padding; | |
|
28 | 29 | box-sizing: border-box; |
|
30 | position: relative; | |
|
31 | vertical-align: bottom; | |
|
32 | ||
|
33 | background-color: @grey1; | |
|
34 | color: @grey5; | |
|
29 | 35 | |
|
30 | 36 | .title { |
|
31 | 37 | padding: 0; |
|
38 | overflow: visible; | |
|
39 | } | |
|
40 | ||
|
41 | &:before, | |
|
42 | &:after { | |
|
43 | content: ""; | |
|
44 | clear: both; | |
|
45 | width: 100%; | |
|
32 | 46 | } |
|
33 | 47 | } |
|
34 | 48 | |
@@ -69,7 +83,7 b'' | |||
|
69 | 83 | .sign-in-image { |
|
70 | 84 | display: block; |
|
71 | 85 | width: 65%; |
|
72 |
margin: |
|
|
86 | margin: 1% auto; | |
|
73 | 87 | } |
|
74 | 88 | |
|
75 | 89 | .sign-in-title { |
@@ -263,9 +263,6 b' input.inline[type="file"] {' | |||
|
263 | 263 | // HEADER |
|
264 | 264 | .header { |
|
265 | 265 | |
|
266 | // TODO: johbo: Fix login pages, so that they work without a min-height | |
|
267 | // for the header and then remove the min-height. I chose a smaller value | |
|
268 | // intentionally here to avoid rendering issues in the main navigation. | |
|
269 | 266 |
|
|
270 | 267 | min-width: 1024px; |
|
271 | 268 | |
@@ -1143,9 +1140,8 b' label {' | |||
|
1143 | 1140 | margin-left: -15px; |
|
1144 | 1141 | } |
|
1145 | 1142 | |
|
1146 | #rev_range_container, #rev_range_clear, #rev_range_more { | |
|
1147 |
|
|
|
1148 | margin-bottom: -5px; | |
|
1143 | #rev_range_action { | |
|
1144 | margin-bottom: -8px; | |
|
1149 | 1145 | } |
|
1150 | 1146 | |
|
1151 | 1147 | #filter_changelog { |
@@ -1591,9 +1587,9 b' table.integrations {' | |||
|
1591 | 1587 | } |
|
1592 | 1588 | .pr-details-title { |
|
1593 | 1589 | height: 20px; |
|
1594 |
line-height: |
|
|
1595 | ||
|
1596 |
padding-bottom: |
|
|
1590 | line-height: 16px; | |
|
1591 | ||
|
1592 | padding-bottom: 4px; | |
|
1597 | 1593 | border-bottom: @border-thickness solid @grey5; |
|
1598 | 1594 | |
|
1599 | 1595 | .action_button.disabled { |
@@ -3212,7 +3208,12 b' details:not([open]) > :not(summary) {' | |||
|
3212 | 3208 | |
|
3213 | 3209 | .sidebar-element { |
|
3214 | 3210 | margin-top: 20px; |
|
3215 | } | |
|
3211 | ||
|
3212 | .icon-draft { | |
|
3213 | color: @color-draft | |
|
3214 | } | |
|
3215 | } | |
|
3216 | ||
|
3216 | 3217 | |
|
3217 | 3218 | .right-sidebar-collapsed-state { |
|
3218 | 3219 | display: flex; |
@@ -3235,5 +3236,4 b' details:not([open]) > :not(summary) {' | |||
|
3235 | 3236 | |
|
3236 | 3237 | .old-comments-marker td { |
|
3237 | 3238 | padding-top: 15px; |
|
3238 | border-bottom: 1px solid @grey5; | |
|
3239 | } | |
|
3239 | } |
@@ -115,11 +115,9 b' div.readme_box pre {' | |||
|
115 | 115 | div.readme_box img { |
|
116 | 116 | border-style: none; |
|
117 | 117 | background-color: #fff; |
|
118 | padding-right: 20px; | |
|
119 | 118 | max-width: 100%; |
|
120 | 119 | } |
|
121 | 120 | |
|
122 | ||
|
123 | 121 | div.readme_box strong { |
|
124 | 122 | font-weight: 600; |
|
125 | 123 | margin: 0; |
@@ -152,6 +150,13 b' div.readme_box a:visited {' | |||
|
152 | 150 | } |
|
153 | 151 | */ |
|
154 | 152 | |
|
153 | div.readme_box p { | |
|
154 | margin-top: 0; | |
|
155 | margin-bottom: 16px; | |
|
156 | padding: 0; | |
|
157 | line-height: unset; | |
|
158 | } | |
|
159 | ||
|
155 | 160 | |
|
156 | 161 | div.readme_box button { |
|
157 | 162 | font-size: @basefontsize; |
@@ -47,6 +47,8 b'' | |||
|
47 | 47 | |
|
48 | 48 | // Highlight color for lines and colors |
|
49 | 49 | @comment-highlight-color: #ffd887; |
|
50 | @color-draft: darken(@alert3, 30%); | |
|
51 | @color-new: darken(@alert1, 5%); | |
|
50 | 52 | |
|
51 | 53 | // FONTS |
|
52 | 54 | @basefontsize: 13px; |
@@ -248,6 +248,7 b' function registerRCRoutes() {' | |||
|
248 | 248 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']); |
|
249 | 249 | pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']); |
|
250 | 250 | pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']); |
|
251 | pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']); | |
|
251 | 252 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
252 | 253 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
253 | 254 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); |
@@ -386,6 +387,8 b' function registerRCRoutes() {' | |||
|
386 | 387 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); |
|
387 | 388 | pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []); |
|
388 | 389 | pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []); |
|
390 | pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']); | |
|
391 | pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']); | |
|
389 | 392 | pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']); |
|
390 | 393 | pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']); |
|
391 | 394 | pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']); |
@@ -71,14 +71,20 b' export class RhodecodeApp extends Polyme' | |||
|
71 | 71 | if (elem) { |
|
72 | 72 | elem.handleNotification(data); |
|
73 | 73 | } |
|
74 | ||
|
75 | 74 | } |
|
76 | 75 | |
|
77 | 76 | handleComment(data) { |
|
78 | if (data.message.comment_id) { | |
|
77 | ||
|
78 | if (data.message.comment_data.length !== 0) { | |
|
79 | 79 | if (window.refreshAllComments !== undefined) { |
|
80 | 80 | refreshAllComments() |
|
81 | 81 | } |
|
82 | var json_data = data.message.comment_data; | |
|
83 | ||
|
84 | if (window.commentsController !== undefined) { | |
|
85 | ||
|
86 | window.commentsController.attachComment(json_data) | |
|
87 | } | |
|
82 | 88 | } |
|
83 | 89 | } |
|
84 | 90 |
@@ -704,3 +704,13 b' var storeUserSessionAttr = function (key' | |||
|
704 | 704 | ajaxPOST(pyroutes.url('store_user_session_value'), postData, success); |
|
705 | 705 | return false; |
|
706 | 706 | }; |
|
707 | ||
|
708 | ||
|
709 | var getUserSessionAttr = function(key) { | |
|
710 | var storeKey = templateContext.session_attrs; | |
|
711 | var val = storeKey[key] | |
|
712 | if (val !== undefined) { | |
|
713 | return JSON.parse(val) | |
|
714 | } | |
|
715 | return null | |
|
716 | } |
@@ -349,7 +349,12 b' var initCommentBoxCodeMirror = function(' | |||
|
349 | 349 | }; |
|
350 | 350 | |
|
351 | 351 | var submitForm = function(cm, pred) { |
|
352 | $(cm.display.input.textarea.form).submit(); | |
|
352 | $(cm.display.input.textarea.form).find('.submit-comment-action').click(); | |
|
353 | return CodeMirror.Pass; | |
|
354 | }; | |
|
355 | ||
|
356 | var submitFormAsDraft = function(cm, pred) { | |
|
357 | $(cm.display.input.textarea.form).find('.submit-draft-action').click(); | |
|
353 | 358 | return CodeMirror.Pass; |
|
354 | 359 | }; |
|
355 | 360 | |
@@ -475,9 +480,11 b' var initCommentBoxCodeMirror = function(' | |||
|
475 | 480 | // submit form on Meta-Enter |
|
476 | 481 | if (OSType === "mac") { |
|
477 | 482 | extraKeys["Cmd-Enter"] = submitForm; |
|
483 | extraKeys["Shift-Cmd-Enter"] = submitFormAsDraft; | |
|
478 | 484 | } |
|
479 | 485 | else { |
|
480 | 486 | extraKeys["Ctrl-Enter"] = submitForm; |
|
487 | extraKeys["Shift-Ctrl-Enter"] = submitFormAsDraft; | |
|
481 | 488 | } |
|
482 | 489 | |
|
483 | 490 | if (triggerActions) { |
@@ -124,16 +124,20 b' var _submitAjaxPOST = function(url, post' | |||
|
124 | 124 | this.statusChange = this.withLineNo('#change_status'); |
|
125 | 125 | |
|
126 | 126 | this.submitForm = formElement; |
|
127 | this.submitButton = $(this.submitForm).find('input[type="submit"]'); | |
|
127 | ||
|
128 | this.submitButton = $(this.submitForm).find('.submit-comment-action'); | |
|
128 | 129 | this.submitButtonText = this.submitButton.val(); |
|
129 | 130 | |
|
131 | this.submitDraftButton = $(this.submitForm).find('.submit-draft-action'); | |
|
132 | this.submitDraftButtonText = this.submitDraftButton.val(); | |
|
130 | 133 | |
|
131 | 134 | this.previewUrl = pyroutes.url('repo_commit_comment_preview', |
|
132 | 135 | {'repo_name': templateContext.repo_name, |
|
133 | 136 | 'commit_id': templateContext.commit_data.commit_id}); |
|
134 | 137 | |
|
135 | 138 | if (edit){ |
|
136 |
this.submitButton |
|
|
139 | this.submitDraftButton.hide(); | |
|
140 | this.submitButtonText = _gettext('Update Comment'); | |
|
137 | 141 | $(this.commentType).prop('disabled', true); |
|
138 | 142 | $(this.commentType).addClass('disabled'); |
|
139 | 143 | var editInfo = |
@@ -215,10 +219,17 b' var _submitAjaxPOST = function(url, post' | |||
|
215 | 219 | this.getCommentStatus = function() { |
|
216 | 220 | return $(this.submitForm).find(this.statusChange).val(); |
|
217 | 221 | }; |
|
222 | ||
|
218 | 223 | this.getCommentType = function() { |
|
219 | 224 | return $(this.submitForm).find(this.commentType).val(); |
|
220 | 225 | }; |
|
221 | 226 | |
|
227 | this.getDraftState = function () { | |
|
228 | var submitterElem = $(this.submitForm).find('input[type="submit"].submitter'); | |
|
229 | var data = $(submitterElem).data('isDraft'); | |
|
230 | return data | |
|
231 | } | |
|
232 | ||
|
222 | 233 | this.getResolvesId = function() { |
|
223 | 234 | return $(this.submitForm).find(this.resolvesId).val() || null; |
|
224 | 235 | }; |
@@ -233,7 +244,9 b' var _submitAjaxPOST = function(url, post' | |||
|
233 | 244 | }; |
|
234 | 245 | |
|
235 | 246 | this.isAllowedToSubmit = function() { |
|
236 |
r |
|
|
247 | var commentDisabled = $(this.submitButton).prop('disabled'); | |
|
248 | var draftDisabled = $(this.submitDraftButton).prop('disabled'); | |
|
249 | return !commentDisabled && !draftDisabled; | |
|
237 | 250 | }; |
|
238 | 251 | |
|
239 | 252 | this.initStatusChangeSelector = function(){ |
@@ -259,11 +272,13 b' var _submitAjaxPOST = function(url, post' | |||
|
259 | 272 | dropdownAutoWidth: true, |
|
260 | 273 | minimumResultsForSearch: -1 |
|
261 | 274 | }); |
|
275 | ||
|
262 | 276 | $(this.submitForm).find(this.statusChange).on('change', function() { |
|
263 | 277 | var status = self.getCommentStatus(); |
|
264 | 278 | |
|
265 | 279 | if (status && !self.isInline()) { |
|
266 | 280 | $(self.submitButton).prop('disabled', false); |
|
281 | $(self.submitDraftButton).prop('disabled', false); | |
|
267 | 282 | } |
|
268 | 283 | |
|
269 | 284 | var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status); |
@@ -295,10 +310,10 b' var _submitAjaxPOST = function(url, post' | |||
|
295 | 310 | $(this.statusChange).select2('readonly', false); |
|
296 | 311 | }; |
|
297 | 312 | |
|
298 | this.globalSubmitSuccessCallback = function(){ | |
|
313 | this.globalSubmitSuccessCallback = function(comment){ | |
|
299 | 314 | // default behaviour is to call GLOBAL hook, if it's registered. |
|
300 | 315 | if (window.commentFormGlobalSubmitSuccessCallback !== undefined){ |
|
301 | commentFormGlobalSubmitSuccessCallback(); | |
|
316 | commentFormGlobalSubmitSuccessCallback(comment); | |
|
302 | 317 | } |
|
303 | 318 | }; |
|
304 | 319 | |
@@ -321,6 +336,7 b' var _submitAjaxPOST = function(url, post' | |||
|
321 | 336 | var text = self.cm.getValue(); |
|
322 | 337 | var status = self.getCommentStatus(); |
|
323 | 338 | var commentType = self.getCommentType(); |
|
339 | var isDraft = self.getDraftState(); | |
|
324 | 340 | var resolvesCommentId = self.getResolvesId(); |
|
325 | 341 | var closePullRequest = self.getClosePr(); |
|
326 | 342 | |
@@ -348,12 +364,15 b' var _submitAjaxPOST = function(url, post' | |||
|
348 | 364 | postData['close_pull_request'] = true; |
|
349 | 365 | } |
|
350 | 366 | |
|
351 |
|
|
|
367 | // submitSuccess for general comments | |
|
368 | var submitSuccessCallback = function(json_data) { | |
|
352 | 369 | // reload page if we change status for single commit. |
|
353 | 370 | if (status && self.commitId) { |
|
354 | 371 | location.reload(true); |
|
355 | 372 | } else { |
|
356 | $('#injected_page_comments').append(o.rendered_text); | |
|
373 | // inject newly created comments, json_data is {<comment_id>: {}} | |
|
374 | self.attachGeneralComment(json_data) | |
|
375 | ||
|
357 | 376 | self.resetCommentFormState(); |
|
358 | 377 | timeagoActivate(); |
|
359 | 378 | tooltipActivate(); |
@@ -365,7 +384,7 b' var _submitAjaxPOST = function(url, post' | |||
|
365 | 384 | } |
|
366 | 385 | |
|
367 | 386 | // run global callback on submit |
|
368 | self.globalSubmitSuccessCallback(); | |
|
387 | self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id}); | |
|
369 | 388 | |
|
370 | 389 | }; |
|
371 | 390 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { |
@@ -409,10 +428,20 b' var _submitAjaxPOST = function(url, post' | |||
|
409 | 428 | } |
|
410 | 429 | |
|
411 | 430 | $(this.submitButton).prop('disabled', submitState); |
|
431 | $(this.submitDraftButton).prop('disabled', submitState); | |
|
432 | ||
|
412 | 433 | if (submitEvent) { |
|
413 | $(this.submitButton).val(_gettext('Submitting...')); | |
|
434 | var isDraft = self.getDraftState(); | |
|
435 | ||
|
436 | if (isDraft) { | |
|
437 | $(this.submitDraftButton).val(_gettext('Saving Draft...')); | |
|
438 | } else { | |
|
439 | $(this.submitButton).val(_gettext('Submitting...')); | |
|
440 | } | |
|
441 | ||
|
414 | 442 | } else { |
|
415 | 443 | $(this.submitButton).val(this.submitButtonText); |
|
444 | $(this.submitDraftButton).val(this.submitDraftButtonText); | |
|
416 | 445 | } |
|
417 | 446 | |
|
418 | 447 | }; |
@@ -488,6 +517,7 b' var _submitAjaxPOST = function(url, post' | |||
|
488 | 517 | if (!allowedToSubmit){ |
|
489 | 518 | return false; |
|
490 | 519 | } |
|
520 | ||
|
491 | 521 | self.handleFormSubmit(); |
|
492 | 522 | }); |
|
493 | 523 | |
@@ -538,26 +568,6 b' var CommentsController = function() {' | |||
|
538 | 568 | var mainComment = '#text'; |
|
539 | 569 | var self = this; |
|
540 | 570 | |
|
541 | this.cancelComment = function (node) { | |
|
542 | var $node = $(node); | |
|
543 | var edit = $(this).attr('edit'); | |
|
544 | if (edit) { | |
|
545 | var $general_comments = null; | |
|
546 | var $inline_comments = $node.closest('div.inline-comments'); | |
|
547 | if (!$inline_comments.length) { | |
|
548 | $general_comments = $('#comments'); | |
|
549 | var $comment = $general_comments.parent().find('div.comment:hidden'); | |
|
550 | // show hidden general comment form | |
|
551 | $('#cb-comment-general-form-placeholder').show(); | |
|
552 | } else { | |
|
553 | var $comment = $inline_comments.find('div.comment:hidden'); | |
|
554 | } | |
|
555 | $comment.show(); | |
|
556 | } | |
|
557 | $node.closest('.comment-inline-form').remove(); | |
|
558 | return false; | |
|
559 | }; | |
|
560 | ||
|
561 | 571 | this.showVersion = function (comment_id, comment_history_id) { |
|
562 | 572 | |
|
563 | 573 | var historyViewUrl = pyroutes.url( |
@@ -655,12 +665,51 b' var CommentsController = function() {' | |||
|
655 | 665 | return self.scrollToComment(node, -1, true); |
|
656 | 666 | }; |
|
657 | 667 | |
|
668 | this.cancelComment = function (node) { | |
|
669 | var $node = $(node); | |
|
670 | var edit = $(this).attr('edit'); | |
|
671 | var $inlineComments = $node.closest('div.inline-comments'); | |
|
672 | ||
|
673 | if (edit) { | |
|
674 | var $general_comments = null; | |
|
675 | if (!$inlineComments.length) { | |
|
676 | $general_comments = $('#comments'); | |
|
677 | var $comment = $general_comments.parent().find('div.comment:hidden'); | |
|
678 | // show hidden general comment form | |
|
679 | $('#cb-comment-general-form-placeholder').show(); | |
|
680 | } else { | |
|
681 | var $comment = $inlineComments.find('div.comment:hidden'); | |
|
682 | } | |
|
683 | $comment.show(); | |
|
684 | } | |
|
685 | var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper') | |
|
686 | $replyWrapper.removeClass('comment-form-active'); | |
|
687 | ||
|
688 | var lastComment = $inlineComments.find('.comment-inline').last(); | |
|
689 | if ($(lastComment).hasClass('comment-outdated')) { | |
|
690 | $replyWrapper.hide(); | |
|
691 | } | |
|
692 | ||
|
693 | $node.closest('.comment-inline-form').remove(); | |
|
694 | return false; | |
|
695 | }; | |
|
696 | ||
|
658 | 697 | this._deleteComment = function(node) { |
|
659 | 698 | var $node = $(node); |
|
660 | 699 | var $td = $node.closest('td'); |
|
661 | 700 | var $comment = $node.closest('.comment'); |
|
662 |
var comment_id = $comment. |
|
|
663 | var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id); | |
|
701 | var comment_id = $($comment).data('commentId'); | |
|
702 | var isDraft = $($comment).data('commentDraft'); | |
|
703 | ||
|
704 | var pullRequestId = templateContext.pull_request_data.pull_request_id; | |
|
705 | var commitId = templateContext.commit_data.commit_id; | |
|
706 | ||
|
707 | if (pullRequestId) { | |
|
708 | var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId}) | |
|
709 | } else if (commitId) { | |
|
710 | var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId}) | |
|
711 | } | |
|
712 | ||
|
664 | 713 | var postData = { |
|
665 | 714 | 'csrf_token': CSRF_TOKEN |
|
666 | 715 | }; |
@@ -677,10 +726,14 b' var CommentsController = function() {' | |||
|
677 | 726 | updateSticky() |
|
678 | 727 | } |
|
679 | 728 | |
|
680 | if (window.refreshAllComments !== undefined) { | |
|
729 | if (window.refreshAllComments !== undefined && !isDraft) { | |
|
681 | 730 | // if we have this handler, run it, and refresh all comments boxes |
|
682 | 731 | refreshAllComments() |
|
683 | 732 | } |
|
733 | else if (window.refreshDraftComments !== undefined && isDraft) { | |
|
734 | // if we have this handler, run it, and refresh all comments boxes | |
|
735 | refreshDraftComments(); | |
|
736 | } | |
|
684 | 737 | return false; |
|
685 | 738 | }; |
|
686 | 739 | |
@@ -695,8 +748,6 b' var CommentsController = function() {' | |||
|
695 | 748 | }; |
|
696 | 749 | ajaxPOST(url, postData, success, failure); |
|
697 | 750 | |
|
698 | ||
|
699 | ||
|
700 | 751 | } |
|
701 | 752 | |
|
702 | 753 | this.deleteComment = function(node) { |
@@ -716,7 +767,59 b' var CommentsController = function() {' | |||
|
716 | 767 | }) |
|
717 | 768 | }; |
|
718 | 769 | |
|
770 | this._finalizeDrafts = function(commentIds) { | |
|
771 | ||
|
772 | var pullRequestId = templateContext.pull_request_data.pull_request_id; | |
|
773 | var commitId = templateContext.commit_data.commit_id; | |
|
774 | ||
|
775 | if (pullRequestId) { | |
|
776 | var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId}) | |
|
777 | } else if (commitId) { | |
|
778 | var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId}) | |
|
779 | } | |
|
780 | ||
|
781 | // remove the drafts so we can lock them before submit. | |
|
782 | $.each(commentIds, function(idx, val){ | |
|
783 | $('#comment-{0}'.format(val)).remove(); | |
|
784 | }) | |
|
785 | ||
|
786 | var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN}; | |
|
787 | ||
|
788 | var submitSuccessCallback = function(json_data) { | |
|
789 | self.attachInlineComment(json_data); | |
|
790 | ||
|
791 | if (window.refreshDraftComments !== undefined) { | |
|
792 | // if we have this handler, run it, and refresh all comments boxes | |
|
793 | refreshDraftComments() | |
|
794 | } | |
|
795 | ||
|
796 | return false; | |
|
797 | }; | |
|
798 | ||
|
799 | ajaxPOST(url, postData, submitSuccessCallback) | |
|
800 | ||
|
801 | } | |
|
802 | ||
|
803 | this.finalizeDrafts = function(commentIds, callback) { | |
|
804 | ||
|
805 | SwalNoAnimation.fire({ | |
|
806 | title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length), | |
|
807 | icon: 'warning', | |
|
808 | showCancelButton: true, | |
|
809 | confirmButtonText: _gettext('Yes'), | |
|
810 | ||
|
811 | }).then(function(result) { | |
|
812 | if (result.value) { | |
|
813 | if (callback !== undefined) { | |
|
814 | callback(result) | |
|
815 | } | |
|
816 | self._finalizeDrafts(commentIds); | |
|
817 | } | |
|
818 | }) | |
|
819 | }; | |
|
820 | ||
|
719 | 821 | this.toggleWideMode = function (node) { |
|
822 | ||
|
720 | 823 | if ($('#content').hasClass('wrapper')) { |
|
721 | 824 | $('#content').removeClass("wrapper"); |
|
722 | 825 | $('#content').addClass("wide-mode-wrapper"); |
@@ -731,16 +834,49 b' var CommentsController = function() {' | |||
|
731 | 834 | |
|
732 | 835 | }; |
|
733 | 836 | |
|
734 | this.toggleComments = function(node, show) { | |
|
837 | /** | |
|
838 | * Turn off/on all comments in file diff | |
|
839 | */ | |
|
840 | this.toggleDiffComments = function(node) { | |
|
841 | // Find closes filediff container | |
|
735 | 842 | var $filediff = $(node).closest('.filediff'); |
|
843 | if ($(node).hasClass('toggle-on')) { | |
|
844 | var show = false; | |
|
845 | } else if ($(node).hasClass('toggle-off')) { | |
|
846 | var show = true; | |
|
847 | } | |
|
848 | ||
|
849 | // Toggle each individual comment block, so we can un-toggle single ones | |
|
850 | $.each($filediff.find('.toggle-comment-action'), function(idx, val) { | |
|
851 | self.toggleLineComments($(val), show) | |
|
852 | }) | |
|
853 | ||
|
854 | // since we change the height of the diff container that has anchor points for upper | |
|
855 | // sticky header, we need to tell it to re-calculate those | |
|
856 | if (window.updateSticky !== undefined) { | |
|
857 | // potentially our comments change the active window size, so we | |
|
858 | // notify sticky elements | |
|
859 | updateSticky() | |
|
860 | } | |
|
861 | ||
|
862 | return false; | |
|
863 | } | |
|
864 | ||
|
865 | this.toggleLineComments = function(node, show) { | |
|
866 | ||
|
867 | var trElem = $(node).closest('tr') | |
|
868 | ||
|
736 | 869 | if (show === true) { |
|
737 | $filediff.removeClass('hide-comments'); | |
|
870 | // mark outdated comments as visible before the toggle; | |
|
871 | $(trElem).find('.comment-outdated').show(); | |
|
872 | $(trElem).removeClass('hide-line-comments'); | |
|
738 | 873 | } else if (show === false) { |
|
739 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); | |
|
740 |
|
|
|
874 | $(trElem).find('.comment-outdated').hide(); | |
|
875 | $(trElem).addClass('hide-line-comments'); | |
|
741 | 876 | } else { |
|
742 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); | |
|
743 | $filediff.toggleClass('hide-comments'); | |
|
877 | // mark outdated comments as visible before the toggle; | |
|
878 | $(trElem).find('.comment-outdated').show(); | |
|
879 | $(trElem).toggleClass('hide-line-comments'); | |
|
744 | 880 | } |
|
745 | 881 | |
|
746 | 882 | // since we change the height of the diff container that has anchor points for upper |
@@ -751,15 +887,6 b' var CommentsController = function() {' | |||
|
751 | 887 | updateSticky() |
|
752 | 888 | } |
|
753 | 889 | |
|
754 | return false; | |
|
755 | }; | |
|
756 | ||
|
757 | this.toggleLineComments = function(node) { | |
|
758 | self.toggleComments(node, true); | |
|
759 | var $node = $(node); | |
|
760 | // mark outdated comments as visible before the toggle; | |
|
761 | $(node.closest('tr')).find('.comment-outdated').show(); | |
|
762 | $node.closest('tr').toggleClass('hide-line-comments'); | |
|
763 | 890 | }; |
|
764 | 891 | |
|
765 | 892 | this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){ |
@@ -913,63 +1040,58 b' var CommentsController = function() {' | |||
|
913 | 1040 | return commentForm; |
|
914 | 1041 | }; |
|
915 | 1042 | |
|
916 | this.editComment = function(node) { | |
|
1043 | this.editComment = function(node, line_no, f_path) { | |
|
1044 | self.edit = true; | |
|
917 | 1045 | var $node = $(node); |
|
1046 | var $td = $node.closest('td'); | |
|
1047 | ||
|
918 | 1048 | var $comment = $(node).closest('.comment'); |
|
919 |
var comment_id = $comment. |
|
|
920 | var $form = null | |
|
1049 | var comment_id = $($comment).data('commentId'); | |
|
1050 | var isDraft = $($comment).data('commentDraft'); | |
|
1051 | var $editForm = null | |
|
921 | 1052 | |
|
922 | 1053 | var $comments = $node.closest('div.inline-comments'); |
|
923 | 1054 | var $general_comments = null; |
|
924 | var lineno = null; | |
|
925 | 1055 | |
|
926 | 1056 | if($comments.length){ |
|
927 | 1057 | // inline comments setup |
|
928 |
$ |
|
|
929 | lineno = self.getLineNumber(node) | |
|
1058 | $editForm = $comments.find('.comment-inline-form'); | |
|
1059 | line_no = self.getLineNumber(node) | |
|
930 | 1060 | } |
|
931 | 1061 | else{ |
|
932 | 1062 | // general comments setup |
|
933 | 1063 | $comments = $('#comments'); |
|
934 |
$ |
|
|
935 | lineno = $comment[0].id | |
|
1064 | $editForm = $comments.find('.comment-inline-form'); | |
|
1065 | line_no = $comment[0].id | |
|
936 | 1066 | $('#cb-comment-general-form-placeholder').hide(); |
|
937 | 1067 | } |
|
938 | 1068 | |
|
939 | this.edit = true; | |
|
1069 | if ($editForm.length === 0) { | |
|
940 | 1070 | |
|
941 | if (!$form.length) { | |
|
942 | ||
|
1071 | // unhide all comments if they are hidden for a proper REPLY mode | |
|
943 | 1072 | var $filediff = $node.closest('.filediff'); |
|
944 | 1073 | $filediff.removeClass('hide-comments'); |
|
945 | var f_path = $filediff.attr('data-f-path'); | |
|
946 | ||
|
947 | // create a new HTML from template | |
|
948 | 1074 | |
|
949 | var tmpl = $('#cb-comment-inline-form-template').html(); | |
|
950 | tmpl = tmpl.format(escapeHtml(f_path), lineno); | |
|
951 | $form = $(tmpl); | |
|
952 | $comment.after($form) | |
|
1075 | $editForm = self.createNewFormWrapper(f_path, line_no); | |
|
1076 | if(f_path && line_no) { | |
|
1077 | $editForm.addClass('comment-inline-form-edit') | |
|
1078 | } | |
|
953 | 1079 | |
|
954 | var _form = $($form[0]).find('form'); | |
|
1080 | $comment.after($editForm) | |
|
1081 | ||
|
1082 | var _form = $($editForm[0]).find('form'); | |
|
955 | 1083 | var autocompleteActions = ['as_note',]; |
|
956 | 1084 | var commentForm = this.createCommentForm( |
|
957 | _form, lineno, '', autocompleteActions, resolvesCommentId, | |
|
1085 | _form, line_no, '', autocompleteActions, resolvesCommentId, | |
|
958 | 1086 | this.edit, comment_id); |
|
959 | 1087 | var old_comment_text_binary = $comment.attr('data-comment-text'); |
|
960 | 1088 | var old_comment_text = b64DecodeUnicode(old_comment_text_binary); |
|
961 | 1089 | commentForm.cm.setValue(old_comment_text); |
|
962 | 1090 | $comment.hide(); |
|
1091 | tooltipActivate(); | |
|
963 | 1092 | |
|
964 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ | |
|
965 | form: _form, | |
|
966 | parent: $comments, | |
|
967 | lineno: lineno, | |
|
968 | f_path: f_path} | |
|
969 | ); | |
|
970 | ||
|
971 | // set a CUSTOM submit handler for inline comments. | |
|
972 | commentForm.setHandleFormSubmit(function(o) { | |
|
1093 | // set a CUSTOM submit handler for inline comment edit action. | |
|
1094 | commentForm.setHandleFormSubmit(function(o) { | |
|
973 | 1095 | var text = commentForm.cm.getValue(); |
|
974 | 1096 | var commentType = commentForm.getCommentType(); |
|
975 | 1097 | |
@@ -1000,14 +1122,15 b' var CommentsController = function() {' | |||
|
1000 | 1122 | var postData = { |
|
1001 | 1123 | 'text': text, |
|
1002 | 1124 | 'f_path': f_path, |
|
1003 | 'line': lineno, | |
|
1125 | 'line': line_no, | |
|
1004 | 1126 | 'comment_type': commentType, |
|
1127 | 'draft': isDraft, | |
|
1005 | 1128 | 'version': version, |
|
1006 | 1129 | 'csrf_token': CSRF_TOKEN |
|
1007 | 1130 | }; |
|
1008 | 1131 | |
|
1009 | 1132 | var submitSuccessCallback = function(json_data) { |
|
1010 |
$ |
|
|
1133 | $editForm.remove(); | |
|
1011 | 1134 | $comment.show(); |
|
1012 | 1135 | var postData = { |
|
1013 | 1136 | 'text': text, |
@@ -1072,8 +1195,7 b' var CommentsController = function() {' | |||
|
1072 | 1195 | 'commit_id': templateContext.commit_data.commit_id}); |
|
1073 | 1196 | |
|
1074 | 1197 | _submitAjaxPOST( |
|
1075 | previewUrl, postData, successRenderCommit, | |
|
1076 | failRenderCommit | |
|
1198 | previewUrl, postData, successRenderCommit, failRenderCommit | |
|
1077 | 1199 | ); |
|
1078 | 1200 | |
|
1079 | 1201 | try { |
@@ -1084,7 +1206,7 b' var CommentsController = function() {' | |||
|
1084 | 1206 | $comments.find('.cb-comment-add-button').before(html); |
|
1085 | 1207 | |
|
1086 | 1208 | // run global callback on submit |
|
1087 | commentForm.globalSubmitSuccessCallback(); | |
|
1209 | commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id}); | |
|
1088 | 1210 | |
|
1089 | 1211 | } catch (e) { |
|
1090 | 1212 | console.error(e); |
@@ -1101,10 +1223,14 b' var CommentsController = function() {' | |||
|
1101 | 1223 | updateSticky() |
|
1102 | 1224 | } |
|
1103 | 1225 | |
|
1104 | if (window.refreshAllComments !== undefined) { | |
|
1226 | if (window.refreshAllComments !== undefined && !isDraft) { | |
|
1105 | 1227 | // if we have this handler, run it, and refresh all comments boxes |
|
1106 | 1228 | refreshAllComments() |
|
1107 | 1229 | } |
|
1230 | else if (window.refreshDraftComments !== undefined && isDraft) { | |
|
1231 | // if we have this handler, run it, and refresh all comments boxes | |
|
1232 | refreshDraftComments(); | |
|
1233 | } | |
|
1108 | 1234 | |
|
1109 | 1235 | commentForm.setActionButtonsDisabled(false); |
|
1110 | 1236 | |
@@ -1129,66 +1255,122 b' var CommentsController = function() {' | |||
|
1129 | 1255 | }); |
|
1130 | 1256 | } |
|
1131 | 1257 | |
|
1132 |
$ |
|
|
1258 | $editForm.addClass('comment-inline-form-open'); | |
|
1133 | 1259 | }; |
|
1134 | 1260 | |
|
1135 |
this. |
|
|
1136 | var resolvesCommentId = resolutionComment || null; | |
|
1261 | this.attachComment = function(json_data) { | |
|
1262 | var self = this; | |
|
1263 | $.each(json_data, function(idx, val) { | |
|
1264 | var json_data_elem = [val] | |
|
1265 | var isInline = val.comment_f_path && val.comment_lineno | |
|
1266 | ||
|
1267 | if (isInline) { | |
|
1268 | self.attachInlineComment(json_data_elem) | |
|
1269 | } else { | |
|
1270 | self.attachGeneralComment(json_data_elem) | |
|
1271 | } | |
|
1272 | }) | |
|
1273 | ||
|
1274 | } | |
|
1275 | ||
|
1276 | this.attachGeneralComment = function(json_data) { | |
|
1277 | $.each(json_data, function(idx, val) { | |
|
1278 | $('#injected_page_comments').append(val.rendered_text); | |
|
1279 | }) | |
|
1280 | } | |
|
1281 | ||
|
1282 | this.attachInlineComment = function(json_data) { | |
|
1283 | ||
|
1284 | $.each(json_data, function (idx, val) { | |
|
1285 | var line_qry = '*[data-line-no="{0}"]'.format(val.line_no); | |
|
1286 | var html = val.rendered_text; | |
|
1287 | var $inlineComments = $('#' + val.target_id) | |
|
1288 | .find(line_qry) | |
|
1289 | .find('.inline-comments'); | |
|
1290 | ||
|
1291 | var lastComment = $inlineComments.find('.comment-inline').last(); | |
|
1292 | ||
|
1293 | if (lastComment.length === 0) { | |
|
1294 | // first comment, we append simply | |
|
1295 | $inlineComments.find('.reply-thread-container-wrapper').before(html); | |
|
1296 | } else { | |
|
1297 | $(lastComment).after(html) | |
|
1298 | } | |
|
1299 | ||
|
1300 | }) | |
|
1301 | ||
|
1302 | }; | |
|
1303 | ||
|
1304 | this.createNewFormWrapper = function(f_path, line_no) { | |
|
1305 | // create a new reply HTML form from template | |
|
1306 | var tmpl = $('#cb-comment-inline-form-template').html(); | |
|
1307 | tmpl = tmpl.format(escapeHtml(f_path), line_no); | |
|
1308 | return $(tmpl); | |
|
1309 | } | |
|
1310 | ||
|
1311 | this.createComment = function(node, f_path, line_no, resolutionComment) { | |
|
1312 | self.edit = false; | |
|
1137 | 1313 | var $node = $(node); |
|
1138 | 1314 | var $td = $node.closest('td'); |
|
1139 | var $form = $td.find('.comment-inline-form'); | |
|
1140 | this.edit = false; | |
|
1315 | var resolvesCommentId = resolutionComment || null; | |
|
1141 | 1316 | |
|
1142 | if (!$form.length) { | |
|
1317 | var $replyForm = $td.find('.comment-inline-form'); | |
|
1143 | 1318 | |
|
1144 | var $filediff = $node.closest('.filediff'); | |
|
1145 | $filediff.removeClass('hide-comments'); | |
|
1146 | var f_path = $filediff.attr('data-f-path'); | |
|
1147 | var lineno = self.getLineNumber(node); | |
|
1148 | // create a new HTML from template | |
|
1149 | var tmpl = $('#cb-comment-inline-form-template').html(); | |
|
1150 |
|
|
|
1151 | $form = $(tmpl); | |
|
1319 | // if form isn't existing, we're generating a new one and injecting it. | |
|
1320 | if ($replyForm.length === 0) { | |
|
1321 | ||
|
1322 | // unhide/expand all comments if they are hidden for a proper REPLY mode | |
|
1323 | self.toggleLineComments($node, true); | |
|
1324 | ||
|
1325 | $replyForm = self.createNewFormWrapper(f_path, line_no); | |
|
1152 | 1326 | |
|
1153 | 1327 | var $comments = $td.find('.inline-comments'); |
|
1154 | if (!$comments.length) { | |
|
1155 | $comments = $( | |
|
1156 | $('#cb-comments-inline-container-template').html()); | |
|
1157 | $td.append($comments); | |
|
1328 | ||
|
1329 | // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first | |
|
1330 | if ($comments.length===0) { | |
|
1331 | var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no) | |
|
1332 | var $reply_container = $('#cb-comments-inline-container-template') | |
|
1333 | $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn); | |
|
1334 | $td.append($($reply_container).html()); | |
|
1158 | 1335 | } |
|
1159 | 1336 | |
|
1160 | $td.find('.cb-comment-add-button').before($form); | |
|
1337 | // default comment button exists, so we prepend the form for leaving initial comment | |
|
1338 | $td.find('.cb-comment-add-button').before($replyForm); | |
|
1339 | // set marker, that we have a open form | |
|
1340 | var $replyWrapper = $td.find('.reply-thread-container-wrapper') | |
|
1341 | $replyWrapper.addClass('comment-form-active'); | |
|
1161 | 1342 | |
|
1162 | var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno); | |
|
1163 | var _form = $($form[0]).find('form'); | |
|
1343 | var lastComment = $comments.find('.comment-inline').last(); | |
|
1344 | if ($(lastComment).hasClass('comment-outdated')) { | |
|
1345 | $replyWrapper.show(); | |
|
1346 | } | |
|
1347 | ||
|
1348 | var _form = $($replyForm[0]).find('form'); | |
|
1164 | 1349 | var autocompleteActions = ['as_note', 'as_todo']; |
|
1165 | 1350 | var comment_id=null; |
|
1166 | var commentForm = this.createCommentForm( | |
|
1167 | _form, lineno, placeholderText, autocompleteActions, resolvesCommentId, this.edit, comment_id); | |
|
1168 | ||
|
1169 | $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({ | |
|
1170 | form: _form, | |
|
1171 | parent: $td[0], | |
|
1172 | lineno: lineno, | |
|
1173 | f_path: f_path} | |
|
1174 | ); | |
|
1351 | var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no); | |
|
1352 | var commentForm = self.createCommentForm( | |
|
1353 | _form, line_no, placeholderText, autocompleteActions, resolvesCommentId, | |
|
1354 | self.edit, comment_id); | |
|
1175 | 1355 | |
|
1176 | 1356 | // set a CUSTOM submit handler for inline comments. |
|
1177 | 1357 | commentForm.setHandleFormSubmit(function(o) { |
|
1178 | 1358 | var text = commentForm.cm.getValue(); |
|
1179 | 1359 | var commentType = commentForm.getCommentType(); |
|
1180 | 1360 | var resolvesCommentId = commentForm.getResolvesId(); |
|
1361 | var isDraft = commentForm.getDraftState(); | |
|
1181 | 1362 | |
|
1182 | 1363 | if (text === "") { |
|
1183 | 1364 | return; |
|
1184 | 1365 | } |
|
1185 | 1366 | |
|
1186 | if (lineno === undefined) { | |
|
1187 | alert('missing line !'); | |
|
1367 | if (line_no === undefined) { | |
|
1368 | alert('Error: unable to fetch line number for this inline comment !'); | |
|
1188 | 1369 | return; |
|
1189 | 1370 | } |
|
1371 | ||
|
1190 | 1372 | if (f_path === undefined) { |
|
1191 | alert('missing file path !'); | |
|
1373 | alert('Error: unable to fetch file path for this inline comment !'); | |
|
1192 | 1374 | return; |
|
1193 | 1375 | } |
|
1194 | 1376 | |
@@ -1199,66 +1381,79 b' var CommentsController = function() {' | |||
|
1199 | 1381 | var postData = { |
|
1200 | 1382 | 'text': text, |
|
1201 | 1383 | 'f_path': f_path, |
|
1202 | 'line': lineno, | |
|
1384 | 'line': line_no, | |
|
1203 | 1385 | 'comment_type': commentType, |
|
1386 | 'draft': isDraft, | |
|
1204 | 1387 | 'csrf_token': CSRF_TOKEN |
|
1205 | 1388 | }; |
|
1206 | 1389 | if (resolvesCommentId){ |
|
1207 | 1390 | postData['resolves_comment_id'] = resolvesCommentId; |
|
1208 | 1391 | } |
|
1209 | 1392 | |
|
1393 | // submitSuccess for inline commits | |
|
1210 | 1394 | var submitSuccessCallback = function(json_data) { |
|
1211 | $form.remove(); | |
|
1212 | try { | |
|
1213 | var html = json_data.rendered_text; | |
|
1214 | var lineno = json_data.line_no; | |
|
1215 | var target_id = json_data.target_id; | |
|
1395 | ||
|
1396 | $replyForm.remove(); | |
|
1397 | $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active'); | |
|
1398 | ||
|
1399 | try { | |
|
1400 | ||
|
1401 | // inject newly created comments, json_data is {<comment_id>: {}} | |
|
1402 | self.attachInlineComment(json_data) | |
|
1216 | 1403 | |
|
1217 | $comments.find('.cb-comment-add-button').before(html); | |
|
1404 | //mark visually which comment was resolved | |
|
1405 | if (resolvesCommentId) { | |
|
1406 | commentForm.markCommentResolved(resolvesCommentId); | |
|
1407 | } | |
|
1218 | 1408 | |
|
1219 | //mark visually which comment was resolved | |
|
1220 | if (resolvesCommentId) { | |
|
1221 | commentForm.markCommentResolved(resolvesCommentId); | |
|
1409 | // run global callback on submit | |
|
1410 | commentForm.globalSubmitSuccessCallback({ | |
|
1411 | draft: isDraft, | |
|
1412 | comment_id: comment_id | |
|
1413 | }); | |
|
1414 | ||
|
1415 | } catch (e) { | |
|
1416 | console.error(e); | |
|
1222 | 1417 | } |
|
1223 | 1418 | |
|
1224 | // run global callback on submit | |
|
1225 | commentForm.globalSubmitSuccessCallback(); | |
|
1226 | ||
|
1227 | } catch (e) { | |
|
1228 | console.error(e); | |
|
1229 | } | |
|
1230 | ||
|
1231 | // re trigger the linkification of next/prev navigation | |
|
1232 | linkifyComments($('.inline-comment-injected')); | |
|
1233 | timeagoActivate(); | |
|
1234 | tooltipActivate(); | |
|
1235 | ||
|
1236 | 1419 | if (window.updateSticky !== undefined) { |
|
1237 | 1420 | // potentially our comments change the active window size, so we |
|
1238 | 1421 | // notify sticky elements |
|
1239 | 1422 | updateSticky() |
|
1240 | 1423 | } |
|
1241 | 1424 | |
|
1242 | if (window.refreshAllComments !== undefined) { | |
|
1425 | if (window.refreshAllComments !== undefined && !isDraft) { | |
|
1243 | 1426 | // if we have this handler, run it, and refresh all comments boxes |
|
1244 | 1427 | refreshAllComments() |
|
1245 | 1428 | } |
|
1429 | else if (window.refreshDraftComments !== undefined && isDraft) { | |
|
1430 | // if we have this handler, run it, and refresh all comments boxes | |
|
1431 | refreshDraftComments(); | |
|
1432 | } | |
|
1246 | 1433 | |
|
1247 | 1434 | commentForm.setActionButtonsDisabled(false); |
|
1248 | 1435 | |
|
1436 | // re trigger the linkification of next/prev navigation | |
|
1437 | linkifyComments($('.inline-comment-injected')); | |
|
1438 | timeagoActivate(); | |
|
1439 | tooltipActivate(); | |
|
1249 | 1440 | }; |
|
1441 | ||
|
1250 | 1442 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { |
|
1251 | 1443 | var prefix = "Error while submitting comment.\n" |
|
1252 | 1444 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
|
1253 | 1445 | ajaxErrorSwal(message); |
|
1254 | 1446 | commentForm.resetCommentFormState(text) |
|
1255 | 1447 | }; |
|
1448 | ||
|
1256 | 1449 | commentForm.submitAjaxPOST( |
|
1257 | 1450 | commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback); |
|
1258 | 1451 | }); |
|
1259 | 1452 | } |
|
1260 | 1453 | |
|
1261 | $form.addClass('comment-inline-form-open'); | |
|
1454 | // Finally "open" our reply form, since we know there are comments and we have the "attached" old form | |
|
1455 | $replyForm.addClass('comment-inline-form-open'); | |
|
1456 | tooltipActivate(); | |
|
1262 | 1457 | }; |
|
1263 | 1458 | |
|
1264 | 1459 | this.createResolutionComment = function(commentId){ |
@@ -1268,9 +1463,12 b' var CommentsController = function() {' | |||
|
1268 | 1463 | var comment = $('#comment-'+commentId); |
|
1269 | 1464 | var commentData = comment.data(); |
|
1270 | 1465 | if (commentData.commentInline) { |
|
1271 | this.createComment(comment, commentId) | |
|
1466 | var f_path = commentData.fPath; | |
|
1467 | var line_no = commentData.lineNo; | |
|
1468 | //TODO check this if we need to give f_path/line_no | |
|
1469 | this.createComment(comment, f_path, line_no, commentId) | |
|
1272 | 1470 | } else { |
|
1273 |
|
|
|
1471 | this.createGeneralComment('general', "$placeholder", commentId) | |
|
1274 | 1472 | } |
|
1275 | 1473 | |
|
1276 | 1474 | return false; |
@@ -1296,3 +1494,8 b' var CommentsController = function() {' | |||
|
1296 | 1494 | }; |
|
1297 | 1495 | |
|
1298 | 1496 | }; |
|
1497 | ||
|
1498 | window.commentHelp = function(renderer) { | |
|
1499 | var funcData = {'renderer': renderer} | |
|
1500 | return renderTemplate('commentHelpHovercard', funcData) | |
|
1501 | } No newline at end of file |
@@ -42,12 +42,22 b' window.toggleElement = function (elem, t' | |||
|
42 | 42 | var $elem = $(elem); |
|
43 | 43 | var $target = $(target); |
|
44 | 44 | |
|
45 | if ($target.is(':visible') || $target.length === 0) { | |
|
45 | if (target !== undefined) { | |
|
46 | var show = $target.is(':visible') || $target.length === 0; | |
|
47 | } else { | |
|
48 | var show = $elem.hasClass('toggle-off') | |
|
49 | } | |
|
50 | ||
|
51 | if (show) { | |
|
46 | 52 | $target.hide(); |
|
47 | 53 | $elem.html($elem.data('toggleOn')) |
|
54 | $elem.addClass('toggle-on') | |
|
55 | $elem.removeClass('toggle-off') | |
|
48 | 56 | } else { |
|
49 | 57 | $target.show(); |
|
50 | 58 | $elem.html($elem.data('toggleOff')) |
|
59 | $elem.addClass('toggle-off') | |
|
60 | $elem.removeClass('toggle-on') | |
|
51 | 61 | } |
|
52 | 62 | |
|
53 | 63 | return false |
@@ -182,83 +182,34 b' window.ReviewersController = function ()' | |||
|
182 | 182 | if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) { |
|
183 | 183 | // default rule, case for older repo that don't have any rules stored |
|
184 | 184 | self.$rulesList.append( |
|
185 | self.addRule( | |
|
186 | _gettext('All reviewers must vote.')) | |
|
185 | self.addRule(_gettext('All reviewers must vote.')) | |
|
187 | 186 | ); |
|
188 | 187 | return self.forbidUsers |
|
189 | 188 | } |
|
190 | 189 | |
|
191 |
if (data.rules. |
|
|
192 | if (data.rules.voting < 0) { | |
|
193 | self.$rulesList.append( | |
|
194 | self.addRule( | |
|
195 | _gettext('All individual reviewers must vote.')) | |
|
196 | ) | |
|
197 | } else if (data.rules.voting === 1) { | |
|
198 | self.$rulesList.append( | |
|
199 | self.addRule( | |
|
200 | _gettext('At least {0} reviewer must vote.').format(data.rules.voting)) | |
|
201 | ) | |
|
202 | ||
|
203 | } else { | |
|
204 | self.$rulesList.append( | |
|
205 | self.addRule( | |
|
206 | _gettext('At least {0} reviewers must vote.').format(data.rules.voting)) | |
|
207 | ) | |
|
208 | } | |
|
190 | if (data.rules.forbid_adding_reviewers) { | |
|
191 | $('#add_reviewer_input').remove(); | |
|
209 | 192 | } |
|
210 | 193 | |
|
211 |
if (data.rules. |
|
|
212 |
$.each(data.rules. |
|
|
213 | self.$rulesList.append( | |
|
214 | self.addRule(rule_data.text) | |
|
215 | ) | |
|
216 | }); | |
|
217 | } | |
|
218 | ||
|
219 | if (data.rules.use_code_authors_for_review) { | |
|
220 | self.$rulesList.append( | |
|
221 | self.addRule( | |
|
222 | _gettext('Reviewers picked from source code changes.')) | |
|
223 | ) | |
|
194 | if (data.rules_data !== undefined && data.rules_data.forbidden_users !== undefined) { | |
|
195 | $.each(data.rules_data.forbidden_users, function(idx, val){ | |
|
196 | self.forbidUsers.push(val) | |
|
197 | }) | |
|
224 | 198 | } |
|
225 | 199 | |
|
226 | if (data.rules.forbid_adding_reviewers) { | |
|
227 | $('#add_reviewer_input').remove(); | |
|
200 | if (data.rules_humanized !== undefined && data.rules_humanized.length > 0) { | |
|
201 | $.each(data.rules_humanized, function(idx, val) { | |
|
202 | self.$rulesList.append( | |
|
203 | self.addRule(val) | |
|
204 | ) | |
|
205 | }) | |
|
206 | } else { | |
|
207 | // we don't have any rules set, so we inform users about it | |
|
228 | 208 | self.$rulesList.append( |
|
229 | self.addRule( | |
|
230 | _gettext('Adding new reviewers is forbidden.')) | |
|
231 | ) | |
|
232 | } | |
|
233 | ||
|
234 | if (data.rules.forbid_author_to_review) { | |
|
235 | self.forbidUsers.push(data.rules_data.pr_author); | |
|
236 | self.$rulesList.append( | |
|
237 | self.addRule( | |
|
238 | _gettext('Author is not allowed to be a reviewer.')) | |
|
209 | self.addRule(_gettext('No additional review rules set.')) | |
|
239 | 210 | ) |
|
240 | 211 | } |
|
241 | 212 | |
|
242 | if (data.rules.forbid_commit_author_to_review) { | |
|
243 | ||
|
244 | if (data.rules_data.forbidden_users) { | |
|
245 | $.each(data.rules_data.forbidden_users, function (index, member_data) { | |
|
246 | self.forbidUsers.push(member_data) | |
|
247 | }); | |
|
248 | } | |
|
249 | ||
|
250 | self.$rulesList.append( | |
|
251 | self.addRule( | |
|
252 | _gettext('Commit Authors are not allowed to be a reviewer.')) | |
|
253 | ) | |
|
254 | } | |
|
255 | ||
|
256 | // we don't have any rules set, so we inform users about it | |
|
257 | if (self.enabledRules.length === 0) { | |
|
258 | self.addRule( | |
|
259 | _gettext('No review rules set.')) | |
|
260 | } | |
|
261 | ||
|
262 | 213 | return self.forbidUsers |
|
263 | 214 | }; |
|
264 | 215 | |
@@ -1066,14 +1017,14 b' window.ReviewerPresenceController = func' | |||
|
1066 | 1017 | this.handlePresence = function (data) { |
|
1067 | 1018 | if (data.type == 'presence' && data.channel === self.channel) { |
|
1068 | 1019 | this.storeUsers(data.users); |
|
1069 | this.render() | |
|
1020 | this.render(); | |
|
1070 | 1021 | } |
|
1071 | 1022 | }; |
|
1072 | 1023 | |
|
1073 | 1024 | this.handleChannelUpdate = function (data) { |
|
1074 | 1025 | if (data.channel === this.channel) { |
|
1075 | 1026 | this.storeUsers(data.state.users); |
|
1076 | this.render() | |
|
1027 | this.render(); | |
|
1077 | 1028 | } |
|
1078 | 1029 | |
|
1079 | 1030 | }; |
@@ -1085,6 +1036,30 b' window.ReviewerPresenceController = func' | |||
|
1085 | 1036 | |
|
1086 | 1037 | }; |
|
1087 | 1038 | |
|
1039 | window.refreshCommentsSuccess = function(targetNode, counterNode, extraCallback) { | |
|
1040 | var $targetElem = targetNode; | |
|
1041 | var $counterElem = counterNode; | |
|
1042 | ||
|
1043 | return function (data) { | |
|
1044 | var newCount = $(data).data('counter'); | |
|
1045 | if (newCount !== undefined) { | |
|
1046 | var callback = function () { | |
|
1047 | $counterElem.animate({'opacity': 1.00}, 200) | |
|
1048 | $counterElem.html(newCount); | |
|
1049 | }; | |
|
1050 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |
|
1051 | } | |
|
1052 | ||
|
1053 | $targetElem.css('opacity', 1); | |
|
1054 | $targetElem.html(data); | |
|
1055 | tooltipActivate(); | |
|
1056 | ||
|
1057 | if (extraCallback !== undefined) { | |
|
1058 | extraCallback(data) | |
|
1059 | } | |
|
1060 | } | |
|
1061 | } | |
|
1062 | ||
|
1088 | 1063 | window.refreshComments = function (version) { |
|
1089 | 1064 | version = version || templateContext.pull_request_data.pull_request_version || ''; |
|
1090 | 1065 | |
@@ -1109,23 +1084,8 b' window.refreshComments = function (versi' | |||
|
1109 | 1084 | |
|
1110 | 1085 | var $targetElem = $('.comments-content-table'); |
|
1111 | 1086 | $targetElem.css('opacity', 0.3); |
|
1112 | ||
|
1113 | var success = function (data) { | |
|
1114 | var $counterElem = $('#comments-count'); | |
|
1115 | var newCount = $(data).data('counter'); | |
|
1116 | if (newCount !== undefined) { | |
|
1117 | var callback = function () { | |
|
1118 | $counterElem.animate({'opacity': 1.00}, 200) | |
|
1119 | $counterElem.html(newCount); | |
|
1120 | }; | |
|
1121 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |
|
1122 | } | |
|
1123 | ||
|
1124 | $targetElem.css('opacity', 1); | |
|
1125 | $targetElem.html(data); | |
|
1126 | tooltipActivate(); | |
|
1127 | } | |
|
1128 | ||
|
1087 | var $counterElem = $('#comments-count'); | |
|
1088 | var success = refreshCommentsSuccess($targetElem, $counterElem); | |
|
1129 | 1089 | ajaxPOST(loadUrl, data, success, null, {}) |
|
1130 | 1090 | |
|
1131 | 1091 | } |
@@ -1139,7 +1099,7 b' window.refreshTODOs = function (version)' | |||
|
1139 | 1099 | 'repo_name': templateContext.repo_name, |
|
1140 | 1100 | 'version': version, |
|
1141 | 1101 | }; |
|
1142 |
var loadUrl = pyroutes.url('pullrequest_ |
|
|
1102 | var loadUrl = pyroutes.url('pullrequest_todos', params); | |
|
1143 | 1103 | } // commit case |
|
1144 | 1104 | else { |
|
1145 | 1105 | return |
@@ -1153,27 +1113,46 b' window.refreshTODOs = function (version)' | |||
|
1153 | 1113 | var data = {"comments": currentIDs}; |
|
1154 | 1114 | var $targetElem = $('.todos-content-table'); |
|
1155 | 1115 | $targetElem.css('opacity', 0.3); |
|
1156 | ||
|
1157 | var success = function (data) { | |
|
1158 | var $counterElem = $('#todos-count') | |
|
1159 | var newCount = $(data).data('counter'); | |
|
1160 | if (newCount !== undefined) { | |
|
1161 | var callback = function () { | |
|
1162 | $counterElem.animate({'opacity': 1.00}, 200) | |
|
1163 | $counterElem.html(newCount); | |
|
1164 | }; | |
|
1165 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |
|
1166 | } | |
|
1167 | ||
|
1168 | $targetElem.css('opacity', 1); | |
|
1169 | $targetElem.html(data); | |
|
1170 | tooltipActivate(); | |
|
1171 | } | |
|
1116 | var $counterElem = $('#todos-count'); | |
|
1117 | var success = refreshCommentsSuccess($targetElem, $counterElem); | |
|
1172 | 1118 | |
|
1173 | 1119 | ajaxPOST(loadUrl, data, success, null, {}) |
|
1174 | 1120 | |
|
1175 | 1121 | } |
|
1176 | 1122 | |
|
1123 | window.refreshDraftComments = function () { | |
|
1124 | ||
|
1125 | // Pull request case | |
|
1126 | if (templateContext.pull_request_data.pull_request_id !== null) { | |
|
1127 | var params = { | |
|
1128 | 'pull_request_id': templateContext.pull_request_data.pull_request_id, | |
|
1129 | 'repo_name': templateContext.repo_name, | |
|
1130 | }; | |
|
1131 | var loadUrl = pyroutes.url('pullrequest_drafts', params); | |
|
1132 | } // commit case | |
|
1133 | else { | |
|
1134 | return | |
|
1135 | } | |
|
1136 | ||
|
1137 | var data = {}; | |
|
1138 | ||
|
1139 | var $targetElem = $('.drafts-content-table'); | |
|
1140 | $targetElem.css('opacity', 0.3); | |
|
1141 | var $counterElem = $('#drafts-count'); | |
|
1142 | var extraCallback = function(data) { | |
|
1143 | if ($(data).data('counter') == 0){ | |
|
1144 | $('#draftsTable').hide(); | |
|
1145 | } else { | |
|
1146 | $('#draftsTable').show(); | |
|
1147 | } | |
|
1148 | // uncheck on load the select all checkbox | |
|
1149 | $('[name=select_all_drafts]').prop('checked', 0); | |
|
1150 | } | |
|
1151 | var success = refreshCommentsSuccess($targetElem, $counterElem, extraCallback); | |
|
1152 | ||
|
1153 | ajaxPOST(loadUrl, data, success, null, {}) | |
|
1154 | }; | |
|
1155 | ||
|
1177 | 1156 | window.refreshAllComments = function (version) { |
|
1178 | 1157 | version = version || templateContext.pull_request_data.pull_request_version || ''; |
|
1179 | 1158 |
@@ -1,7 +1,5 b'' | |||
|
1 | 1 | /__MAIN_APP__ - launched when rhodecode-app element is attached to DOM |
|
2 | 2 | /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed |
|
3 | /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview | |
|
4 | /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created | |
|
5 | 3 | /notifications - shows new event notifications |
|
6 | 4 | /connection_controller/subscribe - subscribes user to new channels |
|
7 | 5 | /connection_controller/presence - receives presence change messages |
@@ -104,12 +104,6 b' def add_request_user_context(event):' | |||
|
104 | 104 | request.environ['rc_req_id'] = req_id |
|
105 | 105 | |
|
106 | 106 | |
|
107 | def inject_app_settings(event): | |
|
108 | settings = event.app.registry.settings | |
|
109 | # inject info about available permissions | |
|
110 | auth.set_available_permissions(settings) | |
|
111 | ||
|
112 | ||
|
113 | 107 | def scan_repositories_if_enabled(event): |
|
114 | 108 | """ |
|
115 | 109 | This is subscribed to the `pyramid.events.ApplicationCreated` event. It |
@@ -46,6 +46,8 b'' | |||
|
46 | 46 | $pullRequestListTable.DataTable({ |
|
47 | 47 | processing: true, |
|
48 | 48 | serverSide: true, |
|
49 | stateSave: true, | |
|
50 | stateDuration: -1, | |
|
49 | 51 | ajax: { |
|
50 | 52 | "url": "${h.route_path('my_account_pullrequests_data')}", |
|
51 | 53 | "data": function (d) { |
@@ -119,6 +121,10 b'' | |||
|
119 | 121 | if (data['owned']) { |
|
120 | 122 | $(row).addClass('owned'); |
|
121 | 123 | } |
|
124 | }, | |
|
125 | "stateSaveParams": function (settings, data) { | |
|
126 | data.search.search = ""; // Don't save search | |
|
127 | data.start = 0; // don't save pagination | |
|
122 | 128 | } |
|
123 | 129 | }); |
|
124 | 130 | $pullRequestListTable.on('xhr.dt', function (e, settings, json, xhr) { |
@@ -129,6 +135,7 b'' | |||
|
129 | 135 | $pullRequestListTable.css('opacity', 0.3); |
|
130 | 136 | }); |
|
131 | 137 | |
|
138 | ||
|
132 | 139 | // filter |
|
133 | 140 | $('#q_filter').on('keyup', |
|
134 | 141 | $.debounce(250, function () { |
@@ -1225,6 +1225,14 b'' | |||
|
1225 | 1225 | (function () { |
|
1226 | 1226 | "use sctrict"; |
|
1227 | 1227 | |
|
1228 | // details block auto-hide menu | |
|
1229 | $(document).mouseup(function(e) { | |
|
1230 | var container = $('.details-inline-block'); | |
|
1231 | if (!container.is(e.target) && container.has(e.target).length === 0) { | |
|
1232 | $('.details-inline-block[open]').removeAttr('open') | |
|
1233 | } | |
|
1234 | }); | |
|
1235 | ||
|
1228 | 1236 | var $sideBar = $('.right-sidebar'); |
|
1229 | 1237 | var expanded = $sideBar.hasClass('right-sidebar-expanded'); |
|
1230 | 1238 | var sidebarState = templateContext.session_attrs.sidebarState; |
@@ -4,7 +4,7 b'' | |||
|
4 | 4 | ## ${sidebar.comments_table()} |
|
5 | 5 | <%namespace name="base" file="/base/base.mako"/> |
|
6 | 6 | |
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> | |
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, draft_comments=False, existing_ids=None, is_pr=True)"> | |
|
8 | 8 | <% |
|
9 | 9 | if todo_comments: |
|
10 | 10 | cls_ = 'todos-content-table' |
@@ -15,10 +15,13 b'' | |||
|
15 | 15 | # own comments first |
|
16 | 16 | user_id = 0 |
|
17 | 17 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
18 | elif draft_comments: | |
|
19 | cls_ = 'drafts-content-table' | |
|
20 | def sorter(entry): | |
|
21 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
|
18 | 22 | else: |
|
19 | 23 | cls_ = 'comments-content-table' |
|
20 | 24 | def sorter(entry): |
|
21 | user_id = entry.author.user_id | |
|
22 | 25 | return '{}'.format(str(entry.comment_id).zfill(10000)) |
|
23 | 26 | |
|
24 | 27 | existing_ids = existing_ids or [] |
@@ -31,8 +34,12 b'' | |||
|
31 | 34 | <% |
|
32 | 35 | display = '' |
|
33 | 36 | _cls = '' |
|
37 | ## Extra precaution to not show drafts in the sidebar for todo/comments | |
|
38 | if comment_obj.draft and not draft_comments: | |
|
39 | continue | |
|
34 | 40 | %> |
|
35 | 41 | |
|
42 | ||
|
36 | 43 | <% |
|
37 | 44 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) |
|
38 | 45 | prev_comment_ver_index = 0 |
@@ -83,6 +90,11 b'' | |||
|
83 | 90 | % endif |
|
84 | 91 | |
|
85 | 92 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> |
|
93 | % if draft_comments: | |
|
94 | <td style="width: 15px;"> | |
|
95 | ${h.checkbox('submit_draft', id=None, value=comment_obj.comment_id)} | |
|
96 | </td> | |
|
97 | % endif | |
|
86 | 98 | <td class="td-todo-number"> |
|
87 | 99 | <% |
|
88 | 100 | version_info = '' |
@@ -24,8 +24,6 b'' | |||
|
24 | 24 | |
|
25 | 25 | <%def name="main()"> |
|
26 | 26 | <script type="text/javascript"> |
|
27 | // TODO: marcink switch this to pyroutes | |
|
28 | AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}"; | |
|
29 | 27 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; |
|
30 | 28 | </script> |
|
31 | 29 |
@@ -1,4 +1,4 b'' | |||
|
1 | 1 | ## this is a dummy html file for partial rendering on server and sending |
|
2 | 2 | ## generated output via ajax after comment submit |
|
3 | 3 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
4 | ${comment.comment_block(c.co, inline=c.co.is_inline)} | |
|
4 | ${comment.comment_block(c.co, inline=c.co.is_inline, is_new=c.is_new)} |
@@ -3,20 +3,25 b'' | |||
|
3 | 3 | ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
4 | 4 | ## ${comment.comment_block(comment)} |
|
5 | 5 | ## |
|
6 | <%namespace name="base" file="/base/base.mako"/> | |
|
6 | 7 |
|
|
7 | 8 | <%! |
|
8 | 9 | from rhodecode.lib import html_filters |
|
9 | 10 | %> |
|
10 | 11 | |
|
11 | <%namespace name="base" file="/base/base.mako"/> | |
|
12 | <%def name="comment_block(comment, inline=False, active_pattern_entries=None)"> | |
|
12 | ||
|
13 | <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)"> | |
|
13 | 14 | |
|
14 | 15 | <% |
|
15 |
|
|
|
16 |
|
|
|
16 | from rhodecode.model.comment import CommentsModel | |
|
17 | comment_model = CommentsModel() | |
|
18 | ||
|
19 | comment_ver = comment.get_index_version(getattr(c, 'versions', [])) | |
|
20 | latest_ver = len(getattr(c, 'versions', [])) | |
|
21 | visible_for_user = True | |
|
22 | if comment.draft: | |
|
23 | visible_for_user = comment.user_id == c.rhodecode_user.user_id | |
|
17 | 24 | %> |
|
18 | <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %> | |
|
19 | <% latest_ver = len(getattr(c, 'versions', [])) %> | |
|
20 | 25 | |
|
21 | 26 | % if inline: |
|
22 | 27 | <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %> |
@@ -24,6 +29,7 b'' | |||
|
24 | 29 | <% outdated_at_ver = comment.older_than_version(c.at_version_num) %> |
|
25 | 30 | % endif |
|
26 | 31 | |
|
32 | % if visible_for_user: | |
|
27 | 33 | <div class="comment |
|
28 | 34 | ${'comment-inline' if inline else 'comment-general'} |
|
29 | 35 | ${'comment-outdated' if outdated_at_ver else 'comment-current'}" |
@@ -31,14 +37,26 b'' | |||
|
31 | 37 | line="${comment.line_no}" |
|
32 | 38 | data-comment-id="${comment.comment_id}" |
|
33 | 39 | data-comment-type="${comment.comment_type}" |
|
40 | data-comment-draft=${h.json.dumps(comment.draft)} | |
|
34 | 41 | data-comment-renderer="${comment.renderer}" |
|
35 | 42 | data-comment-text="${comment.text | html_filters.base64,n}" |
|
43 | data-comment-f-path="${comment.f_path}" | |
|
36 | 44 | data-comment-line-no="${comment.line_no}" |
|
37 | 45 | data-comment-inline=${h.json.dumps(inline)} |
|
38 | 46 | style="${'display: none;' if outdated_at_ver else ''}"> |
|
39 | 47 | |
|
40 | 48 | <div class="meta"> |
|
41 | 49 | <div class="comment-type-label"> |
|
50 | % if comment.draft: | |
|
51 | <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}."> | |
|
52 | DRAFT | |
|
53 | </div> | |
|
54 | % elif is_new: | |
|
55 | <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}."> | |
|
56 | NEW | |
|
57 | </div> | |
|
58 | % endif | |
|
59 | ||
|
42 | 60 | <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}"> |
|
43 | 61 | |
|
44 | 62 | ## TODO COMMENT |
@@ -90,7 +108,7 b'' | |||
|
90 | 108 | |
|
91 | 109 | </div> |
|
92 | 110 | </div> |
|
93 | ||
|
111 | ## NOTE 0 and .. => because we disable it for now until UI ready | |
|
94 | 112 | % if 0 and comment.status_change: |
|
95 | 113 | <div class="pull-left"> |
|
96 | 114 | <span class="tag authortag tooltip" title="${_('Status from pull request.')}"> |
@@ -100,10 +118,12 b'' | |||
|
100 | 118 | </span> |
|
101 | 119 | </div> |
|
102 | 120 | % endif |
|
103 | ||
|
121 | ## Since only author can see drafts, we don't show it | |
|
122 | % if not comment.draft: | |
|
104 | 123 | <div class="author ${'author-inline' if inline else 'author-general'}"> |
|
105 | 124 | ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)} |
|
106 | 125 | </div> |
|
126 | % endif | |
|
107 | 127 | |
|
108 | 128 | <div class="date"> |
|
109 | 129 | ${h.age_component(comment.modified_at, time_is_local=True)} |
@@ -164,7 +184,7 b'' | |||
|
164 | 184 | % if inline: |
|
165 | 185 | <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> |
|
166 | 186 | % if outdated_at_ver: |
|
167 |
<code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"> |
|
|
187 | <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> | |
|
168 | 188 | <code class="action-divider">|</code> |
|
169 | 189 | % elif comment_ver: |
|
170 | 190 | <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> |
@@ -210,11 +230,17 b'' | |||
|
210 | 230 | %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): |
|
211 | 231 | <div class="dropdown-divider"></div> |
|
212 | 232 | <div class="dropdown-item"> |
|
213 | <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a> | |
|
233 | <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a> | |
|
214 | 234 | </div> |
|
215 | 235 | <div class="dropdown-item"> |
|
216 | 236 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a> |
|
217 | 237 | </div> |
|
238 | ## Only available in EE edition | |
|
239 | % if comment.draft and c.rhodecode_edition_id == 'EE': | |
|
240 | <div class="dropdown-item"> | |
|
241 | <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a> | |
|
242 | </div> | |
|
243 | % endif | |
|
218 | 244 | %else: |
|
219 | 245 | <div class="dropdown-divider"></div> |
|
220 | 246 | <div class="dropdown-item"> |
@@ -252,6 +278,7 b'' | |||
|
252 | 278 | </div> |
|
253 | 279 | |
|
254 | 280 | </div> |
|
281 | % endif | |
|
255 | 282 | </%def> |
|
256 | 283 | |
|
257 | 284 | ## generate main comments |
@@ -298,10 +325,9 b'' | |||
|
298 | 325 | ## inject form here |
|
299 | 326 | </div> |
|
300 | 327 | <script type="text/javascript"> |
|
301 | var lineNo = 'general'; | |
|
302 | 328 | var resolvesCommentId = null; |
|
303 | 329 | var generalCommentForm = Rhodecode.comments.createGeneralComment( |
|
304 |
|
|
|
330 | 'general', "${placeholder}", resolvesCommentId); | |
|
305 | 331 | |
|
306 | 332 | // set custom success callback on rangeCommit |
|
307 | 333 | % if is_compare: |
@@ -311,6 +337,7 b'' | |||
|
311 | 337 | var text = self.cm.getValue(); |
|
312 | 338 | var status = self.getCommentStatus(); |
|
313 | 339 | var commentType = self.getCommentType(); |
|
340 | var isDraft = self.getDraftState(); | |
|
314 | 341 | |
|
315 | 342 | if (text === "" && !status) { |
|
316 | 343 | return; |
@@ -337,6 +364,7 b'' | |||
|
337 | 364 | 'text': text, |
|
338 | 365 | 'changeset_status': status, |
|
339 | 366 | 'comment_type': commentType, |
|
367 | 'draft': isDraft, | |
|
340 | 368 | 'commit_ids': commitIds, |
|
341 | 369 | 'csrf_token': CSRF_TOKEN |
|
342 | 370 | }; |
@@ -371,7 +399,7 b'' | |||
|
371 | 399 | |
|
372 | 400 | <div class="comment-area-write" style="display: block;"> |
|
373 | 401 | <div id="edit-container"> |
|
374 |
<div style="padding: |
|
|
402 | <div style="padding: 20px 0px 0px 0;"> | |
|
375 | 403 | ${_('You need to be logged in to leave comments.')} |
|
376 | 404 | <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> |
|
377 | 405 | </div> |
@@ -430,7 +458,7 b'' | |||
|
430 | 458 | </div> |
|
431 | 459 | |
|
432 | 460 | <div class="comment-area-write" style="display: block;"> |
|
433 | <div id="edit-container_${lineno_id}"> | |
|
461 | <div id="edit-container_${lineno_id}" style="margin-top: -1px"> | |
|
434 | 462 | <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea> |
|
435 | 463 | </div> |
|
436 | 464 | <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;"> |
@@ -477,39 +505,50 b'' | |||
|
477 | 505 | <div class="action-buttons-extra"></div> |
|
478 | 506 | % endif |
|
479 | 507 | |
|
480 |
<input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_(' |
|
|
508 | <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')"> | |
|
509 | ||
|
510 | % if form_type == 'inline': | |
|
511 | % if c.rhodecode_edition_id == 'EE': | |
|
512 | ## Disable the button for CE, the "real" validation is in the backend code anyway | |
|
513 | <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')"> | |
|
514 | % else: | |
|
515 | <input class="btn btn-warning comment-button-input submit-draft-action disabled" disabled="disabled" type="submit" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode"> | |
|
516 | % endif | |
|
517 | % endif | |
|
518 | ||
|
519 | % if review_statuses: | |
|
520 | <div class="comment-status-box"> | |
|
521 | <select id="change_status_${lineno_id}" name="changeset_status"> | |
|
522 | <option></option> ## Placeholder | |
|
523 | % for status, lbl in review_statuses: | |
|
524 | <option value="${status}" data-status="${status}">${lbl}</option> | |
|
525 | %if is_pull_request and change_status and status in ('approved', 'rejected'): | |
|
526 | <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option> | |
|
527 | %endif | |
|
528 | % endfor | |
|
529 | </select> | |
|
530 | </div> | |
|
531 | % endif | |
|
481 | 532 | |
|
482 | 533 | ## inline for has a file, and line-number together with cancel hide button. |
|
483 | 534 | % if form_type == 'inline': |
|
484 | 535 | <input type="hidden" name="f_path" value="{0}"> |
|
485 | 536 | <input type="hidden" name="line" value="${lineno_id}"> |
|
486 | 537 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> |
|
487 | ${_('Cancel')} | |
|
538 | <i class="icon-cancel-circled2"></i> | |
|
488 | 539 | </button> |
|
489 | 540 | % endif |
|
490 | 541 | </div> |
|
491 | 542 |
|
|
492 | % if review_statuses: | |
|
493 | <div class="status_box"> | |
|
494 | <select id="change_status_${lineno_id}" name="changeset_status"> | |
|
495 | <option></option> ## Placeholder | |
|
496 | % for status, lbl in review_statuses: | |
|
497 | <option value="${status}" data-status="${status}">${lbl}</option> | |
|
498 | %if is_pull_request and change_status and status in ('approved', 'rejected'): | |
|
499 | <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option> | |
|
500 | %endif | |
|
501 | % endfor | |
|
502 | </select> | |
|
503 | </div> | |
|
504 | % endif | |
|
505 | ||
|
506 | 543 | <div class="toolbar-text"> |
|
507 | 544 | <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %> |
|
508 |
${_(' |
|
|
509 | <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> | |
|
510 | ${_('and')} | |
|
511 | <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span> | |
|
512 | ${_('actions supported.')} | |
|
545 | <span>${_('{} is supported.').format(renderer_url)|n} | |
|
546 | ||
|
547 | <i class="icon-info-circled tooltip-hovercard" | |
|
548 | data-hovercard-alt="ALT" | |
|
549 | data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')" | |
|
550 | data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i> | |
|
551 | </span> | |
|
513 | 552 | </div> |
|
514 | 553 | </div> |
|
515 | 554 |
@@ -104,7 +104,9 b'' | |||
|
104 | 104 | %for commit in c.commit_ranges: |
|
105 | 105 | ## commit range header for each individual diff |
|
106 | 106 | <h3> |
|
107 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a> | |
|
107 | <a class="tooltip-hovercard revision" data-hovercard-alt="Commit: ${commit.short_id}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=commit.raw_id)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}"> | |
|
108 | ${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))} | |
|
109 | </a> | |
|
108 | 110 | </h3> |
|
109 | 111 | |
|
110 | 112 | ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])} |
@@ -1,3 +1,4 b'' | |||
|
1 | <%namespace name="base" file="/base/base.mako"/> | |
|
1 | 2 | <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/> |
|
2 | 3 | |
|
3 | 4 | <%def name="diff_line_anchor(commit, filename, line, type)"><% |
@@ -74,24 +75,9 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
74 | 75 | |
|
75 | 76 | <div class="js-template" id="cb-comment-inline-form-template"> |
|
76 | 77 | <div class="comment-inline-form ac"> |
|
77 | ||
|
78 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
|
78 | %if not c.rhodecode_user.is_default: | |
|
79 | 79 | ## render template for inline comments |
|
80 | 80 | ${commentblock.comment_form(form_type='inline')} |
|
81 | %else: | |
|
82 | ${h.form('', class_='inline-form comment-form-login', method='get')} | |
|
83 | <div class="pull-left"> | |
|
84 | <div class="comment-help pull-right"> | |
|
85 | ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a> | |
|
86 | </div> | |
|
87 | </div> | |
|
88 | <div class="comment-button pull-right"> | |
|
89 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> | |
|
90 | ${_('Cancel')} | |
|
91 | </button> | |
|
92 | </div> | |
|
93 | <div class="clearfix"></div> | |
|
94 | ${h.end_form()} | |
|
95 | 81 | %endif |
|
96 | 82 | </div> |
|
97 | 83 | </div> |
@@ -287,7 +273,7 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
287 | 273 | <label for="filediff-collapse-${id(filediff)}" class="filediff-heading"> |
|
288 | 274 | <% |
|
289 | 275 | file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values() |
|
290 | total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated] | |
|
276 | total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not (_c.outdated or _c.draft)] | |
|
291 | 277 | %> |
|
292 | 278 | <div class="filediff-collapse-indicator icon-"></div> |
|
293 | 279 | |
@@ -327,7 +313,7 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
327 | 313 | </label> |
|
328 | 314 | |
|
329 | 315 | ${diff_menu(filediff, use_comments=use_comments)} |
|
330 | <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}"> | |
|
316 | <table id="file-${h.safeid(h.safe_unicode(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}"> | |
|
331 | 317 | |
|
332 | 318 | ## new/deleted/empty content case |
|
333 | 319 | % if not filediff.hunks: |
@@ -626,8 +612,10 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
626 | 612 | |
|
627 | 613 | % if use_comments: |
|
628 | 614 | | |
|
629 |
<a href="#" onclick=" |
|
|
630 | <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span> | |
|
615 | <a href="#" onclick="Rhodecode.comments.toggleDiffComments(this);return toggleElement(this)" | |
|
616 | data-toggle-on="${_('Hide comments')}" | |
|
617 | data-toggle-off="${_('Show comments')}"> | |
|
618 | <span class="hide-comment-button">${_('Hide comments')}</span> | |
|
631 | 619 | </a> |
|
632 | 620 | % endif |
|
633 | 621 | |
@@ -637,23 +625,37 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
637 | 625 | </%def> |
|
638 | 626 | |
|
639 | 627 | |
|
640 | <%def name="inline_comments_container(comments, active_pattern_entries=None)"> | |
|
628 | <%def name="inline_comments_container(comments, active_pattern_entries=None, line_no='', f_path='')"> | |
|
641 | 629 | |
|
642 | 630 | <div class="inline-comments"> |
|
643 | 631 | %for comment in comments: |
|
644 | 632 | ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)} |
|
645 | 633 | %endfor |
|
646 | % if comments and comments[-1].outdated: | |
|
647 | <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}"> | |
|
648 | ${_('Add another comment')} | |
|
649 | </span> | |
|
650 | % else: | |
|
651 | <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button"> | |
|
652 | ${_('Add another comment')} | |
|
653 | </span> | |
|
654 | % endif | |
|
634 | ||
|
635 | <% | |
|
636 | extra_class = '' | |
|
637 | extra_style = '' | |
|
638 | ||
|
639 | if comments and comments[-1].outdated_at_version(c.at_version_num): | |
|
640 | extra_class = ' comment-outdated' | |
|
641 | extra_style = 'display: none;' | |
|
642 | ||
|
643 | %> | |
|
655 | 644 | |
|
645 | <div class="reply-thread-container-wrapper${extra_class}" style="${extra_style}"> | |
|
646 | <div class="reply-thread-container${extra_class}"> | |
|
647 | <div class="reply-thread-gravatar"> | |
|
648 | ${base.gravatar(c.rhodecode_user.email, 20, tooltip=True, user=c.rhodecode_user)} | |
|
649 | </div> | |
|
650 | <div class="reply-thread-reply-button"> | |
|
651 | ## initial reply button, some JS logic can append here a FORM to leave a first comment. | |
|
652 | <button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">Reply...</button> | |
|
653 | </div> | |
|
654 | <div class="reply-thread-last"></div> | |
|
655 | </div> | |
|
656 | </div> | |
|
656 | 657 | </div> |
|
658 | ||
|
657 | 659 | </%def> |
|
658 | 660 | |
|
659 | 661 | <%! |
@@ -711,16 +713,19 b' def get_comments_for(diff_type, comments' | |||
|
711 | 713 | data-line-no="${line.original.lineno}" |
|
712 | 714 | > |
|
713 | 715 | |
|
714 | <% line_old_comments = None %> | |
|
716 | <% line_old_comments, line_old_comments_no_drafts = None, None %> | |
|
715 | 717 | %if line.original.get_comment_args: |
|
716 | <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %> | |
|
718 | <% | |
|
719 | line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) | |
|
720 | line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else [] | |
|
721 | has_outdated = any([x.outdated for x in line_old_comments_no_drafts]) | |
|
722 | %> | |
|
717 | 723 | %endif |
|
718 | %if line_old_comments: | |
|
719 | <% has_outdated = any([x.outdated for x in line_old_comments]) %> | |
|
724 | %if line_old_comments_no_drafts: | |
|
720 | 725 | % if has_outdated: |
|
721 |
<i class="tooltip icon-comment-toggle" title="${_(' |
|
|
726 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
722 | 727 | % else: |
|
723 |
<i class="tooltip icon-comment" title="${_(' |
|
|
728 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
724 | 729 | % endif |
|
725 | 730 | %endif |
|
726 | 731 | </td> |
@@ -734,16 +739,18 b' def get_comments_for(diff_type, comments' | |||
|
734 | 739 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a> |
|
735 | 740 | %endif |
|
736 | 741 | </td> |
|
742 | ||
|
743 | <% line_no = 'o{}'.format(line.original.lineno) %> | |
|
737 | 744 | <td class="cb-content ${action_class(line.original.action)}" |
|
738 |
data-line-no=" |
|
|
745 | data-line-no="${line_no}" | |
|
739 | 746 | > |
|
740 | 747 | %if use_comments and line.original.lineno: |
|
741 | ${render_add_comment_button()} | |
|
748 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
|
742 | 749 | %endif |
|
743 | 750 | <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span> |
|
744 | 751 | |
|
745 | 752 | %if use_comments and line.original.lineno and line_old_comments: |
|
746 | ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries)} | |
|
753 | ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |
|
747 | 754 | %endif |
|
748 | 755 | |
|
749 | 756 | </td> |
@@ -752,18 +759,20 b' def get_comments_for(diff_type, comments' | |||
|
752 | 759 | > |
|
753 | 760 | <div> |
|
754 | 761 |
|
|
762 | <% line_new_comments, line_new_comments_no_drafts = None, None %> | |
|
755 | 763 | %if line.modified.get_comment_args: |
|
756 | <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %> | |
|
757 | %else: | |
|
758 | <% line_new_comments = None%> | |
|
764 | <% | |
|
765 | line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) | |
|
766 | line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else [] | |
|
767 | has_outdated = any([x.outdated for x in line_new_comments_no_drafts]) | |
|
768 | %> | |
|
759 | 769 | %endif |
|
760 | %if line_new_comments: | |
|
761 | 770 | |
|
762 | <% has_outdated = any([x.outdated for x in line_new_comments]) %> | |
|
771 | %if line_new_comments_no_drafts: | |
|
763 | 772 | % if has_outdated: |
|
764 |
<i class="tooltip icon-comment-toggle" title="${_(' |
|
|
773 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
765 | 774 | % else: |
|
766 |
<i class="tooltip icon-comment" title="${_(' |
|
|
775 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
767 | 776 | % endif |
|
768 | 777 | %endif |
|
769 | 778 | </div> |
@@ -778,22 +787,25 b' def get_comments_for(diff_type, comments' | |||
|
778 | 787 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a> |
|
779 | 788 | %endif |
|
780 | 789 | </td> |
|
790 | ||
|
791 | <% line_no = 'n{}'.format(line.modified.lineno) %> | |
|
781 | 792 | <td class="cb-content ${action_class(line.modified.action)}" |
|
782 |
data-line-no=" |
|
|
793 | data-line-no="${line_no}" | |
|
783 | 794 | > |
|
784 | 795 | %if use_comments and line.modified.lineno: |
|
785 | ${render_add_comment_button()} | |
|
796 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
|
786 | 797 | %endif |
|
787 | 798 | <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span> |
|
788 | %if use_comments and line.modified.lineno and line_new_comments: | |
|
789 | ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)} | |
|
790 | %endif | |
|
791 | 799 | % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']: |
|
792 | 800 | <div class="nav-chunk" style="visibility: hidden"> |
|
793 | 801 | <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i> |
|
794 | 802 | </div> |
|
795 | 803 | <% chunk_count +=1 %> |
|
796 | 804 | % endif |
|
805 | %if use_comments and line.modified.lineno and line_new_comments: | |
|
806 | ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |
|
807 | %endif | |
|
808 | ||
|
797 | 809 | </td> |
|
798 | 810 | </tr> |
|
799 | 811 | %endfor |
@@ -814,20 +826,22 b' def get_comments_for(diff_type, comments' | |||
|
814 | 826 | <td class="cb-data ${action_class(action)}"> |
|
815 | 827 | <div> |
|
816 | 828 |
|
|
817 | %if comments_args: | |
|
818 | <% comments = get_comments_for('unified', inline_comments, *comments_args) %> | |
|
819 |
% |
|
|
820 | <% comments = None %> | |
|
821 | %endif | |
|
829 | <% comments, comments_no_drafts = None, None %> | |
|
830 | %if comments_args: | |
|
831 | <% | |
|
832 | comments = get_comments_for('unified', inline_comments, *comments_args) | |
|
833 | comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else [] | |
|
834 | has_outdated = any([x.outdated for x in comments_no_drafts]) | |
|
835 | %> | |
|
836 | %endif | |
|
822 | 837 | |
|
823 | % if comments: | |
|
824 | <% has_outdated = any([x.outdated for x in comments]) %> | |
|
825 | % if has_outdated: | |
|
826 | <i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
827 | % else: | |
|
828 | <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
838 | % if comments_no_drafts: | |
|
839 | % if has_outdated: | |
|
840 | <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
841 | % else: | |
|
842 | <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
|
843 | % endif | |
|
829 | 844 | % endif |
|
830 | % endif | |
|
831 | 845 | </div> |
|
832 | 846 | </td> |
|
833 | 847 | <td class="cb-lineno ${action_class(action)}" |
@@ -850,15 +864,16 b' def get_comments_for(diff_type, comments' | |||
|
850 | 864 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a> |
|
851 | 865 | %endif |
|
852 | 866 | </td> |
|
867 | <% line_no = '{}{}'.format(new_line_no and 'n' or 'o', new_line_no or old_line_no) %> | |
|
853 | 868 | <td class="cb-content ${action_class(action)}" |
|
854 | data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}" | |
|
869 | data-line-no="${line_no}" | |
|
855 | 870 | > |
|
856 | 871 | %if use_comments: |
|
857 | ${render_add_comment_button()} | |
|
872 | ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])} | |
|
858 | 873 | %endif |
|
859 | 874 | <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span> |
|
860 | 875 | %if use_comments and comments: |
|
861 | ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)} | |
|
876 | ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])} | |
|
862 | 877 | %endif |
|
863 | 878 | </td> |
|
864 | 879 | </tr> |
@@ -879,10 +894,12 b' def get_comments_for(diff_type, comments' | |||
|
879 | 894 | </%def>file changes |
|
880 | 895 | |
|
881 | 896 | |
|
882 | <%def name="render_add_comment_button()"> | |
|
883 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)"> | |
|
897 | <%def name="render_add_comment_button(line_no='', f_path='')"> | |
|
898 | % if not c.rhodecode_user.is_default: | |
|
899 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)"> | |
|
884 | 900 | <span><i class="icon-comment"></i></span> |
|
885 | 901 | </button> |
|
902 | % endif | |
|
886 | 903 | </%def> |
|
887 | 904 | |
|
888 | 905 | <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)"> |
@@ -108,7 +108,26 b'' | |||
|
108 | 108 | <i class="icon-cancel-circled2"></i> |
|
109 | 109 | </div> |
|
110 | 110 | <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div> |
|
111 | <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a> | |
|
111 | ||
|
112 | <div id="rev_range_action" class="btn-group btn-group-actions" style="display:none;"> | |
|
113 | <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a> | |
|
114 | ||
|
115 | <a class="btn btn-success btn-sm btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button"> | |
|
116 | <i class="icon-down"></i> | |
|
117 | </a> | |
|
118 | ||
|
119 | <div class="btn-action-switcher-container right-align"> | |
|
120 | <ul class="btn-action-switcher" role="menu" style="min-width: 220px; width: max-content"> | |
|
121 | <li> | |
|
122 | ## JS fills the URL | |
|
123 | <a id="rev_range_combined_url" class="btn btn-primary btn-sm" href=""> | |
|
124 | ${_('Show combined diff')} | |
|
125 | </a> | |
|
126 | </li> | |
|
127 | </ul> | |
|
128 | </div> | |
|
129 | </div> | |
|
130 | ||
|
112 | 131 | </th> |
|
113 | 132 | |
|
114 | 133 | ## commit message expand arrow |
@@ -147,6 +166,9 b'' | |||
|
147 | 166 | var $commitRangeMore = $('#rev_range_more'); |
|
148 | 167 | var $commitRangeContainer = $('#rev_range_container'); |
|
149 | 168 | var $commitRangeClear = $('#rev_range_clear'); |
|
169 | var $commitRangeAction = $('#rev_range_action'); | |
|
170 | var $commitRangeCombinedUrl = $('#rev_range_combined_url'); | |
|
171 | var $compareFork = $('#compare_fork_button'); | |
|
150 | 172 | |
|
151 | 173 | var checkboxRangeSelector = function(e){ |
|
152 | 174 | var selectedCheckboxes = []; |
@@ -169,9 +191,8 b'' | |||
|
169 | 191 | } |
|
170 | 192 | |
|
171 | 193 | if (selectedCheckboxes.length > 0) { |
|
172 |
$ |
|
|
194 | $compareFork.hide(); | |
|
173 | 195 | var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data(); |
|
174 | ||
|
175 | 196 | var revStart = commitStart.commitId; |
|
176 | 197 | |
|
177 | 198 | var commitEnd = $(selectedCheckboxes[0]).data(); |
@@ -181,7 +202,9 b'' | |||
|
181 | 202 | var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId); |
|
182 | 203 | |
|
183 | 204 | var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd}); |
|
184 | var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end); | |
|
205 | var urlCombined = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd, 'redirect_combined': '1'}); | |
|
206 | ||
|
207 | var link = _gettext('Show commit range {0}<i class="icon-angle-right"></i>{1}').format(lbl_start, lbl_end); | |
|
185 | 208 | |
|
186 | 209 | if (selectedCheckboxes.length > 1) { |
|
187 | 210 | $commitRangeClear.show(); |
@@ -192,9 +215,12 b'' | |||
|
192 | 215 | .html(link) |
|
193 | 216 | .show(); |
|
194 | 217 | |
|
218 | $commitRangeCombinedUrl.attr('href', urlCombined); | |
|
219 | $commitRangeAction.show(); | |
|
195 | 220 | |
|
196 | 221 | } else { |
|
197 | 222 | $commitRangeContainer.hide(); |
|
223 | $commitRangeAction.hide(); | |
|
198 | 224 | $commitRangeClear.show(); |
|
199 | 225 | $commitRangeMore.show(); |
|
200 | 226 | } |
@@ -212,6 +238,7 b'' | |||
|
212 | 238 | $commitRangeContainer.hide(); |
|
213 | 239 | $commitRangeClear.hide(); |
|
214 | 240 | $commitRangeMore.hide(); |
|
241 | $commitRangeAction.hide(); | |
|
215 | 242 | |
|
216 | 243 | %if c.branch_name: |
|
217 | 244 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'}); |
@@ -220,7 +247,7 b'' | |||
|
220 | 247 | var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'}); |
|
221 | 248 | open_new_pull_request.attr('href', _url); |
|
222 | 249 | %endif |
|
223 |
$ |
|
|
250 | $compareFork.show(); | |
|
224 | 251 | } |
|
225 | 252 | }; |
|
226 | 253 |
@@ -397,7 +397,10 b'' | |||
|
397 | 397 | % endif |
|
398 | 398 | </%def> |
|
399 | 399 | |
|
400 | <%def name="pullrequest_updated_on(updated_on)"> | |
|
400 | <%def name="pullrequest_updated_on(updated_on, pr_version=None)"> | |
|
401 | % if pr_version: | |
|
402 | <code>v${pr_version}</code> | |
|
403 | % endif | |
|
401 | 404 | ${h.age_component(h.time_to_utcdatetime(updated_on))} |
|
402 | 405 | </%def> |
|
403 | 406 | |
@@ -456,7 +459,7 b'' | |||
|
456 | 459 | </div> |
|
457 | 460 | |
|
458 | 461 | <div class="markup-form-area-write" style="display: block;"> |
|
459 | <div id="edit-container_${form_id}"> | |
|
462 | <div id="edit-container_${form_id}" style="margin-top: -1px"> | |
|
460 | 463 | <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea> |
|
461 | 464 | </div> |
|
462 | 465 | <div id="preview-container_${form_id}" class="clearfix" style="display: none;"> |
@@ -237,6 +237,24 b' if (show_disabled) {' | |||
|
237 | 237 | |
|
238 | 238 | </script> |
|
239 | 239 | |
|
240 | <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate"> | |
|
241 | ||
|
242 | <div> | |
|
243 | Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/> | |
|
244 | Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/> | |
|
245 | <br/> | |
|
246 | Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/> | |
|
247 | <br/> | |
|
248 | <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/> | |
|
249 | They are permanent until deleted, or converted to regular comments.<br/> | |
|
250 | <br/> | |
|
251 | <br/> | |
|
252 | </div> | |
|
253 | ||
|
254 | </script> | |
|
255 | ||
|
256 | ||
|
257 | ||
|
240 | 258 | ##// END OF EJS Templates |
|
241 | 259 | </div> |
|
242 | 260 |
@@ -337,7 +337,6 b' text_monospace = "\'Menlo\', \'Liberation M' | |||
|
337 | 337 | div.markdown-block img { |
|
338 | 338 | border-style: none; |
|
339 | 339 | background-color: #fff; |
|
340 | padding-right: 20px; | |
|
341 | 340 | max-width: 100% |
|
342 | 341 | } |
|
343 | 342 | |
@@ -395,6 +394,13 b' text_monospace = "\'Menlo\', \'Liberation M' | |||
|
395 | 394 | background-color: #eeeeee |
|
396 | 395 | } |
|
397 | 396 | |
|
397 | div.markdown-block p { | |
|
398 | margin-top: 0; | |
|
399 | margin-bottom: 16px; | |
|
400 | padding: 0; | |
|
401 | line-height: unset; | |
|
402 | } | |
|
403 | ||
|
398 | 404 | div.markdown-block code, |
|
399 | 405 | div.markdown-block pre, |
|
400 | 406 | div.markdown-block #ws, |
@@ -82,8 +82,8 b'' | |||
|
82 | 82 | <p> |
|
83 | 83 | <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/> |
|
84 | 84 | |
|
85 |
Super-admins can see detail |
|
|
86 | Please include the above link for further details of this exception. | |
|
85 | Super-admins can see details of the above error in the exception tracker found under | |
|
86 | <a href="${h.route_url('admin_settings_exception_tracker')}">admin > settings > exception tracker</a>. | |
|
87 | 87 | </p> |
|
88 | 88 | </div> |
|
89 | 89 | % endif |
@@ -29,7 +29,7 b'' | |||
|
29 | 29 | </a> |
|
30 | 30 | |
|
31 | 31 | <div class="btn-action-switcher-container right-align"> |
|
32 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px"> | |
|
32 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |
|
33 | 33 | <li> |
|
34 | 34 | <a class="action_button" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}"> |
|
35 | 35 | <i class="icon-upload"></i> |
@@ -44,18 +44,41 b'' | |||
|
44 | 44 | % endif |
|
45 | 45 | |
|
46 | 46 | % if c.enable_downloads: |
|
47 | <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %> | |
|
48 | <div class="btn btn-default new-file"> | |
|
49 |
|
|
|
50 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}"> | |
|
51 | ${_('Download full tree ZIP')} | |
|
47 | <% | |
|
48 | at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) | |
|
49 | if c.f_path == '/': | |
|
50 | label = _('Full tree as {}') | |
|
51 | _query = {'with_hash': '1'} | |
|
52 | else: | |
|
53 | label = _('This tree as {}') | |
|
54 | _query = {'at_path':c.f_path, 'with_hash': '1'} | |
|
55 | %> | |
|
56 | ||
|
57 | <div class="btn-group btn-group-actions new-file"> | |
|
58 | <a class="archive_link btn btn-default" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname='{}{}'.format(c.commit.raw_id, '.zip'), _query=_query)}"> | |
|
59 | <i class="icon-download"></i> | |
|
60 | ${label.format('.zip')} | |
|
52 | 61 | </a> |
|
53 | % else: | |
|
54 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}"> | |
|
55 | ${_('Download this tree ZIP')} | |
|
62 | ||
|
63 | <a class="tooltip btn btn-default btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more download options')}"> | |
|
64 | <i class="icon-down"></i> | |
|
56 | 65 | </a> |
|
57 | % endif | |
|
58 | </div> | |
|
66 | ||
|
67 | <div class="btn-action-switcher-container left-align"> | |
|
68 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |
|
69 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: | |
|
70 | % if extension not in ['.zip']: | |
|
71 | <li> | |
|
72 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname='{}{}'.format(c.commit.raw_id, extension), _query=_query)}"> | |
|
73 | <i class="icon-download"></i> | |
|
74 | ${label.format(extension)} | |
|
75 | </a> | |
|
76 | </li> | |
|
77 | % endif | |
|
78 | % endfor | |
|
79 | </ul> | |
|
80 | </div> | |
|
81 | </div> | |
|
59 | 82 | % endif |
|
60 | 83 | |
|
61 | 84 | <div class="files-quick-filter"> |
@@ -7,10 +7,10 b'' | |||
|
7 | 7 | · ${h.branding(c.rhodecode_name)} |
|
8 | 8 | %endif |
|
9 | 9 | </%def> |
|
10 | <style>body{background-color:#eeeeee;}</style> | |
|
10 | 11 | |
|
11 | <style>body{background-color:#eeeeee;}</style> | |
|
12 | 12 | <div class="loginbox"> |
|
13 | <div class="header"> | |
|
13 | <div class="header-account"> | |
|
14 | 14 | <div id="header-inner" class="title"> |
|
15 | 15 | <div id="logo"> |
|
16 | 16 | <div class="logo-wrapper"> |
@@ -28,12 +28,12 b'' | |||
|
28 | 28 | <div class="loginwrapper"> |
|
29 | 29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
30 | 30 | |
|
31 |
<div class=" |
|
|
31 | <div class="auth-image-wrapper"> | |
|
32 | 32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
33 | 33 | </div> |
|
34 | 34 | |
|
35 | <%block name="above_login_button" /> | |
|
36 | <div id="login" class="right-column"> | |
|
35 | <div id="login"> | |
|
36 | <%block name="above_login_button" /> | |
|
37 | 37 | <!-- login --> |
|
38 | 38 | <div class="sign-in-title"> |
|
39 | 39 | <h1>${_('Sign In using username/password')}</h1> |
@@ -10,7 +10,7 b'' | |||
|
10 | 10 | <style>body{background-color:#eeeeee;}</style> |
|
11 | 11 | |
|
12 | 12 | <div class="loginbox"> |
|
13 | <div class="header"> | |
|
13 | <div class="header-account"> | |
|
14 | 14 | <div id="header-inner" class="title"> |
|
15 | 15 | <div id="logo"> |
|
16 | 16 | <div class="logo-wrapper"> |
@@ -27,7 +27,8 b'' | |||
|
27 | 27 | |
|
28 | 28 | <div class="loginwrapper"> |
|
29 | 29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
30 | <div class="left-column"> | |
|
30 | ||
|
31 | <div class="auth-image-wrapper"> | |
|
31 | 32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
32 | 33 | </div> |
|
33 | 34 | |
@@ -43,7 +44,7 b'' | |||
|
43 | 44 | </p> |
|
44 | 45 | </div> |
|
45 | 46 | %else: |
|
46 |
<div id="register" |
|
|
47 | <div id="register"> | |
|
47 | 48 | <!-- login --> |
|
48 | 49 | <div class="sign-in-title"> |
|
49 | 50 | <h1>${_('Reset your Password')}</h1> |
@@ -32,8 +32,6 b'' | |||
|
32 | 32 | %> |
|
33 | 33 | |
|
34 | 34 | <script type="text/javascript"> |
|
35 | // TODO: marcink switch this to pyroutes | |
|
36 | AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}"; | |
|
37 | 35 | templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id}; |
|
38 | 36 | templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}'; |
|
39 | 37 | </script> |
@@ -552,6 +550,42 b'' | |||
|
552 | 550 | ## CONTENT |
|
553 | 551 | <div class="sidebar-content"> |
|
554 | 552 | |
|
553 | ## Drafts | |
|
554 | % if c.rhodecode_edition_id == 'EE': | |
|
555 | <div id="draftsTable" class="sidebar-element clear-both" style="display: ${'block' if c.draft_comments else 'none'}"> | |
|
556 | <div class="tooltip right-sidebar-collapsed-state" style="display: none;" onclick="toggleSidebar(); return false" title="${_('Drafts')}"> | |
|
557 | <i class="icon-comment icon-draft"></i> | |
|
558 | <span id="drafts-count">${len(c.draft_comments)}</span> | |
|
559 | </div> | |
|
560 | ||
|
561 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
562 | <span style="padding-left: 2px"> | |
|
563 | <input name="select_all_drafts" type="checkbox" onclick="$('[name=submit_draft]').prop('checked', !$('[name=submit_draft]').prop('checked'))"> | |
|
564 | </span> | |
|
565 | <span class="sidebar-heading noselect" onclick="refreshDraftComments(); return false"> | |
|
566 | <i class="icon-comment icon-draft"></i> | |
|
567 | ${_('Drafts')} | |
|
568 | </span> | |
|
569 | <span class="block-right action_button last-item" onclick="submitDrafts(event)">${_('Submit')}</span> | |
|
570 | </div> | |
|
571 | ||
|
572 | <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers"> | |
|
573 | % if c.draft_comments: | |
|
574 | ${sidebar.comments_table(c.draft_comments, len(c.draft_comments), draft_comments=True)} | |
|
575 | % else: | |
|
576 | <table class="drafts-content-table"> | |
|
577 | <tr> | |
|
578 | <td> | |
|
579 | ${_('No TODOs yet')} | |
|
580 | </td> | |
|
581 | </tr> | |
|
582 | </table> | |
|
583 | % endif | |
|
584 | </div> | |
|
585 | ||
|
586 | </div> | |
|
587 | % endif | |
|
588 | ||
|
555 | 589 | ## RULES SUMMARY/RULES |
|
556 | 590 | <div class="sidebar-element clear-both"> |
|
557 | 591 | <% vote_title = _ungettext( |
@@ -678,7 +712,7 b'' | |||
|
678 | 712 | % endif |
|
679 | 713 | |
|
680 | 714 | ## TODOs |
|
681 | <div class="sidebar-element clear-both"> | |
|
715 | <div id="todosTable" class="sidebar-element clear-both"> | |
|
682 | 716 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs"> |
|
683 | 717 | <i class="icon-flag-filled"></i> |
|
684 | 718 | <span id="todos-count">${len(c.unresolved_comments)}</span> |
@@ -712,7 +746,7 b'' | |||
|
712 | 746 | % if c.unresolved_comments + c.resolved_comments: |
|
713 | 747 | ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)} |
|
714 | 748 | % else: |
|
715 | <table> | |
|
749 | <table class="todos-content-table"> | |
|
716 | 750 | <tr> |
|
717 | 751 | <td> |
|
718 | 752 | ${_('No TODOs yet')} |
@@ -725,7 +759,7 b'' | |||
|
725 | 759 | </div> |
|
726 | 760 | |
|
727 | 761 | ## COMMENTS |
|
728 | <div class="sidebar-element clear-both"> | |
|
762 | <div id="commentsTable" class="sidebar-element clear-both"> | |
|
729 | 763 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}"> |
|
730 | 764 | <i class="icon-comment" style="color: #949494"></i> |
|
731 | 765 | <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span> |
@@ -763,7 +797,7 b'' | |||
|
763 | 797 | % if c.inline_comments_flat + c.comments: |
|
764 | 798 | ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))} |
|
765 | 799 | % else: |
|
766 | <table> | |
|
800 | <table class="comments-content-table"> | |
|
767 | 801 | <tr> |
|
768 | 802 | <td> |
|
769 | 803 | ${_('No Comments yet')} |
@@ -846,6 +880,7 b' versionController.init();' | |||
|
846 | 880 | |
|
847 | 881 | reviewersController = new ReviewersController(); |
|
848 | 882 | commitsController = new CommitsController(); |
|
883 | commentsController = new CommentsController(); | |
|
849 | 884 | |
|
850 | 885 | updateController = new UpdatePrController(); |
|
851 | 886 | |
@@ -891,6 +926,23 b' window.setObserversData = ${c.pull_reque' | |||
|
891 | 926 | ); |
|
892 | 927 | }; |
|
893 | 928 | |
|
929 | window.submitDrafts = function (event) { | |
|
930 | var target = $(event.currentTarget); | |
|
931 | var callback = function (result) { | |
|
932 | target.removeAttr('onclick').html('saving...'); | |
|
933 | } | |
|
934 | var draftIds = []; | |
|
935 | $.each($('[name=submit_draft]:checked'), function (idx, val) { | |
|
936 | draftIds.push(parseInt($(val).val())); | |
|
937 | }) | |
|
938 | if (draftIds.length > 0) { | |
|
939 | Rhodecode.comments.finalizeDrafts(draftIds, callback); | |
|
940 | } | |
|
941 | else { | |
|
942 | ||
|
943 | } | |
|
944 | } | |
|
945 | ||
|
894 | 946 | window.closePullRequest = function (status) { |
|
895 | 947 | if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) { |
|
896 | 948 | return false; |
@@ -980,9 +1032,11 b' window.setObserversData = ${c.pull_reque' | |||
|
980 | 1032 | $(btns).each(fn_display); |
|
981 | 1033 | }); |
|
982 | 1034 | |
|
983 | // register submit callback on commentForm form to track TODOs | |
|
984 | window.commentFormGlobalSubmitSuccessCallback = function () { | |
|
985 | refreshMergeChecks(); | |
|
1035 | // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions | |
|
1036 | window.commentFormGlobalSubmitSuccessCallback = function (comment) { | |
|
1037 | if (!comment.draft) { | |
|
1038 | refreshMergeChecks(); | |
|
1039 | } | |
|
986 | 1040 | }; |
|
987 | 1041 | |
|
988 | 1042 | ReviewerAutoComplete('#user', reviewersController); |
@@ -994,7 +1048,8 b' window.setObserversData = ${c.pull_reque' | |||
|
994 | 1048 | |
|
995 | 1049 | var channel = '${c.pr_broadcast_channel}'; |
|
996 | 1050 | new ReviewerPresenceController(channel) |
|
997 | ||
|
1051 | // register globally so inject comment logic can re-use it. | |
|
1052 | window.commentsController = commentsController; | |
|
998 | 1053 | }) |
|
999 | 1054 | </script> |
|
1000 | 1055 |
@@ -74,6 +74,8 b'' | |||
|
74 | 74 | $pullRequestListTable.DataTable({ |
|
75 | 75 | processing: true, |
|
76 | 76 | serverSide: true, |
|
77 | stateSave: true, | |
|
78 | stateDuration: -1, | |
|
77 | 79 | ajax: { |
|
78 | 80 | "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}", |
|
79 | 81 | "data": function (d) { |
@@ -114,6 +116,10 b'' | |||
|
114 | 116 | if (data['closed']) { |
|
115 | 117 | $(row).addClass('closed'); |
|
116 | 118 | } |
|
119 | }, | |
|
120 | "stateSaveParams": function (settings, data) { | |
|
121 | data.search.search = ""; // Don't save search | |
|
122 | data.start = 0; // don't save pagination | |
|
117 | 123 | } |
|
118 | 124 | }); |
|
119 | 125 |
@@ -10,7 +10,7 b'' | |||
|
10 | 10 | <style>body{background-color:#eeeeee;}</style> |
|
11 | 11 | |
|
12 | 12 | <div class="loginbox"> |
|
13 | <div class="header"> | |
|
13 | <div class="header-account"> | |
|
14 | 14 | <div id="header-inner" class="title"> |
|
15 | 15 | <div id="logo"> |
|
16 | 16 | <div class="logo-wrapper"> |
@@ -27,11 +27,13 b'' | |||
|
27 | 27 | |
|
28 | 28 | <div class="loginwrapper"> |
|
29 | 29 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
30 | <div class="left-column"> | |
|
30 | ||
|
31 | <div class="auth-image-wrapper"> | |
|
31 | 32 | <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/> |
|
32 | 33 | </div> |
|
33 | <%block name="above_register_button" /> | |
|
34 |
<div id="register" |
|
|
34 | ||
|
35 | <div id="register"> | |
|
36 | <%block name="above_register_button" /> | |
|
35 | 37 | <!-- login --> |
|
36 | 38 | <div class="sign-in-title"> |
|
37 | 39 | % if external_auth_provider: |
@@ -187,7 +187,7 b'' | |||
|
187 | 187 | <div class="enabled pull-left" style="margin-right: 10px"> |
|
188 | 188 | |
|
189 | 189 | <div class="btn-group btn-group-actions"> |
|
190 |
<a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip' |
|
|
190 | <a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip', _query={'with_hash': '1'})}"> | |
|
191 | 191 | <i class="icon-download"></i> |
|
192 | 192 | ${c.rhodecode_db_repo.landing_ref_name}.zip |
|
193 | 193 | ## replaced by some JS on select |
@@ -198,12 +198,11 b'' | |||
|
198 | 198 | </a> |
|
199 | 199 | |
|
200 | 200 | <div class="btn-action-switcher-container left-align"> |
|
201 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px"> | |
|
201 | <ul class="btn-action-switcher" role="menu" style="min-width: 200px; width: max-content"> | |
|
202 | 202 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: |
|
203 | 203 | % if extension not in ['.zip']: |
|
204 | 204 | <li> |
|
205 | ||
|
206 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension)}"> | |
|
205 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension, _query={'with_hash': '1'})}"> | |
|
207 | 206 | <i class="icon-download"></i> |
|
208 | 207 | ${c.rhodecode_db_repo.landing_ref_name+extension} |
|
209 | 208 | </a> |
@@ -49,23 +49,32 b' class TestArchives(BackendTestMixin):' | |||
|
49 | 49 | @classmethod |
|
50 | 50 | def _get_commits(cls): |
|
51 | 51 | start_date = datetime.datetime(2010, 1, 1, 20) |
|
52 | yield { | |
|
53 | 'message': 'Initial Commit', | |
|
54 | 'author': 'Joe Doe <joe.doe@example.com>', | |
|
55 | 'date': start_date + datetime.timedelta(hours=12), | |
|
56 | 'added': [ | |
|
57 | FileNode('executable_0o100755', '...', mode=0o100755), | |
|
58 | FileNode('executable_0o100500', '...', mode=0o100500), | |
|
59 | FileNode('not_executable', '...', mode=0o100644), | |
|
60 | ], | |
|
61 | } | |
|
52 | 62 | for x in range(5): |
|
53 | 63 | yield { |
|
54 | 64 | 'message': 'Commit %d' % x, |
|
55 | 65 | 'author': 'Joe Doe <joe.doe@example.com>', |
|
56 | 66 | 'date': start_date + datetime.timedelta(hours=12 * x), |
|
57 | 67 | 'added': [ |
|
58 | FileNode( | |
|
59 | '%d/file_%d.txt' % (x, x), content='Foobar %d' % x), | |
|
68 | FileNode('%d/file_%d.txt' % (x, x), content='Foobar %d' % x), | |
|
60 | 69 | ], |
|
61 | 70 | } |
|
62 | 71 | |
|
63 | 72 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) |
|
64 | 73 | def test_archive_tar(self, compressor): |
|
65 | 74 | self.tip.archive_repo( |
|
66 |
self.temp_file, kind='t' |
|
|
75 | self.temp_file, kind='t{}'.format(compressor), archive_dir_name='repo') | |
|
67 | 76 | out_dir = tempfile.mkdtemp() |
|
68 |
out_file = tarfile.open(self.temp_file, 'r|' |
|
|
77 | out_file = tarfile.open(self.temp_file, 'r|{}'.format(compressor)) | |
|
69 | 78 | out_file.extractall(out_dir) |
|
70 | 79 | out_file.close() |
|
71 | 80 | |
@@ -77,8 +86,24 b' class TestArchives(BackendTestMixin):' | |||
|
77 | 86 | |
|
78 | 87 | shutil.rmtree(out_dir) |
|
79 | 88 | |
|
89 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) | |
|
90 | def test_archive_tar_symlink(self, compressor): | |
|
91 | return False | |
|
92 | ||
|
93 | @pytest.mark.parametrize('compressor', ['gz', 'bz2']) | |
|
94 | def test_archive_tar_file_modes(self, compressor): | |
|
95 | self.tip.archive_repo( | |
|
96 | self.temp_file, kind='t{}'.format(compressor), archive_dir_name='repo') | |
|
97 | out_dir = tempfile.mkdtemp() | |
|
98 | out_file = tarfile.open(self.temp_file, 'r|{}'.format(compressor)) | |
|
99 | out_file.extractall(out_dir) | |
|
100 | out_file.close() | |
|
101 | dest = lambda inp: os.path.join(out_dir, 'repo/' + inp) | |
|
102 | ||
|
103 | assert oct(os.stat(dest('not_executable')).st_mode) == '0100644' | |
|
104 | ||
|
80 | 105 | def test_archive_zip(self): |
|
81 |
self.tip.archive_repo(self.temp_file, kind='zip', |
|
|
106 | self.tip.archive_repo(self.temp_file, kind='zip', archive_dir_name='repo') | |
|
82 | 107 | out = zipfile.ZipFile(self.temp_file) |
|
83 | 108 | |
|
84 | 109 | for x in range(5): |
@@ -91,10 +116,10 b' class TestArchives(BackendTestMixin):' | |||
|
91 | 116 | |
|
92 | 117 | def test_archive_zip_with_metadata(self): |
|
93 | 118 | self.tip.archive_repo(self.temp_file, kind='zip', |
|
94 |
|
|
|
119 | archive_dir_name='repo', write_metadata=True) | |
|
95 | 120 | |
|
96 | 121 | out = zipfile.ZipFile(self.temp_file) |
|
97 | metafile = out.read('.archival.txt') | |
|
122 | metafile = out.read('repo/.archival.txt') | |
|
98 | 123 | |
|
99 | 124 | raw_id = self.tip.raw_id |
|
100 | 125 | assert 'commit_id:%s' % raw_id in metafile |
General Comments 0
You need to be logged in to leave comments.
Login now