Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System comments #2032

Merged
merged 1 commit into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion web/api/v6/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ enum ExtendedReportDataType {
FIXIT = 20,
}

enum CommentKind {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking maybe it would be better if we introduce some prefix for the thrift types.
There are conversion functions which convert from CommentKind to CommentKind one of them is a thrift enum and the other is a local enum it is hard to follow which type is used where and what is converted to what.

USER, // User-given comments.
SYSTEM // System events.
}

struct SourceFileData {
1: i64 fileId,
2: string filePath,
Expand Down Expand Up @@ -280,7 +285,8 @@ struct CommentData {
1: i64 id,
2: string author,
3: string message,
4: string createdAt
4: string createdAt,
5: CommentKind kind
}
typedef list<CommentData> CommentDataList

Expand Down
2 changes: 1 addition & 1 deletion web/codechecker_web/shared/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# The newest supported minor version (value) for each supported major version
# (key) in this particular build.
SUPPORTED_VERSIONS = {
6: 23
6: 24
}

# Used by the client to automatically identify the latest major and minor
Expand Down
11 changes: 11 additions & 0 deletions web/codechecker_web/shared/webserver_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def __init__(self, package_root, pckg_layout, cfg_dict):
self._package_root = package_root
self._severity_map = SeverityMap(
load_json_or_empty(self.checkers_severity_map_file, {}))
self.__system_comment_map = \
load_json_or_empty(self.system_comment_map_file, {})
self.__package_version = None
self.__package_build_date = None
self.__package_git_hash = None
Expand Down Expand Up @@ -110,6 +112,15 @@ def package_git_tag(self):
def version_file(self):
return os.path.join(self._package_root, 'config', 'web_version.json')

@property
def system_comment_map(self):
return self.__system_comment_map

@property
def system_comment_map_file(self):
return os.path.join(self._package_root, 'config',
'system_comment_kinds.json')

@property
def path_plist_to_html_dist(self):
return os.path.join(self.package_root, 'lib', 'python2.7',
Expand Down
5 changes: 5 additions & 0 deletions web/config/system_comment_kinds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rev_st_changed": "%author% changed review status from <b class=\"old-review-status\">{0}</b> to <b class=\"new-review-status\">{1}</b> %date%",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to store the whole text, couldn't we create some more dense representation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not store the whole text in the database we just store the type of the system comment and the arguments: rev_st_changed Unreviewed Confirmed. We are using this file only to display the comment in the gui.

"rev_st_changed_msg": "%author% changed review status from <b class=\"old-review-status\">{0}</b> to <b class=\"new-review-status\">{1}</b> with <i>{2}</i> message %date%",
"comment_changed": "%author% changed comment message from <b class=\"old-comment-message\">{0}</b> to <b class=\"new-comment-message\">{1}</b> %date%"
}
122 changes: 109 additions & 13 deletions web/server/codechecker_server/api/report_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io
import os
import re
import shlex
import tempfile
import zipfile
import zlib
Expand All @@ -40,6 +41,8 @@
from codechecker_common.logger import get_logger
from codechecker_common.report import get_report_path_hash

from codechecker_web.shared import webserver_context

from codechecker_server.profiler import timeit

from .. import permissions
Expand All @@ -62,6 +65,27 @@
LOG = get_logger('server')


class CommentKindValue(object):
USER = 0
SYSTEM = 1


def comment_kind_from_thrift_type(kind):
""" Convert the given comment kind from Thrift type to Python enum. """
if kind == ttypes.CommentKind.USER:
return CommentKindValue.USER
elif kind == ttypes.CommentKind.SYSTEM:
return CommentKindValue.SYSTEM


def comment_kind_to_thrift_type(kind):
""" Convert the given comment kind from Python enum to Thrift type. """
if kind == CommentKindValue.USER:
return ttypes.CommentKind.USER
elif kind == CommentKindValue.SYSTEM:
return ttypes.CommentKind.SYSTEM


def verify_limit_range(limit):
"""Verify limit value for the queries.

Expand Down Expand Up @@ -653,6 +677,17 @@ def sort_run_data_query(query, sort_mode):
return query


def escape_whitespaces(s, whitespaces=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add function docstring.

if not whitespaces:
whitespaces = [' ', '\n', '\t', '\r']

escaped = s
for w in whitespaces:
escaped = escaped.replace(w, '\\{0}'.format(w))

return escaped


class ThriftRequestHandler(object):
"""
Connect to database and handle thrift client requests.
Expand Down Expand Up @@ -736,7 +771,15 @@ def __get_run_ids_to_query(session, cmp_data=None):

return run_ids

@exc_to_thrift_reqfail
def __add_comment(self, bug_id, message, kind=CommentKindValue.USER):
""" Creates a new comment object. """
user = self.__get_username()
return Comment(bug_id,
user,
message,
kind,
datetime.now())

@timeit
def getRunData(self, run_filter, limit, offset, sort_mode):
self.__require_access()
Expand Down Expand Up @@ -1331,12 +1374,29 @@ def _setReviewStatus(self, report_id, status, message, session):

user = self.__get_username()

old_status = review_status.status if review_status.status \
else review_status_str(ttypes.ReviewStatus.UNREVIEWED)

review_status.status = review_status_str(status)
review_status.author = user
review_status.message = message.encode('utf8')
review_status.date = datetime.now()

session.add(review_status)

if message:
system_comment_msg = 'rev_st_changed_msg {0} {1} {2}'.format(
escape_whitespaces(old_status.capitalize()),
escape_whitespaces(review_status.status.capitalize()),
escape_whitespaces(message))
else:
system_comment_msg = 'rev_st_changed {0} {1}'.format(
escape_whitespaces(old_status.capitalize()),
escape_whitespaces(review_status.status.capitalize()))

system_comment = self.__add_comment(review_status.bug_hash,
system_comment_msg,
CommentKindValue.SYSTEM)
session.add(system_comment)
session.flush()

return True
Expand Down Expand Up @@ -1397,12 +1457,28 @@ def getComments(self, report_id):
.order_by(Comment.created_at.desc()) \
.all()

context = webserver_context.get_context()
for comment in comments:
message = comment.message

sys_comment = comment_kind_from_thrift_type(
ttypes.CommentKind.SYSTEM)
if comment.kind == sys_comment:
elements = shlex.split(message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need the shlex.split here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we store a system comment in the database, we will escape the white spaces (escape_whitespaces). We are using shlex here to split this escaped string properly.

system_comment = context.system_comment_map.get(
elements[0])
if system_comment:
for idx, value in enumerate(elements[1:]):
system_comment = system_comment.replace(
'{' + str(idx) + '}', value)
message = system_comment

result.append(CommentData(
comment.id,
comment.author,
comment.message,
str(comment.created_at)))
message,
str(comment.created_at),
comment_kind_to_thrift_type(comment.kind)))

return result
else:
Expand Down Expand Up @@ -1433,19 +1509,19 @@ def getCommentCount(self, report_id):
@exc_to_thrift_reqfail
@timeit
def addComment(self, report_id, comment_data):
"""
Add new comment for the given bug.
"""
""" Add new comment for the given bug. """
self.__require_access()

if not comment_data.message.strip():
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.GENERAL,
'The comment message can not be empty!')

with DBSession(self.__Session) as session:
report = session.query(Report).get(report_id)
if report:
user = self.__get_username()
comment = Comment(report.bug_id,
user,
comment_data.message,
datetime.now())

comment = self.__add_comment(report.bug_id,
comment_data.message)
session.add(comment)
session.commit()

Expand All @@ -1466,6 +1542,12 @@ def updateComment(self, comment_id, content):
Anyonymous comments that can be updated by anybody.
"""
self.__require_access()

if not content.strip():
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.GENERAL,
'The comment message can not be empty!')

with DBSession(self.__Session) as session:

user = self.__get_username()
Expand All @@ -1476,8 +1558,22 @@ def updateComment(self, comment_id, content):
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED,
'Unathorized comment modification!')

# Create system comment if the message is changed.
if comment.message != content:
system_comment_msg = 'comment_changed {0} {1}'.format(
escape_whitespaces(comment.message),
escape_whitespaces(content))

system_comment = \
self.__add_comment(comment.bug_hash,
system_comment_msg,
CommentKindValue.SYSTEM)
session.add(system_comment)

comment.message = content
session.add(comment)

session.commit()
return True
else:
Expand Down
9 changes: 8 additions & 1 deletion web/server/codechecker_server/database/run_db_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,19 @@ class Comment(Base):
bug_hash = Column(String, nullable=False, index=True)
author = Column(String, nullable=False)
message = Column(Binary, nullable=False)

# Default value is 0 which means a user given comment.
kind = Column(Integer,
nullable=False,
server_default="0")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a comment about the default value here would be nice. '0' means a comment type by the user tight?


created_at = Column(DateTime, nullable=False)

def __init__(self, bug_hash, author, message, created_at):
def __init__(self, bug_hash, author, message, kind, created_at):
self.bug_hash = bug_hash
self.author = author
self.message = message
self.kind = kind
self.created_at = created_at


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""System comments

Revision ID: 6cb6a3a41967
Revises: 3e91d0612422
Create Date: 2019-04-02 16:12:46.794131

"""

# revision identifiers, used by Alembic.
revision = '6cb6a3a41967'
down_revision = '3e91d0612422'
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column('comments', sa.Column('kind',
sa.Integer(),
server_default="0",
nullable=False),)


def downgrade():
op.drop_column('comments', 'kind')
Loading