launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #22421
[Merge] lp:~cjwatson/launchpad/bmp-hide-comments into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/bmp-hide-comments into lp:launchpad with lp:~cjwatson/launchpad/bmp-remove-old-comment-threading as a prerequisite.
Commit message:
Add spam controls to code review comments.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1680746 in Launchpad itself: "No way to remove merge proposal comment spam"
https://bugs.launchpad.net/launchpad/+bug/1680746
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/bmp-hide-comments/+merge/344225
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/bmp-hide-comments into lp:launchpad.
=== modified file 'lib/lp/app/javascript/comment.js'
--- lib/lp/app/javascript/comment.js 2017-09-22 19:41:16 +0000
+++ lib/lp/app/javascript/comment.js 2018-04-25 12:03:26 +0000
@@ -72,6 +72,16 @@
});
},
+ /**
+ * Bind the UI to a new comment.
+ *
+ * @method bind_new_comment
+ */
+ bind_new_comment: function(comment_node) {
+ comment_node.delegate(
+ 'click', this.toggle_hidden, 'a.mark-spam', this);
+ },
+
destructor: function() {
var comment_list_container = this.get('comment_list_container');
comment_list_container.detachAll();
@@ -475,7 +485,7 @@
var comment = Y.Node.create(message_html);
conversation.appendChild(comment);
this.reset_contents();
- this.fire('CodeReviewComment.appended');
+ this.fire('CodeReviewComment.appended', comment);
Y.lp.anim.green_flash({node: comment}).run();
},
=== modified file 'lib/lp/app/javascript/tests/test_comment.html'
--- lib/lp/app/javascript/tests/test_comment.html 2012-10-26 09:54:28 +0000
+++ lib/lp/app/javascript/tests/test_comment.html 2018-04-25 12:03:26 +0000
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<!--
-Copyright 2012 Canonical Ltd. This software is licensed under the
+Copyright 2012-2018 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -32,7 +32,9 @@
<script type="text/javascript"
src="../../../../../build/js/lp/app/client.js"></script>
<script type="text/javascript"
- src="../../../../../build/js/lp/app/client.js"></script>
+ src="../../../../../build/js/lp/app/anim/anim.js"></script>
+ <script type="text/javascript"
+ src="../../../../../build/js/lp/app/extras/extras.js"></script>
<!-- The module under test. -->
<script type="text/javascript" src="../comment.js"></script>
=== modified file 'lib/lp/app/javascript/tests/test_comment.js'
--- lib/lp/app/javascript/tests/test_comment.js 2017-09-22 19:41:16 +0000
+++ lib/lp/app/javascript/tests/test_comment.js 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-/* Copyright 2012 Canonical Ltd. This software is licensed under the
+/* Copyright 2012-2018 Canonical Ltd. This software is licensed under the
* GNU Affero General Public License version 3 (see the file LICENSE). */
YUI.add('lp.app.comment.test', function (Y) {
@@ -120,6 +120,48 @@
comment.insert_comment_HTML('<span id="fake"></span>');
Y.Assert.isTrue(reset_contents_called);
Y.Assert.isObject(Y.one('#fake'));
+ },
+
+ test_insert_code_review_comment_html: function () {
+ // Test CodeReviewComment.insert_comment_HTML
+ window.LP = {
+ cache: {
+ context: { self_link: '/mp/1/' }
+ }
+ };
+ Y.one('body').appendChild(
+ Y.Node.create('<div id="conversation"/>'));
+ var mock_client = new Y.lp.testing.helpers.LPClient();
+ mock_client.named_post.args = [];
+ mock_client.get.args = [];
+ var comment = new Y.lp.app.comment.CodeReviewComment({
+ animate: false,
+ lp_client: mock_client
+ });
+ var reset_contents_called = false;
+ comment.reset_contents = function () {
+ reset_contents_called = true;
+ };
+ var new_comment_node = null;
+ comment.on('CodeReviewComment.appended', function (comment_node) {
+ new_comment_node = comment_node;
+ });
+ var green_flash = Y.lp.anim.green_flash;
+ var green_flashed = false;
+ Y.lp.anim.green_flash = function () {
+ return {
+ run: function () {
+ green_flashed = true;
+ }
+ };
+ };
+ comment.insert_comment_HTML('<span id="fake"></span>');
+ Y.Assert.isTrue(reset_contents_called);
+ Y.Assert.isObject(new_comment_node);
+ Y.Assert.isObject(Y.one('#fake'));
+ Y.Assert.areEqual(new_comment_node.get('id'), 'fake');
+ Y.Assert.isTrue(green_flashed);
+ Y.lp.anim.green_flash = green_flash;
}
}));
=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py 2018-04-25 12:03:26 +0000
+++ lib/lp/code/browser/branchmergeproposal.py 2018-04-25 12:03:26 +0000
@@ -123,6 +123,7 @@
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.escaping import structured
+from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.menu import NavigationMenu
@@ -436,9 +437,14 @@
except ValueError:
return None
try:
- return self.context.getComment(id)
+ comment = self.context.getComment(id)
except WrongBranchMergeProposal:
return None
+ user = getUtility(ILaunchBag).user
+ if comment.visible or comment.userCanSetCommentVisibility(user):
+ return comment
+ else:
+ return None
@stepthrough("+preview-diff")
def traverse_preview_diff(self, id):
@@ -591,6 +597,8 @@
self.comment_date = None
self.display_attachments = False
self.index = None
+ self.visible = True
+ self.show_spam_controls = False
def download(self, request):
pass
@@ -661,6 +669,8 @@
source = merge_proposal.merge_source
if IBranch.providedBy(source):
source = DecoratedBranch(source)
+ user = getUtility(ILaunchBag).user
+ strip_invisible = not merge_proposal.userCanSetCommentVisibility(user)
comments = []
if (getFeatureFlag('code.incremental_diffs.enabled') and
merge_proposal.source_branch is not None):
@@ -688,6 +698,8 @@
for comment in merge_proposal.all_comments)
merge_proposal = merge_proposal.supersedes
comments = sorted(comments, key=operator.attrgetter('date'))
+ if strip_invisible:
+ comments = [c for c in comments if c.visible or c.author == user]
self._populate_previewdiffs(comments)
return CodeReviewConversation(comments)
=== modified file 'lib/lp/code/browser/codereviewcomment.py'
--- lib/lp/code/browser/codereviewcomment.py 2015-07-09 20:06:17 +0000
+++ lib/lp/code/browser/codereviewcomment.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -50,6 +50,7 @@
LaunchpadView,
Link,
)
+from lp.services.webapp.interfaces import ILaunchBag
class ICodeReviewDisplayComment(IComment, ICodeReviewComment):
@@ -85,10 +86,11 @@
@property
def extra_css_class(self):
+ css_classes = (
+ super(CodeReviewDisplayComment, self).extra_css_class.split())
if self.from_superseded:
- return 'from-superseded'
- else:
- return ''
+ css_classes.append('from-superseded')
+ return ' '.join(css_classes)
@cachedproperty
def previewdiff_id(self):
@@ -121,6 +123,11 @@
def download_url(self):
return canonical_url(self.comment, view_name='+download')
+ @cachedproperty
+ def show_spam_controls(self):
+ user = getUtility(ILaunchBag).user
+ return self.comment.userCanSetCommentVisibility(user)
+
def get_message(display_comment):
"""Adapt an ICodeReviwComment to an IMessage."""
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2018-03-16 21:20:00 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2018-04-25 12:03:26 +0000
@@ -26,6 +26,10 @@
Tag,
Within,
)
+from testscenarios import (
+ load_tests_apply_scenarios,
+ WithScenarios,
+ )
from testtools.matchers import (
ContainsDict,
DocTestMatches,
@@ -77,6 +81,10 @@
make_merge_proposal_without_reviewers,
)
from lp.code.xmlrpc.git import GitAPI
+from lp.coop.answersbugs.visibility import (
+ TestHideMessageControlMixin,
+ TestMessageVisibilityMixin,
+ )
from lp.registry.enums import (
PersonVisibility,
TeamMembershipPolicy,
@@ -1899,6 +1907,64 @@
git_proposal.merge_source.commit_sha1, view.source_revid)
+class TestCodeReviewCommentVisibility(
+ WithScenarios, BrowserTestCase, TestMessageVisibilityMixin):
+
+ layer = DatabaseFunctionalLayer
+
+ scenarios = [
+ ('bzr', {'git': False}),
+ ('git', {'git': True}),
+ ]
+
+ def makeHiddenMessage(self, comment_owner=None):
+ """See `TestMessageVisibilityMixin`."""
+ if self.git:
+ self.useFixture(GitHostingFixture())
+ comment = self.factory.makeCodeReviewComment(
+ sender=comment_owner, body=self.comment_text, git=self.git)
+ with admin_logged_in():
+ comment.message.visible = False
+ return comment.branch_merge_proposal
+
+ def getView(self, context, user=None, no_login=False):
+ """See `TestMessageVisibilityMixin`."""
+ return self.getViewBrowser(
+ context=context, user=user, no_login=no_login)
+
+
+class TestCodeReviewCommentHideControls(
+ WithScenarios, BrowserTestCase, TestHideMessageControlMixin):
+
+ layer = DatabaseFunctionalLayer
+
+ scenarios = [
+ ('bzr', {'git': False}),
+ ('git', {'git': True}),
+ ]
+
+ def getContext(self, comment_owner=None):
+ """See `TestHideMessageControlMixin`."""
+ if self.git:
+ self.useFixture(GitHostingFixture())
+ comment = self.factory.makeCodeReviewComment(
+ sender=comment_owner, git=self.git)
+ self.control_text = 'mark-spam-%d' % comment.id
+ return comment.branch_merge_proposal
+
+ def getView(self, context, user=None, no_login=False):
+ """See `TestHideMessageControlMixin`."""
+ return self.getViewBrowser(
+ context=context, user=user, no_login=no_login)
+
+ def test_comment_owner_sees_hide_control(self):
+ user = self.factory.makePerson()
+ context = self.getContext(comment_owner=user)
+ view = self.getView(context=context, user=user)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
+
+
class TestCommentAttachmentRendering(TestCaseWithFactory):
"""Test diff attachments are rendered correctly."""
@@ -2308,3 +2374,6 @@
def _makeBranchMergeProposal(self, **kwargs):
return self.factory.makeBranchMergeProposalForGit(**kwargs)
+
+
+load_tests = load_tests_apply_scenarios
=== modified file 'lib/lp/code/browser/tests/test_codereviewcomment.py'
--- lib/lp/code/browser/tests/test_codereviewcomment.py 2017-10-04 01:16:22 +0000
+++ lib/lp/code/browser/tests/test_codereviewcomment.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Unit tests for CodeReviewComments."""
@@ -24,9 +24,12 @@
from lp.code.interfaces.codereviewinlinecomment import (
ICodeReviewInlineCommentSet,
)
+from lp.services.propertycache import clear_property_cache
from lp.services.webapp import canonical_url
from lp.testing import (
+ admin_logged_in,
BrowserTestCase,
+ celebrity_logged_in,
person_logged_in,
StormStatementRecorder,
TestCaseWithFactory,
@@ -52,6 +55,34 @@
verifyObject(ICodeReviewDisplayComment, display_comment)
+ def test_extra_css_classes_visibility(self):
+ author = self.factory.makePerson()
+ comment = self.factory.makeCodeReviewComment(sender=author)
+ display_comment = CodeReviewDisplayComment(comment)
+ self.assertEqual('', display_comment.extra_css_class)
+ with person_logged_in(author):
+ display_comment.message.setVisible(False)
+ self.assertEqual(
+ 'adminHiddenComment', display_comment.extra_css_class)
+
+ def test_show_spam_controls_permissions(self):
+ # Admins, registry experts, and the author of the comment itself can
+ # hide comments, but other people can't.
+ author = self.factory.makePerson()
+ comment = self.factory.makeCodeReviewComment(sender=author)
+ display_comment = CodeReviewDisplayComment(comment)
+ with person_logged_in(author):
+ self.assertTrue(display_comment.show_spam_controls)
+ clear_property_cache(display_comment)
+ with person_logged_in(self.factory.makePerson()):
+ self.assertFalse(display_comment.show_spam_controls)
+ clear_property_cache(display_comment)
+ with celebrity_logged_in('registry_experts'):
+ self.assertTrue(display_comment.show_spam_controls)
+ clear_property_cache(display_comment)
+ with admin_logged_in():
+ self.assertTrue(display_comment.show_spam_controls)
+
class TestCodeReviewCommentInlineComments(TestCaseWithFactory):
"""Test `CodeReviewDisplayComment` integration with inline-comments."""
=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
--- lib/lp/code/interfaces/branchmergeproposal.py 2018-03-16 21:20:00 +0000
+++ lib/lp/code/interfaces/branchmergeproposal.py 2018-04-25 12:03:26 +0000
@@ -376,7 +376,41 @@
@operation_returns_entry(Interface)
@export_read_operation()
def getComment(id):
- """Return the CodeReviewComment with the specified ID."""
+ """Return the CodeReviewComment with the specified ID.
+
+ :raises WrongBranchMergeProposal: if the comment with this ID is not
+ on this merge proposal.
+ """
+
+ def userCanSetCommentVisibility(user):
+ """Can `user` set code review comment visibility?
+
+ Admins and registry experts can set the visibility of any code
+ review comment.
+
+ Comment authors can also set the visibility of their own comments,
+ but that is not checked here; this method determines whether
+ arbitrary users can set the visibility of comments they did not make
+ themselves.
+ """
+
+ @operation_parameters(
+ # Really comment_id, but this lets us use common JavaScript code.
+ comment_number=Int(title=_('The comment ID.'), required=True),
+ visible=Bool(title=_('Show this comment?'), required=True))
+ @call_with(user=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version('devel')
+ def setCommentVisibility(user, comment_number, visible):
+ """Set the visible attribute on a code review comment.
+
+ This is restricted to Launchpad admins, registry experts, and
+ comment authors, and will return a HTTP Error 401: Unauthorized
+ error for non-admin callers.
+
+ :raises WrongBranchMergeProposal: if the comment with this ID is not
+ on this merge proposal.
+ """
@call_with(user=REQUEST_USER)
# Really IBugTask.
=== modified file 'lib/lp/code/interfaces/codereviewcomment.py'
--- lib/lp/code/interfaces/codereviewcomment.py 2013-01-07 02:40:55 +0000
+++ lib/lp/code/interfaces/codereviewcomment.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""CodeReviewComment interfaces."""
@@ -16,6 +16,7 @@
from lazr.restful.fields import Reference
from zope.interface import Interface
from zope.schema import (
+ Bool,
Choice,
Datetime,
Int,
@@ -87,6 +88,16 @@
title=_('The message as quoted in email.'),
readonly=True))
+ visible = Bool(title=_('Whether this comment is visible.'))
+
+ def userCanSetCommentVisibility(user):
+ """Can `user` set the visibility of this comment?
+
+ Admins and registry experts can set the visibility of any code
+ review comment. Comment authors can set the visibility of their own
+ comments.
+ """
+
class ICodeReviewCommentDeletion(Interface):
"""This interface provides deletion of CodeReviewComments.
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2017-11-24 17:22:34 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database class for branch merge proposals."""
@@ -43,6 +43,7 @@
from zope.error.interfaces import IErrorReportingUtility
from zope.event import notify
from zope.interface import implementer
+from zope.security.interfaces import Unauthorized
from lp.app.enums import PRIVATE_INFORMATION_TYPES
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
@@ -97,6 +98,7 @@
validate_public_person,
)
from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.role import IPersonRoles
from lp.registry.model.person import Person
from lp.services.config import config
from lp.services.database.bulk import (
@@ -560,14 +562,30 @@
return CodeReviewComment.selectBy(branch_merge_proposal=self.id)
def getComment(self, id):
- """See `IBranchMergeProposal`.
-
- This function can raise WrongBranchMergeProposal."""
+ """See `IBranchMergeProposal`."""
comment = CodeReviewComment.get(id)
if comment.branch_merge_proposal != self:
raise WrongBranchMergeProposal
return comment
+ def userCanSetCommentVisibility(self, user):
+ """See `IBranchMergeProposal`."""
+ if user is None:
+ return False
+ roles = IPersonRoles(user)
+ return roles.in_admin or roles.in_registry_experts
+
+ def setCommentVisibility(self, user, comment_number, visible):
+ """See `IBranchMergeProposal`."""
+ comment = CodeReviewComment.get(comment_number)
+ if comment.branch_merge_proposal != self:
+ raise WrongBranchMergeProposal
+ if not comment.userCanSetCommentVisibility(user):
+ raise Unauthorized(
+ "User %s cannot hide or show code review comments." %
+ (user.name if user is not None else "<anonymous>"))
+ comment.message.setVisible(visible)
+
def getVoteReference(self, id):
"""See `IBranchMergeProposal`.
=== modified file 'lib/lp/code/model/codereviewcomment.py'
--- lib/lp/code/model/codereviewcomment.py 2015-10-19 10:56:16 +0000
+++ lib/lp/code/model/codereviewcomment.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""The database implementation class for CodeReviewComment."""
@@ -124,3 +124,13 @@
if self.message.raw is None:
return None
return signed_message_from_string(self.message.raw.read())
+
+ @property
+ def visible(self):
+ return self.message.visible
+
+ def userCanSetCommentVisibility(self, user):
+ """See `ICodeReviewComment`."""
+ return (
+ self.branch_merge_proposal.userCanSetCommentVisibility(user) or
+ (user is not None and user.inTeam(self.author)))
=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
--- lib/lp/code/model/tests/test_branchmergeproposal.py 2018-01-02 16:10:26 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposal.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for BranchMergeProposals."""
@@ -32,6 +32,7 @@
)
import transaction
from zope.component import getUtility
+from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
@@ -607,6 +608,57 @@
self.merge_proposal2.getComment, self.comment.id)
+class TestMergeProposalSetCommentVisibility(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_anonymous(self):
+ comment = self.factory.makeCodeReviewComment()
+ self.assertRaisesWithContent(
+ Unauthorized,
+ "User <anonymous> cannot hide or show code review comments.",
+ comment.branch_merge_proposal.setCommentVisibility,
+ user=None, comment_number=comment.id, visible=False)
+
+ def test_random_user(self):
+ comment = self.factory.makeCodeReviewComment()
+ person = self.factory.makePerson()
+ self.assertRaisesWithContent(
+ Unauthorized,
+ "User %s cannot hide or show code review comments." % person.name,
+ comment.branch_merge_proposal.setCommentVisibility,
+ user=person, comment_number=comment.id, visible=False)
+
+ def test_comment_author(self):
+ comment = self.factory.makeCodeReviewComment()
+ another_comment = self.factory.makeCodeReviewComment(
+ merge_proposal=comment.branch_merge_proposal)
+ comment.branch_merge_proposal.setCommentVisibility(
+ user=comment.author, comment_number=comment.id, visible=False)
+ self.assertFalse(comment.visible)
+ self.assertTrue(another_comment.visible)
+
+ def test_registry_expert(self):
+ comment = self.factory.makeCodeReviewComment()
+ another_comment = self.factory.makeCodeReviewComment(
+ merge_proposal=comment.branch_merge_proposal)
+ comment.branch_merge_proposal.setCommentVisibility(
+ user=self.factory.makeRegistryExpert(), comment_number=comment.id,
+ visible=False)
+ self.assertFalse(comment.visible)
+ self.assertTrue(another_comment.visible)
+
+ def test_admin(self):
+ comment = self.factory.makeCodeReviewComment()
+ another_comment = self.factory.makeCodeReviewComment(
+ merge_proposal=comment.branch_merge_proposal)
+ comment.branch_merge_proposal.setCommentVisibility(
+ user=self.factory.makeAdministrator(), comment_number=comment.id,
+ visible=False)
+ self.assertFalse(comment.visible)
+ self.assertTrue(another_comment.visible)
+
+
class TestMergeProposalGetVoteReference(TestCaseWithFactory):
"""Tester for `BranchMergeProposal.getComment`."""
=== modified file 'lib/lp/code/templates/branchmergeproposal-index.pt'
--- lib/lp/code/templates/branchmergeproposal-index.pt 2016-05-19 01:04:56 +0000
+++ lib/lp/code/templates/branchmergeproposal-index.pt 2018-04-25 12:03:26 +0000
@@ -218,18 +218,25 @@
Y.on('load', function() {
var logged_in = LP.links['me'] !== undefined;
- var ic = Y.lp.code.branchmergeproposal.inlinecomments;
- var diffnav = new ic.DiffNav({srcNode: Y.one('#review-diff')});
+ var comment = null;
if (logged_in) {
- var comment = new Y.lp.app.comment.CodeReviewComment();
+ comment = new Y.lp.app.comment.CodeReviewComment();
comment.render();
- comment.on('CodeReviewComment.appended', function () {
- diffnav.update_on_new_comment();
- });
- Y.lp.code.branchmergeproposal.status.connect_status(conf);
- }
- Y.lp.code.branchmergeproposal.reviewcomment.connect_links();
- diffnav.render();
+ }
+
+ var review_diff_node = Y.one('#review-diff');
+ if (review_diff_node !== null) {
+ var ic = Y.lp.code.branchmergeproposal.inlinecomments;
+ var diffnav = new ic.DiffNav({srcNode: review_diff_node});
+ if (comment !== null) {
+ comment.on('CodeReviewComment.appended', function () {
+ diffnav.update_on_new_comment();
+ });
+ Y.lp.code.branchmergeproposal.status.connect_status(conf);
+ }
+ Y.lp.code.branchmergeproposal.reviewcomment.connect_links();
+ diffnav.render();
+ }
if (Y.Lang.isValue(LP.cache.merge_proposal_event_key)) {
var upt = Y.lp.code.branchmergeproposal.updater;
@@ -247,6 +254,15 @@
diffnav.render();
});
}
+
+ LP.cache.comment_context = LP.cache.context;
+ var cl = new Y.lp.app.comment.CommentList({
+ comment_list_container: Y.one('#conversation')
+ });
+ if (comment !== null) {
+ comment.on('CodeReviewComment.appended', cl.bind_new_comment);
+ }
+ cl.render();
}, window);
Y.on('domready', function() {
=== modified file 'lib/lp/registry/browser/distroseriesdifference.py'
--- lib/lp/registry/browser/distroseriesdifference.py 2015-07-09 12:18:51 +0000
+++ lib/lp/registry/browser/distroseriesdifference.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Browser views for DistroSeriesDifferences."""
@@ -255,6 +255,8 @@
download_url = None
+ visible = True
+
def __init__(self, comment):
"""Setup the attributes required by `IComment`."""
super(DistroSeriesDifferenceDisplayComment, self).__init__(None)
=== modified file 'lib/lp/services/comments/browser/messagecomment.py'
--- lib/lp/services/comments/browser/messagecomment.py 2012-01-27 18:56:36 +0000
+++ lib/lp/services/comments/browser/messagecomment.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -22,6 +22,15 @@
self.comment_limit = comment_limit
@property
+ def extra_css_class(self):
+ if not self.visible:
+ # If a comment that isn't visible is being rendered, it's being
+ # rendered for an admin or registry_expert.
+ return 'adminHiddenComment'
+ else:
+ return ''
+
+ @property
def display_attachments(self):
return []
@@ -64,3 +73,5 @@
# less than 3 (which can happen in a testcase) and it makes
# counting the strings harder.
return "%s..." % self.body_text[:self.comment_limit]
+
+ show_spam_controls = False
=== modified file 'lib/lp/services/comments/interfaces/conversation.py'
--- lib/lp/services/comments/interfaces/conversation.py 2012-02-01 15:26:32 +0000
+++ lib/lp/services/comments/interfaces/conversation.py 2018-04-25 12:03:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interfaces to do with conversations on Launchpad entities."""
@@ -73,6 +73,11 @@
description=_("Should attachments be displayed for this comment."),
readonly=True)
+ visible = Bool(title=_("Whether this comment is visible."))
+
+ show_spam_controls = Bool(
+ title=_("Whether to show spam controls for this comment."))
+
class IConversation(Interface):
"""A conversation has a number of comments."""
=== modified file 'lib/lp/services/comments/templates/comment.pt'
--- lib/lp/services/comments/templates/comment.pt 2014-05-29 07:27:25 +0000
+++ lib/lp/services/comments/templates/comment.pt 2018-04-25 12:03:26 +0000
@@ -25,6 +25,12 @@
<div class="boardCommentFooter"
tal:define="link context/menu:context/reply | nothing"
tal:condition="link/enabled | nothing">
+ <a tal:condition="context/show_spam_controls"
+ tal:attributes="id string:mark-spam-${context/index};"
+ class="js-action mark-spam" href="#">
+ <tal:not-spam condition="not: context/visible">Unhide</tal:not-spam>
+ <tal:spam condition="context/visible">Hide</tal:spam>
+ </a>
<a itemprop="replyToUrl"
tal:attributes="href link/fmt:url"
tal:content="link/escapedtext">
Follow ups