comments: introduce new draft comments.

- private to author
- not triggering any notifications
- sidebar doesn't display draft comments
- They are just placeholders for longer review.
This commit is contained in:
Milka Kuzminski 2020-10-21 17:53:23 +02:00
parent cf30382fbc
commit afd3357349
15 changed files with 343 additions and 81 deletions

View file

@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {}
EXTENSIONS = {}
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
__dbversion__ = 110 # defines current db version for migrations
__dbversion__ = 111 # defines current db version for migrations
__platform__ = platform.system()
__license__ = 'AGPLv3, and Commercial License'
__author__ = 'RhodeCode GmbH'

View file

@ -47,7 +47,7 @@ from rhodecode.lib.vcs.exceptions import (
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.db import (
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
PullRequestReviewers)
from rhodecode.model.forms import PullRequestForm
from rhodecode.model.meta import Session
@ -268,12 +268,14 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
return diffset
def register_comments_vars(self, c, pull_request, versions):
def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
comments_model = CommentsModel()
# GENERAL COMMENTS with versions #
q = comments_model._all_general_comments_of_pull_request(pull_request)
q = q.order_by(ChangesetComment.comment_id.asc())
if not include_drafts:
q = q.filter(ChangesetComment.draft == false())
general_comments = q
# pick comments we want to render at current version
@ -283,6 +285,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
# INLINE COMMENTS with versions #
q = comments_model._all_inline_comments_of_pull_request(pull_request)
q = q.order_by(ChangesetComment.comment_id.asc())
if not include_drafts:
q = q.filter(ChangesetComment.draft == false())
inline_comments = q
c.inline_versions = comments_model.aggregate_comments(
@ -1015,7 +1019,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
if at_version and at_version != PullRequest.LATEST_VER
else None)
self.register_comments_vars(c, pull_request_latest, versions)
self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
all_comments = c.inline_comments_flat + c.comments
existing_ids = self._get_existing_ids(self.request.POST)
@ -1055,9 +1059,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
else None)
c.unresolved_comments = CommentsModel() \
.get_pull_request_unresolved_todos(pull_request)
.get_pull_request_unresolved_todos(pull_request, include_drafts=False)
c.resolved_comments = CommentsModel() \
.get_pull_request_resolved_todos(pull_request)
.get_pull_request_resolved_todos(pull_request, include_drafts=False)
all_comments = c.unresolved_comments + c.resolved_comments
existing_ids = self._get_existing_ids(self.request.POST)
@ -1544,6 +1548,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
status = self.request.POST.get('changeset_status', None)
text = self.request.POST.get('text')
comment_type = self.request.POST.get('comment_type')
is_draft = str2bool(self.request.POST.get('draft'))
resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
close_pull_request = self.request.POST.get('close_pull_request')
@ -1574,9 +1579,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
else:
# regular comment case, could be inline, or one with status.
# for that one we check also permissions
# Additionally ENSURE if somehow draft is sent we're then unable to change status
allowed_to_change_status = PullRequestModel().check_user_change_status(
pull_request, self._rhodecode_user)
pull_request, self._rhodecode_user) and not is_draft
if status and allowed_to_change_status:
message = (_('Status change %(transition_icon)s %(status)s')
@ -1596,8 +1601,10 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
status_change_type=(status
if status and allowed_to_change_status else None),
comment_type=comment_type,
is_draft=is_draft,
resolves_comment_id=resolves_comment_id,
auth_user=self._rhodecode_user
auth_user=self._rhodecode_user,
send_email=not is_draft, # skip notification for draft comments
)
is_inline = comment.is_inline
@ -1638,6 +1645,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
'target_id': h.safeid(h.safe_unicode(
self.request.POST.get('f_path'))),
}
if comment:
c.co = comment
c.at_version_num = None
@ -1648,15 +1656,17 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
data.update(comment.get_dict())
data.update({'rendered_text': rendered_comment})
comment_broadcast_channel = channelstream.comment_channel(
self.db_repo_name, pull_request_obj=pull_request)
# skip channelstream for draft comments
if not is_draft:
comment_broadcast_channel = channelstream.comment_channel(
self.db_repo_name, pull_request_obj=pull_request)
comment_data = data
comment_type = 'inline' if is_inline else 'general'
channelstream.comment_channelstream_push(
self.request, comment_broadcast_channel, self._rhodecode_user,
_('posted a new {} comment').format(comment_type),
comment_data=comment_data)
comment_data = data
comment_type = 'inline' if is_inline else 'general'
channelstream.comment_channelstream_push(
self.request, comment_broadcast_channel, self._rhodecode_user,
_('posted a new {} comment').format(comment_type),
comment_data=comment_data)
return data

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import logging
from sqlalchemy import *
from alembic.migration import MigrationContext
from alembic.operations import Operations
from rhodecode.lib.dbmigrate.versions import _reset_base
from rhodecode.model import meta, init_model_encryption
log = logging.getLogger(__name__)
def upgrade(migrate_engine):
"""
Upgrade operations go here.
Don't create your own engine; bind migrate_engine to your metadata
"""
_reset_base(migrate_engine)
from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
init_model_encryption(db)
context = MigrationContext.configure(migrate_engine.connect())
op = Operations(context)
table = db.ChangesetComment.__table__
with op.batch_alter_table(table.name) as batch_op:
new_column = Column('draft', Boolean(), nullable=True)
batch_op.add_column(new_column)
_set_default_as_non_draft(op, meta.Session)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
def fixups(models, _SESSION):
pass
def _set_default_as_non_draft(op, session):
params = {'draft': False}
query = text(
'UPDATE changeset_comments SET draft = :draft'
).bindparams(**params)
op.execute(query)
session().commit()

View file

@ -37,6 +37,7 @@ from rhodecode.lib.exceptions import CommentVersionMismatch
from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
from rhodecode.model import BaseModel
from rhodecode.model.db import (
false,
ChangesetComment,
User,
Notification,
@ -160,7 +161,7 @@ class CommentsModel(BaseModel):
return todos
def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
todos = Session().query(ChangesetComment) \
.filter(ChangesetComment.pull_request == pull_request) \
@ -168,6 +169,9 @@ class CommentsModel(BaseModel):
.filter(ChangesetComment.comment_type
== ChangesetComment.COMMENT_TYPE_TODO)
if not include_drafts:
todos = todos.filter(ChangesetComment.draft == false())
if not show_outdated:
todos = todos.filter(
coalesce(ChangesetComment.display_state, '') !=
@ -177,7 +181,7 @@ class CommentsModel(BaseModel):
return todos
def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
todos = Session().query(ChangesetComment) \
.filter(ChangesetComment.pull_request == pull_request) \
@ -185,6 +189,9 @@ class CommentsModel(BaseModel):
.filter(ChangesetComment.comment_type
== ChangesetComment.COMMENT_TYPE_TODO)
if not include_drafts:
todos = todos.filter(ChangesetComment.draft == false())
if not show_outdated:
todos = todos.filter(
coalesce(ChangesetComment.display_state, '') !=
@ -194,7 +201,7 @@ class CommentsModel(BaseModel):
return todos
def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
todos = Session().query(ChangesetComment) \
.filter(ChangesetComment.revision == commit_id) \
@ -202,6 +209,9 @@ class CommentsModel(BaseModel):
.filter(ChangesetComment.comment_type
== ChangesetComment.COMMENT_TYPE_TODO)
if not include_drafts:
todos = todos.filter(ChangesetComment.draft == false())
if not show_outdated:
todos = todos.filter(
coalesce(ChangesetComment.display_state, '') !=
@ -211,7 +221,7 @@ class CommentsModel(BaseModel):
return todos
def get_commit_resolved_todos(self, commit_id, show_outdated=True):
def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
todos = Session().query(ChangesetComment) \
.filter(ChangesetComment.revision == commit_id) \
@ -219,6 +229,9 @@ class CommentsModel(BaseModel):
.filter(ChangesetComment.comment_type
== ChangesetComment.COMMENT_TYPE_TODO)
if not include_drafts:
todos = todos.filter(ChangesetComment.draft == false())
if not show_outdated:
todos = todos.filter(
coalesce(ChangesetComment.display_state, '') !=
@ -228,11 +241,15 @@ class CommentsModel(BaseModel):
return todos
def get_commit_inline_comments(self, commit_id):
def get_commit_inline_comments(self, commit_id, include_drafts=True):
inline_comments = Session().query(ChangesetComment) \
.filter(ChangesetComment.line_no != None) \
.filter(ChangesetComment.f_path != None) \
.filter(ChangesetComment.revision == commit_id)
if not include_drafts:
inline_comments = inline_comments.filter(ChangesetComment.draft == false())
inline_comments = inline_comments.all()
return inline_comments
@ -245,7 +262,7 @@ class CommentsModel(BaseModel):
def create(self, text, repo, user, commit_id=None, pull_request=None,
f_path=None, line_no=None, status_change=None,
status_change_type=None, comment_type=None,
status_change_type=None, comment_type=None, is_draft=False,
resolves_comment_id=None, closing_pr=False, send_email=True,
renderer=None, auth_user=None, extra_recipients=None):
"""
@ -262,6 +279,7 @@ class CommentsModel(BaseModel):
:param line_no:
:param status_change: Label for status change
:param comment_type: Type of comment
:param is_draft: is comment a draft only
:param resolves_comment_id: id of comment which this one will resolve
:param status_change_type: type of status change
:param closing_pr:
@ -288,6 +306,7 @@ class CommentsModel(BaseModel):
validated_kwargs = schema.deserialize(dict(
comment_body=text,
comment_type=comment_type,
is_draft=is_draft,
comment_file=f_path,
comment_line=line_no,
renderer_type=renderer,
@ -296,6 +315,7 @@ class CommentsModel(BaseModel):
repo=repo.repo_id,
user=user.user_id,
))
is_draft = validated_kwargs['is_draft']
comment = ChangesetComment()
comment.renderer = validated_kwargs['renderer_type']
@ -303,6 +323,7 @@ class CommentsModel(BaseModel):
comment.f_path = validated_kwargs['comment_file']
comment.line_no = validated_kwargs['comment_line']
comment.comment_type = validated_kwargs['comment_type']
comment.draft = is_draft
comment.repo = repo
comment.author = user
@ -462,10 +483,11 @@ class CommentsModel(BaseModel):
else:
action = 'repo.commit.comment.create'
comment_data = comment.get_api_data()
if not is_draft:
comment_data = comment.get_api_data()
self._log_audit_action(
action, {'data': comment_data}, auth_user, comment)
self._log_audit_action(
action, {'data': comment_data}, auth_user, comment)
return comment

View file

@ -3767,6 +3767,7 @@ class ChangesetComment(Base, BaseModel):
renderer = Column('renderer', Unicode(64), nullable=True)
display_state = Column('display_state', Unicode(128), nullable=True)
immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
draft = Column('draft', Boolean(), nullable=True, default=False)
comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)

View file

@ -60,7 +60,7 @@ class CommentSchema(colander.MappingSchema):
colander.String(),
validator=colander.OneOf(ChangesetComment.COMMENT_TYPES),
missing=ChangesetComment.COMMENT_TYPE_NOTE)
is_draft = colander.SchemaNode(colander.Boolean(),missing=False)
comment_file = colander.SchemaNode(colander.String(), missing=None)
comment_line = colander.SchemaNode(colander.String(), missing=None)
status_change = colander.SchemaNode(

View file

@ -162,7 +162,6 @@ input[type="button"] {
}
}
.btn-warning,
.btn-danger,
.revoke_perm,
.btn-x,
@ -196,6 +195,36 @@ input[type="button"] {
}
}
.btn-warning {
.border ( @border-thickness, @alert3 );
background-color: white;
color: @alert3;
a {
color: @alert3;
}
&:hover,
&.active {
.border ( @border-thickness, @alert3 );
color: white;
background-color: @alert3;
a {
color: white;
}
}
i {
display:none;
}
&:disabled {
background-color: white;
color: @alert3;
}
}
.btn-approved-status {
.border ( @border-thickness, @alert1 );
background-color: white;
@ -401,6 +430,37 @@ input[type="button"] {
}
input[type="submit"].btn-warning {
&:extend(.btn-warning);
&:focus {
outline: 0;
}
&:hover {
&:extend(.btn-warning:hover);
}
&.btn-link {
&:extend(.btn-link);
color: @alert3;
&:disabled {
color: @alert3;
background-color: transparent;
}
}
&:disabled {
.border ( @border-thickness-buttons, @alert3 );
background-color: white;
color: @alert3;
opacity: 0.5;
}
}
// TODO: johbo: Form button tweaks, check if we can use the classes instead
input[type="submit"] {
&:extend(.btn-primary);

View file

@ -61,6 +61,13 @@ tr.inline-comments div {
visibility: hidden;
}
.comment-draft {
float: left;
margin-right: 10px;
font-weight: 600;
color: @alert3;
}
.comment-label {
float: left;

View file

@ -349,7 +349,12 @@ var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions)
};
var submitForm = function(cm, pred) {
$(cm.display.input.textarea.form).submit();
$(cm.display.input.textarea.form).find('.submit-comment-action').click();
return CodeMirror.Pass;
};
var submitFormAsDraft = function(cm, pred) {
$(cm.display.input.textarea.form).find('.submit-draft-action').click();
return CodeMirror.Pass;
};
@ -475,9 +480,11 @@ var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions)
// submit form on Meta-Enter
if (OSType === "mac") {
extraKeys["Cmd-Enter"] = submitForm;
extraKeys["Shift-Cmd-Enter"] = submitFormAsDraft;
}
else {
extraKeys["Ctrl-Enter"] = submitForm;
extraKeys["Shift-Ctrl-Enter"] = submitFormAsDraft;
}
if (triggerActions) {

View file

@ -124,16 +124,20 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
this.statusChange = this.withLineNo('#change_status');
this.submitForm = formElement;
this.submitButton = $(this.submitForm).find('input[type="submit"]');
this.submitButton = $(this.submitForm).find('.submit-comment-action');
this.submitButtonText = this.submitButton.val();
this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
this.submitDraftButtonText = this.submitDraftButton.val();
this.previewUrl = pyroutes.url('repo_commit_comment_preview',
{'repo_name': templateContext.repo_name,
'commit_id': templateContext.commit_data.commit_id});
if (edit){
this.submitButtonText = _gettext('Updated Comment');
this.submitDraftButton.hide();
this.submitButtonText = _gettext('Update Comment');
$(this.commentType).prop('disabled', true);
$(this.commentType).addClass('disabled');
var editInfo =
@ -215,10 +219,17 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
this.getCommentStatus = function() {
return $(this.submitForm).find(this.statusChange).val();
};
this.getCommentType = function() {
return $(this.submitForm).find(this.commentType).val();
};
this.getDraftState = function () {
var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
var data = $(submitterElem).data('isDraft');
return data
}
this.getResolvesId = function() {
return $(this.submitForm).find(this.resolvesId).val() || null;
};
@ -233,7 +244,9 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
};
this.isAllowedToSubmit = function() {
return !$(this.submitButton).prop('disabled');
var commentDisabled = $(this.submitButton).prop('disabled');
var draftDisabled = $(this.submitDraftButton).prop('disabled');
return !commentDisabled && !draftDisabled;
};
this.initStatusChangeSelector = function(){
@ -259,11 +272,13 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
dropdownAutoWidth: true,
minimumResultsForSearch: -1
});
$(this.submitForm).find(this.statusChange).on('change', function() {
var status = self.getCommentStatus();
if (status && !self.isInline()) {
$(self.submitButton).prop('disabled', false);
$(self.submitDraftButton).prop('disabled', false);
}
var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
@ -295,10 +310,10 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
$(this.statusChange).select2('readonly', false);
};
this.globalSubmitSuccessCallback = function(){
this.globalSubmitSuccessCallback = function(comment){
// default behaviour is to call GLOBAL hook, if it's registered.
if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
commentFormGlobalSubmitSuccessCallback();
commentFormGlobalSubmitSuccessCallback(comment);
}
};
@ -321,6 +336,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
var text = self.cm.getValue();
var status = self.getCommentStatus();
var commentType = self.getCommentType();
var isDraft = self.getDraftState();
var resolvesCommentId = self.getResolvesId();
var closePullRequest = self.getClosePr();
@ -365,7 +381,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
}
// run global callback on submit
self.globalSubmitSuccessCallback();
self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
};
var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
@ -409,10 +425,20 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
}
$(this.submitButton).prop('disabled', submitState);
$(this.submitDraftButton).prop('disabled', submitState);
if (submitEvent) {
$(this.submitButton).val(_gettext('Submitting...'));
var isDraft = self.getDraftState();
if (isDraft) {
$(this.submitDraftButton).val(_gettext('Saving Draft...'));
} else {
$(this.submitButton).val(_gettext('Submitting...'));
}
} else {
$(this.submitButton).val(this.submitButtonText);
$(this.submitDraftButton).val(this.submitDraftButtonText);
}
};
@ -488,6 +514,7 @@ var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
if (!allowedToSubmit){
return false;
}
self.handleFormSubmit();
});
@ -659,7 +686,8 @@ var CommentsController = function() {
var $node = $(node);
var $td = $node.closest('td');
var $comment = $node.closest('.comment');
var comment_id = $comment.attr('data-comment-id');
var comment_id = $($comment).data('commentId');
var isDraft = $($comment).data('commentDraft');
var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
var postData = {
'csrf_token': CSRF_TOKEN
@ -677,7 +705,7 @@ var CommentsController = function() {
updateSticky()
}
if (window.refreshAllComments !== undefined) {
if (window.refreshAllComments !== undefined && !isDraft) {
// if we have this handler, run it, and refresh all comments boxes
refreshAllComments()
}
@ -716,6 +744,25 @@ var CommentsController = function() {
})
};
this._finalizeDrafts = function(commentIds) {
window.finalizeDrafts(commentIds)
}
this.finalizeDrafts = function(commentIds) {
SwalNoAnimation.fire({
title: _ngettext('Submit {0} draft comment', 'Submit {0} draft comments', commentIds.length).format(commentIds.length),
icon: 'warning',
showCancelButton: true,
confirmButtonText: _gettext('Yes, finalize drafts'),
}).then(function(result) {
if (result.value) {
self._finalizeDrafts(commentIds);
}
})
};
this.toggleWideMode = function (node) {
if ($('#content').hasClass('wrapper')) {
$('#content').removeClass("wrapper");
@ -916,7 +963,8 @@ var CommentsController = function() {
this.editComment = function(node) {
var $node = $(node);
var $comment = $(node).closest('.comment');
var comment_id = $comment.attr('data-comment-id');
var comment_id = $($comment).data('commentId');
var isDraft = $($comment).data('commentDraft');
var $form = null
var $comments = $node.closest('div.inline-comments');
@ -1002,6 +1050,7 @@ var CommentsController = function() {
'f_path': f_path,
'line': lineno,
'comment_type': commentType,
'draft': isDraft,
'version': version,
'csrf_token': CSRF_TOKEN
};
@ -1084,7 +1133,7 @@ var CommentsController = function() {
$comments.find('.cb-comment-add-button').before(html);
// run global callback on submit
commentForm.globalSubmitSuccessCallback();
commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
} catch (e) {
console.error(e);
@ -1101,7 +1150,7 @@ var CommentsController = function() {
updateSticky()
}
if (window.refreshAllComments !== undefined) {
if (window.refreshAllComments !== undefined && !isDraft) {
// if we have this handler, run it, and refresh all comments boxes
refreshAllComments()
}
@ -1178,6 +1227,7 @@ var CommentsController = function() {
var text = commentForm.cm.getValue();
var commentType = commentForm.getCommentType();
var resolvesCommentId = commentForm.getResolvesId();
var isDraft = commentForm.getDraftState();
if (text === "") {
return;
@ -1201,6 +1251,7 @@ var CommentsController = function() {
'f_path': f_path,
'line': lineno,
'comment_type': commentType,
'draft': isDraft,
'csrf_token': CSRF_TOKEN
};
if (resolvesCommentId){
@ -1222,7 +1273,7 @@ var CommentsController = function() {
}
// run global callback on submit
commentForm.globalSubmitSuccessCallback();
commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
} catch (e) {
console.error(e);
@ -1239,7 +1290,7 @@ var CommentsController = function() {
updateSticky()
}
if (window.refreshAllComments !== undefined) {
if (window.refreshAllComments !== undefined && !isDraft) {
// if we have this handler, run it, and refresh all comments boxes
refreshAllComments()
}

View file

@ -1225,6 +1225,14 @@
(function () {
"use sctrict";
// details block auto-hide menu
$(document).mouseup(function(e) {
var container = $('.details-inline-block');
if (!container.is(e.target) && container.has(e.target).length === 0) {
$('.details-inline-block[open]').removeAttr('open')
}
});
var $sideBar = $('.right-sidebar');
var expanded = $sideBar.hasClass('right-sidebar-expanded');
var sidebarState = templateContext.session_attrs.sidebarState;

View file

@ -31,8 +31,12 @@
<%
display = ''
_cls = ''
## Extra precaution to not show drafts in the sidebar for todo/comments
if comment_obj.draft:
continue
%>
<%
comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
prev_comment_ver_index = 0

View file

@ -3,20 +3,25 @@
## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
## ${comment.comment_block(comment)}
##
<%namespace name="base" file="/base/base.mako"/>
<%!
from rhodecode.lib import html_filters
%>
<%namespace name="base" file="/base/base.mako"/>
<%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
<%
from rhodecode.model.comment import CommentsModel
comment_model = CommentsModel()
from rhodecode.model.comment import CommentsModel
comment_model = CommentsModel()
comment_ver = comment.get_index_version(getattr(c, 'versions', []))
latest_ver = len(getattr(c, 'versions', []))
visible_for_user = True
if comment.draft:
visible_for_user = comment.user_id == c.rhodecode_user.user_id
%>
<% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
<% latest_ver = len(getattr(c, 'versions', [])) %>
% if inline:
<% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
@ -24,6 +29,7 @@
<% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
% endif
% if visible_for_user:
<div class="comment
${'comment-inline' if inline else 'comment-general'}
${'comment-outdated' if outdated_at_ver else 'comment-current'}"
@ -31,6 +37,7 @@
line="${comment.line_no}"
data-comment-id="${comment.comment_id}"
data-comment-type="${comment.comment_type}"
data-comment-draft=${h.json.dumps(comment.draft)}
data-comment-renderer="${comment.renderer}"
data-comment-text="${comment.text | html_filters.base64,n}"
data-comment-line-no="${comment.line_no}"
@ -39,6 +46,9 @@
<div class="meta">
<div class="comment-type-label">
% if comment.draft:
<div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">DRAFT</div>
% endif
<div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
## TODO COMMENT
@ -90,7 +100,7 @@
</div>
</div>
## NOTE 0 and .. => because we disable it for now until UI ready
% if 0 and comment.status_change:
<div class="pull-left">
<span class="tag authortag tooltip" title="${_('Status from pull request.')}">
@ -100,10 +110,12 @@
</span>
</div>
% endif
## Since only author can see drafts, we don't show it
% if not comment.draft:
<div class="author ${'author-inline' if inline else 'author-general'}">
${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
</div>
% endif
<div class="date">
${h.age_component(comment.modified_at, time_is_local=True)}
@ -215,6 +227,11 @@
<div class="dropdown-item">
<a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
</div>
% if comment.draft:
<div class="dropdown-item">
<a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
</div>
% endif
%else:
<div class="dropdown-divider"></div>
<div class="dropdown-item">
@ -252,6 +269,7 @@
</div>
</div>
% endif
</%def>
## generate main comments
@ -311,6 +329,7 @@
var text = self.cm.getValue();
var status = self.getCommentStatus();
var commentType = self.getCommentType();
var isDraft = self.getDraftState();
if (text === "" && !status) {
return;
@ -337,6 +356,7 @@
'text': text,
'changeset_status': status,
'comment_type': commentType,
'draft': isDraft,
'commit_ids': commitIds,
'csrf_token': CSRF_TOKEN
};
@ -477,14 +497,18 @@
<div class="action-buttons-extra"></div>
% endif
<input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
<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')">
% if form_type == 'inline':
<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')">
% endif
## inline for has a file, and line-number together with cancel hide button.
% if form_type == 'inline':
<input type="hidden" name="f_path" value="{0}">
<input type="hidden" name="line" value="${lineno_id}">
<button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
${_('Cancel')}
<button type="button" class="tooltip cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);" title="Hide comment box">
<i class="icon-cancel-circled2"></i>
</button>
% endif
</div>

View file

@ -711,16 +711,19 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
data-line-no="${line.original.lineno}"
>
<% line_old_comments = None %>
<% line_old_comments, line_old_comments_no_drafts = None, None %>
%if line.original.get_comment_args:
<% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
<%
line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args)
line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else []
has_outdated = any([x.outdated for x in line_old_comments_no_drafts])
%>
%endif
%if line_old_comments:
<% has_outdated = any([x.outdated for x in line_old_comments]) %>
%if line_old_comments_no_drafts:
% if has_outdated:
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% else:
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% endif
%endif
</td>
@ -752,18 +755,20 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
>
<div>
<% line_new_comments, line_new_comments_no_drafts = None, None %>
%if line.modified.get_comment_args:
<% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
%else:
<% line_new_comments = None%>
<%
line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args)
line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
has_outdated = any([x.outdated for x in line_new_comments_no_drafts])
%>
%endif
%if line_new_comments:
<% has_outdated = any([x.outdated for x in line_new_comments]) %>
%if line_new_comments_no_drafts:
% if has_outdated:
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_new_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% else:
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% endif
%endif
</div>
@ -814,20 +819,22 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
<td class="cb-data ${action_class(action)}">
<div>
%if comments_args:
<% comments = get_comments_for('unified', inline_comments, *comments_args) %>
%else:
<% comments = None %>
%endif
<% comments, comments_no_drafts = None, None %>
%if comments_args:
<%
comments = get_comments_for('unified', inline_comments, *comments_args)
comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
has_outdated = any([x.outdated for x in comments_no_drafts])
%>
%endif
% if comments:
<% has_outdated = any([x.outdated for x in comments]) %>
% if has_outdated:
<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>
% else:
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% if comments_no_drafts:
% if has_outdated:
<i class="tooltip icon-comment-toggle" title="${_('comments including outdated: {}. Click here to display them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% else:
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
% endif
% endif
% endif
</div>
</td>
<td class="cb-lineno ${action_class(action)}"

View file

@ -980,9 +980,11 @@ window.setObserversData = ${c.pull_request_set_observers_data_json | n};
$(btns).each(fn_display);
});
// register submit callback on commentForm form to track TODOs
window.commentFormGlobalSubmitSuccessCallback = function () {
refreshMergeChecks();
// register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
window.commentFormGlobalSubmitSuccessCallback = function (comment) {
if (!comment.draft) {
refreshMergeChecks();
}
};
ReviewerAutoComplete('#user', reviewersController);
@ -995,6 +997,12 @@ $(document).ready(function () {
var channel = '${c.pr_broadcast_channel}';
new ReviewerPresenceController(channel)
window.finalizeDrafts = function(commentIds) {
alert('okok !' + commentIds)
}
})
</script>