##// END OF EJS Templates
comments: add comments type into comments.
marcink -
r1324:efd94f49 default
parent child Browse files
Show More
@@ -0,0 +1,30 b''
1 import logging
2
3 from sqlalchemy import Column, MetaData, Integer, Unicode, ForeignKey
4
5 from rhodecode.lib.dbmigrate.versions import _reset_base
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_5_0_0 as db
17
18 # add comment type and link to resolve by id
19 comment_table = db.ChangesetComment.__table__
20 col1 = Column('comment_type', Unicode(128), nullable=True)
21 col1.create(table=comment_table)
22
23 col1 = Column('resolved_comment_id', Integer(),
24 ForeignKey('changeset_comments.comment_id'), nullable=True)
25 col1.create(table=comment_table)
26
27
28 def downgrade(migrate_engine):
29 meta = MetaData()
30 meta.bind = migrate_engine
@@ -0,0 +1,70 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 import colander
24
25 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import preparers
27 from rhodecode.model.validation_schema import types
28
29
30 @colander.deferred
31 def deferred_lifetime_validator(node, kw):
32 options = kw.get('lifetime_options', [])
33 return colander.All(
34 colander.Range(min=-1, max=60 * 24 * 30 * 12),
35 colander.OneOf([x for x in options]))
36
37
38 def unique_gist_validator(node, value):
39 from rhodecode.model.db import Gist
40 existing = Gist.get_by_access_id(value)
41 if existing:
42 msg = _(u'Gist with name {} already exists').format(value)
43 raise colander.Invalid(node, msg)
44
45
46 def filename_validator(node, value):
47 if value != os.path.basename(value):
48 msg = _(u'Filename {} cannot be inside a directory').format(value)
49 raise colander.Invalid(node, msg)
50
51
52 comment_types = ['note', 'todo']
53
54
55 class CommentSchema(colander.MappingSchema):
56 from rhodecode.model.db import ChangesetComment
57
58 comment_body = colander.SchemaNode(colander.String())
59 comment_type = colander.SchemaNode(
60 colander.String(),
61 validator=colander.OneOf(ChangesetComment.COMMENT_TYPES))
62
63 comment_file = colander.SchemaNode(colander.String(), missing=None)
64 comment_line = colander.SchemaNode(colander.String(), missing=None)
65 status_change = colander.SchemaNode(colander.String(), missing=None)
66 renderer_type = colander.SchemaNode(colander.String())
67
68 # do those ?
69 user = colander.SchemaNode(types.StrOrIntType())
70 repo = colander.SchemaNode(types.StrOrIntType())
@@ -51,7 +51,7 b' PYRAMID_SETTINGS = {}'
51 51 EXTENSIONS = {}
52 52
53 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 63 # defines current db version for migrations
54 __dbversion__ = 64 # defines current db version for migrations
55 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
@@ -334,6 +334,8 b' class ChangesetController(BaseRepoContro'
334 334 commit_id = revision
335 335 status = request.POST.get('changeset_status', None)
336 336 text = request.POST.get('text')
337 comment_type = request.POST.get('comment_type')
338
337 339 if status:
338 340 text = text or (_('Status change %(transition_icon)s %(status)s')
339 341 % {'transition_icon': '>',
@@ -355,7 +357,8 b' class ChangesetController(BaseRepoContro'
355 357 line_no=request.POST.get('line'),
356 358 status_change=(ChangesetStatus.get_status_lbl(status)
357 359 if status else None),
358 status_change_type=status
360 status_change_type=status,
361 comment_type=comment_type
359 362 )
360 363 c.inline_comment = True if comment.line_no else False
361 364
@@ -903,6 +903,7 b' class PullrequestsController(BaseRepoCon'
903 903 # as a changeset status, still we want to send it in one value.
904 904 status = request.POST.get('changeset_status', None)
905 905 text = request.POST.get('text')
906 comment_type = request.POST.get('comment_type')
906 907 if status and '_closed' in status:
907 908 close_pr = True
908 909 status = status.replace('_closed', '')
@@ -934,7 +935,8 b' class PullrequestsController(BaseRepoCon'
934 935 if status and allowed_to_change_status else None),
935 936 status_change_type=(status
936 937 if status and allowed_to_change_status else None),
937 closing_pr=close_pr
938 closing_pr=close_pr,
939 comment_type=comment_type
938 940 )
939 941
940 942 if allowed_to_change_status:
@@ -56,7 +56,7 b' from rhodecode.lib.utils2 import ('
56 56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 60 from rhodecode.model.notification import NotificationModel
61 61 from rhodecode.model.scm import ScmModel
62 62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
@@ -299,6 +299,7 b' def attach_context_attributes(context, r'
299 299 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
300 300 context.visual.default_renderer = rc_config.get(
301 301 'rhodecode_markup_renderer', 'rst')
302 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
302 303 context.visual.rhodecode_support_url = \
303 304 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
304 305
@@ -44,6 +44,8 b' from rhodecode.model.notification import'
44 44 from rhodecode.model.meta import Session
45 45 from rhodecode.model.settings import VcsSettingsModel
46 46 from rhodecode.model.notification import EmailNotificationModel
47 from rhodecode.model.validation_schema.schemas import comment_schema
48
47 49
48 50 log = logging.getLogger(__name__)
49 51
@@ -111,15 +113,32 b' class CommentsModel(BaseModel):'
111 113 if not renderer:
112 114 renderer = self._get_renderer()
113 115
114 repo = self._get_repo(repo)
115 user = self._get_user(user)
116
117 schema = comment_schema.CommentSchema()
118 validated_kwargs = schema.deserialize(dict(
119 comment_body=text,
120 comment_type=comment_type,
121 comment_file=f_path,
122 comment_line=line_no,
123 renderer_type=renderer,
124 status_change=status_change,
125
126 repo=repo,
127 user=user,
128 ))
129
130 repo = self._get_repo(validated_kwargs['repo'])
131 user = self._get_user(validated_kwargs['user'])
132
116 133 comment = ChangesetComment()
117 comment.renderer = renderer
134 comment.renderer = validated_kwargs['renderer_type']
135 comment.text = validated_kwargs['comment_body']
136 comment.f_path = validated_kwargs['comment_file']
137 comment.line_no = validated_kwargs['comment_line']
138 comment.comment_type = validated_kwargs['comment_type']
139
118 140 comment.repo = repo
119 141 comment.author = user
120 comment.text = text
121 comment.f_path = f_path
122 comment.line_no = line_no
123 142
124 143 pull_request_id = pull_request
125 144
@@ -2896,6 +2896,9 b' class ChangesetComment(Base, BaseModel):'
2896 2896 )
2897 2897
2898 2898 COMMENT_OUTDATED = u'comment_outdated'
2899 COMMENT_TYPE_NOTE = u'note'
2900 COMMENT_TYPE_TODO = u'todo'
2901 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2899 2902
2900 2903 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2901 2904 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
@@ -2912,6 +2915,9 b' class ChangesetComment(Base, BaseModel):'
2912 2915 renderer = Column('renderer', Unicode(64), nullable=True)
2913 2916 display_state = Column('display_state', Unicode(128), nullable=True)
2914 2917
2918 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2919 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2920 resolved_comment = relationship('ChangesetComment', remote_side=comment_id)
2915 2921 author = relationship('User', lazy='joined')
2916 2922 repo = relationship('Repository')
2917 2923 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
@@ -186,3 +186,11 b' class UserType(UserOrUserGroupType):'
186 186
187 187 class UserGroupType(UserOrUserGroupType):
188 188 scopes = ('usergroup',)
189
190
191 class StrOrIntType(colander.String):
192 def deserialize(self, node, cstruct):
193 if isinstance(node, basestring):
194 return super(StrOrIntType, self).deserialize(node, cstruct)
195 else:
196 return colander.Integer().deserialize(node, cstruct)
@@ -880,8 +880,6 b' input.filediff-collapse-state {'
880 880 display: inline;
881 881 }
882 882
883 @comment-padding: 5px;
884
885 883 /**** COMMENTS ****/
886 884
887 885 .filediff-menu {
@@ -909,59 +907,6 b' input.filediff-collapse-state {'
909 907 }
910 908 }
911 909
912 .inline-comments {
913 border-radius: @border-radius;
914 .comment {
915 margin: 0;
916 border-radius: @border-radius;
917 }
918 .comment-outdated {
919 opacity: 0.5;
920 }
921
922 .comment-inline {
923 background: white;
924 padding: (@comment-padding + 3px) @comment-padding;
925 border: @comment-padding solid @grey6;
926
927 .text {
928 border: none;
929 }
930 .meta {
931 border-bottom: 1px solid @grey6;
932 padding-bottom: 10px;
933 }
934 }
935 .comment-selected {
936 border-left: 6px solid @comment-highlight-color;
937 }
938 .comment-inline-form {
939 padding: @comment-padding;
940 display: none;
941 }
942 .cb-comment-add-button {
943 margin: @comment-padding;
944 }
945 /* hide add comment button when form is open */
946 .comment-inline-form-open ~ .cb-comment-add-button {
947 display: none;
948 }
949 .comment-inline-form-open {
950 display: block;
951 }
952 /* hide add comment button when form but no comments */
953 .comment-inline-form:first-child + .cb-comment-add-button {
954 display: none;
955 }
956 /* hide add comment button when no comments or form */
957 .cb-comment-add-button:first-child {
958 display: none;
959 }
960 /* hide add comment button when only comment is being deleted */
961 .comment-deleting:first-child + .cb-comment-add-button {
962 display: none;
963 }
964 }
965 910 /**** END COMMENTS ****/
966 911
967 912 }
@@ -47,6 +47,33 b' tr.inline-comments div {'
47 47 visibility: hidden;
48 48 }
49 49
50 .comment-label {
51 float: left;
52
53 padding: 0.4em 0.4em;
54 margin: 2px 5px 0px -10px;
55 display: inline-block;
56 min-height: 0;
57
58 text-align: center;
59 font-size: 10px;
60 line-height: .8em;
61
62 font-family: @text-italic;
63 background: #fff none;
64 color: @grey4;
65 border: 1px solid @grey4;
66 white-space: nowrap;
67
68 text-transform: uppercase;
69
70 &.todo {
71 color: @color5;
72 font-family: @text-bold-italic;
73 }
74 }
75
76
50 77 .comment {
51 78
52 79 &.comment-general {
@@ -60,15 +87,19 b' tr.inline-comments div {'
60 87
61 88 .rc-user {
62 89 min-width: 0;
63 margin: -2px .5em 0 0;
90 margin: 0px .5em 0 0;
91
92 .user {
93 display: inline;
94 }
64 95 }
65 96
66 97 .meta {
67 98 position: relative;
68 99 width: 100%;
69 margin: 0 0 .5em 0;
70 100 border-bottom: 1px solid @grey5;
71 padding: 8px 0px;
101 margin: -5px 0px;
102 line-height: 24px;
72 103
73 104 &:hover .permalink {
74 105 visibility: visible;
@@ -87,10 +118,10 b' tr.inline-comments div {'
87 118 }
88 119
89 120 .author-general img {
90 top: -3px;
121 top: 3px;
91 122 }
92 123 .author-inline img {
93 top: -3px;
124 top: 3px;
94 125 }
95 126
96 127 .status-change,
@@ -182,7 +213,7 b' tr.inline-comments div {'
182 213 }
183 214 .pr-version-inline {
184 215 float: left;
185 margin: 1px 4px;
216 margin: 0px 4px;
186 217 }
187 218 .pr-version-num {
188 219 font-size: 10px;
@@ -190,6 +221,64 b' tr.inline-comments div {'
190 221
191 222 }
192 223
224 @comment-padding: 5px;
225
226 .inline-comments {
227 border-radius: @border-radius;
228 .comment {
229 margin: 0;
230 border-radius: @border-radius;
231 }
232 .comment-outdated {
233 opacity: 0.5;
234 }
235
236 .comment-inline {
237 background: white;
238 padding: @comment-padding @comment-padding;
239 border: @comment-padding solid @grey6;
240
241 .text {
242 border: none;
243 }
244 .meta {
245 border-bottom: 1px solid @grey6;
246 margin: -5px 0px;
247 line-height: 24px;
248 }
249 }
250 .comment-selected {
251 border-left: 6px solid @comment-highlight-color;
252 }
253 .comment-inline-form {
254 padding: @comment-padding;
255 display: none;
256 }
257 .cb-comment-add-button {
258 margin: @comment-padding;
259 }
260 /* hide add comment button when form is open */
261 .comment-inline-form-open ~ .cb-comment-add-button {
262 display: none;
263 }
264 .comment-inline-form-open {
265 display: block;
266 }
267 /* hide add comment button when form but no comments */
268 .comment-inline-form:first-child + .cb-comment-add-button {
269 display: none;
270 }
271 /* hide add comment button when no comments or form */
272 .cb-comment-add-button:first-child {
273 display: none;
274 }
275 /* hide add comment button when only comment is being deleted */
276 .comment-deleting:first-child + .cb-comment-add-button {
277 display: none;
278 }
279 }
280
281
193 282 .show-outdated-comments {
194 283 display: inline;
195 284 color: @rcblue;
@@ -300,6 +389,12 b' form.comment-form {'
300 389 }
301 390 }
302 391
392 .comment-type {
393 margin: 0px;
394 border-radius: inherit;
395 border-color: @grey6;
396 }
397
303 398 .preview-box {
304 399 min-height: 105px;
305 400 margin-bottom: 15px;
@@ -688,6 +688,7 b' label {'
688 688 padding: 0;
689 689 line-height: 1em;
690 690 border: 1px solid @grey4;
691 box-sizing: content-box;
691 692
692 693 &.gravatar-large {
693 694 margin: -0.5em .25em -0.5em 0;
@@ -132,12 +132,13 b' var CommentForm = (function() {'
132 132
133 133 this.editButton = this.withLineNo('#edit-btn');
134 134 this.editContainer = this.withLineNo('#edit-container');
135 this.cancelButton = this.withLineNo('#cancel-btn');
136 this.commentType = this.withLineNo('#comment_type');
135 137
136 this.cancelButton = this.withLineNo('#cancel-btn');
138 this.cmBox = this.withLineNo('#text');
139 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
137 140
138 141 this.statusChange = '#change_status';
139 this.cmBox = this.withLineNo('#text');
140 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
141 142
142 143 this.submitForm = formElement;
143 144 this.submitButton = $(this.submitForm).find('input[type="submit"]');
@@ -172,7 +173,9 b' var CommentForm = (function() {'
172 173 this.getCommentStatus = function() {
173 174 return $(this.submitForm).find(this.statusChange).val();
174 175 };
175
176 this.getCommentType = function() {
177 return $(this.submitForm).find(this.commentType).val();
178 };
176 179 this.isAllowedToSubmit = function() {
177 180 return !$(this.submitButton).prop('disabled');
178 181 };
@@ -256,6 +259,7 b' var CommentForm = (function() {'
256 259 this.handleFormSubmit = function() {
257 260 var text = self.cm.getValue();
258 261 var status = self.getCommentStatus();
262 var commentType = self.getCommentType();
259 263
260 264 if (text === "" && !status) {
261 265 return;
@@ -268,6 +272,7 b' var CommentForm = (function() {'
268 272 var postData = {
269 273 'text': text,
270 274 'changeset_status': status,
275 'comment_type': commentType,
271 276 'csrf_token': CSRF_TOKEN
272 277 };
273 278
@@ -536,6 +541,7 b' var CommentsController = function() { /*'
536 541 $filediff.removeClass('hide-comments');
537 542 var f_path = $filediff.attr('data-f-path');
538 543 var lineno = self.getLineNumber(node);
544
539 545 tmpl = tmpl.format(f_path, lineno);
540 546 $form = $(tmpl);
541 547
@@ -557,6 +563,7 b' var CommentsController = function() { /*'
557 563 // set a CUSTOM submit handler for inline comments.
558 564 commentForm.setHandleFormSubmit(function(o) {
559 565 var text = commentForm.cm.getValue();
566 var commentType = commentForm.getCommentType();
560 567
561 568 if (text === "") {
562 569 return;
@@ -579,6 +586,7 b' var CommentsController = function() { /*'
579 586 'text': text,
580 587 'f_path': f_path,
581 588 'line': lineno,
589 'comment_type': commentType,
582 590 'csrf_token': CSRF_TOKEN
583 591 };
584 592 var submitSuccessCallback = function(json_data) {
@@ -18,8 +18,14 b''
18 18 style="${'display: none;' if outdated_at_ver else ''}">
19 19
20 20 <div class="meta">
21 <div class="comment-type-label tooltip">
22 <div class="comment-label ${comment.comment_type or 'note'}">
23 ${comment.comment_type or 'note'}
24 </div>
25 </div>
26
21 27 <div class="author ${'author-inline' if inline else 'author-general'}">
22 ${base.gravatar_with_user(comment.author.email, 20)}
28 ${base.gravatar_with_user(comment.author.email, 16)}
23 29 </div>
24 30 <div class="date">
25 31 ${h.age_component(comment.modified_at, time_is_local=True)}
@@ -31,16 +37,14 b''
31 37 % if comment.pull_request:
32 38 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
33 39 % if comment.status_change:
34 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
40 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
35 41 % else:
36 ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id}
42 ${_('pull request #%s') % comment.pull_request.pull_request_id}
37 43 % endif
38 44 </a>
39 45 % else:
40 46 % if comment.status_change:
41 47 ${_('Status change on commit')}:
42 % else:
43 ${_('Comment on commit')}
44 48 % endif
45 49 % endif
46 50 </div>
@@ -185,6 +189,13 b''
185 189 <li class="">
186 190 <a href="#preview-btn" tabindex="-1" id="preview-btn">${_('Preview')}</a>
187 191 </li>
192 <li class="pull-right">
193 <select class="comment-type" id="comment_type" name="comment_type">
194 % for val in c.visual.comment_types:
195 <option value="${val}">${val.upper()}</option>
196 % endfor
197 </select>
198 </li>
188 199 </ul>
189 200 </div>
190 201
@@ -70,6 +70,13 b" return h.url('', **new_args)"
70 70 <li class="">
71 71 <a href="#preview-btn" tabindex="-1" id="preview-btn_{1}">${_('Preview')}</a>
72 72 </li>
73 <li class="pull-right">
74 <select class="comment-type" id="comment_type_{1}" name="comment_type">
75 % for val in c.visual.comment_types:
76 <option value="${val}">${val.upper()}</option>
77 % endfor
78 </select>
79 </li>
73 80 </ul>
74 81 </div>
75 82
General Comments 0
You need to be logged in to leave comments. Login now