Skip to content

Commit

Permalink
Merge pull request #5289 from hypothesis/nipsas
Browse files Browse the repository at this point in the history
Add a field called hidden to an annotation document at the time of indexing
  • Loading branch information
sheetaluk committed Sep 20, 2018
2 parents 0858a77 + a83e95c commit 91997c5
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 15 deletions.
14 changes: 13 additions & 1 deletion h/presenters/annotation_searchindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
class AnnotationSearchIndexPresenter(AnnotationBasePresenter):

"""Present an annotation in the JSON format used in the search index."""
def __init__(self, annotation):
def __init__(self, annotation, request):
self.annotation = annotation
self.request = request

def asdict(self):
docpresenter = DocumentSearchIndexPresenter(self.annotation.document)
Expand Down Expand Up @@ -40,6 +41,17 @@ def asdict(self):
if self.annotation.references:
result['references'] = self.annotation.references

# Mark an annotation as hidden if it and all of it's children have been
# moderated and hidden.
result['hidden'] = False
if self.annotation.thread_ids:
ann_mod_svc = self.request.find_service(name='annotation_moderation')
annotation_hidden = ann_mod_svc.hidden(self.annotation)
thread_ids_hidden = len(ann_mod_svc.all_hidden(self.annotation.thread_ids)) == len(self.annotation.thread_ids)

if annotation_hidden and thread_ids_hidden:
result['hidden'] = True

return result

@property
Expand Down
1 change: 1 addition & 0 deletions h/search/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'quote': {'type': 'text', 'analyzer': 'uni_normalizer'},
'references': {'type': 'keyword'},
'shared': {'type': 'boolean'},
'hidden': {'type': 'boolean'},
'tags': {'type': 'text', 'analyzer': 'uni_normalizer'},
'tags_raw': {'type': 'keyword'},
'text': {'type': 'text', 'analyzer': 'uni_normalizer'},
Expand Down
4 changes: 2 additions & 2 deletions h/search/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def index(es, annotation, request, target_index=None):
:param target_index: the index name, uses default index if not given
:type target_index: unicode
"""
presenter = presenters.AnnotationSearchIndexPresenter(annotation)
presenter = presenters.AnnotationSearchIndexPresenter(annotation, request)
annotation_dict = presenter.asdict()

event = AnnotationTransformEvent(request, annotation, annotation_dict)
Expand Down Expand Up @@ -155,7 +155,7 @@ def _prepare(self, annotation):
action = {self.op_type: {'_index': self._target_index,
'_type': self.es_client.mapping_type,
'_id': annotation.id}}
data = presenters.AnnotationSearchIndexPresenter(annotation).asdict()
data = presenters.AnnotationSearchIndexPresenter(annotation, self.request).asdict()

event = AnnotationTransformEvent(self.request, annotation, data)
self.request.registry.notify(event)
Expand Down
99 changes: 90 additions & 9 deletions tests/h/presenters/annotation_searchindex_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import pytest

from h.presenters.annotation_searchindex import AnnotationSearchIndexPresenter
from h.services.annotation_moderation import AnnotationModerationService


@pytest.mark.usefixtures('DocumentSearchIndexPresenter')
@pytest.mark.usefixtures('DocumentSearchIndexPresenter', 'moderation_service', 'thread_ids')
class TestAnnotationSearchIndexPresenter(object):

def test_asdict(self, DocumentSearchIndexPresenter):
annotation = mock.Mock(
def test_asdict(self, DocumentSearchIndexPresenter, pyramid_request, thread_ids):

annotation = mock.MagicMock(
id='xyz123',
created=datetime.datetime(2016, 2, 24, 18, 3, 25, 768),
updated=datetime.datetime(2016, 2, 29, 10, 24, 5, 564),
Expand All @@ -27,11 +29,11 @@ def test_asdict(self, DocumentSearchIndexPresenter):
shared=True,
target_selectors=[{'TestSelector': 'foobar'}],
references=['referenced-id-1', 'referenced-id-2'],
thread_ids=['thread-id-1', 'thread-id-2'],
thread_ids=thread_ids,
extra={'extra-1': 'foo', 'extra-2': 'bar'})
DocumentSearchIndexPresenter.return_value.asdict.return_value = {'foo': 'bar'}

annotation_dict = AnnotationSearchIndexPresenter(annotation).asdict()
annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict == {
'authority': 'hypothes.is',
Expand All @@ -51,22 +53,101 @@ def test_asdict(self, DocumentSearchIndexPresenter):
'selector': [{'TestSelector': 'foobar'}]}],
'document': {'foo': 'bar'},
'references': ['referenced-id-1', 'referenced-id-2'],
'thread_ids': ['thread-id-1', 'thread-id-2'],
'thread_ids': thread_ids,
'hidden': False,
}

def test_it_copies_target_uri_normalized_to_target_scope(self):
annotation = mock.Mock(
def test_it_copies_target_uri_normalized_to_target_scope(self, pyramid_request):
annotation = mock.MagicMock(
userid='acct:luke@hypothes.is',
target_uri_normalized='http://example.com/normalized',
extra={})

annotation_dict = AnnotationSearchIndexPresenter(annotation).asdict()
annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['target'][0]['scope'] == [
'http://example.com/normalized']

def test_it_marks_annotation_hidden_when_it_and_all_children_are_moderated(self,
pyramid_request,
moderation_service,
thread_ids):
annotation = mock.MagicMock(
userid='acct:luke@hypothes.is',
thread_ids=thread_ids)

moderation_service.hidden.return_value = True
moderation_service.all_hidden.return_value = thread_ids

annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['hidden'] is True

def test_it_does_not_mark_annotation_hidden_when_it_is_not_moderated(self,
pyramid_request,
moderation_service,
thread_ids):
annotation = mock.MagicMock(
userid='acct:luke@hypothes.is',
thread_ids=thread_ids)

moderation_service.all_hidden.return_value = thread_ids

annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['hidden'] is False

def test_it_does_not_mark_annotation_hidden_when_children_are_not_moderated(self,
pyramid_request,
moderation_service,
thread_ids):
annotation = mock.MagicMock(
userid='acct:luke@hypothes.is',
thread_ids=thread_ids)

moderation_service.all_hidden.return_value = thread_ids[1:]
moderation_service.hidden.return_value = True

annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['hidden'] is False

def test_it_does_not_mark_annotation_hidden_when_not_moderated_and_no_replies(self, pyramid_request):
thread_ids = []
annotation = mock.MagicMock(
userid='acct:luke@hypothes.is',
thread_ids=thread_ids)

annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['hidden'] is False

def test_it_marks_annotation_hidden_when_moderated_and_no_replies(self, pyramid_request, moderation_service):
annotation = mock.MagicMock(userid='acct:luke@hypothes.is')

moderation_service.hidden.return_value = True

annotation_dict = AnnotationSearchIndexPresenter(annotation, pyramid_request).asdict()

assert annotation_dict['hidden'] is True

@pytest.fixture
def DocumentSearchIndexPresenter(self, patch):
class_ = patch('h.presenters.annotation_searchindex.DocumentSearchIndexPresenter')
class_.return_value.asdict.return_value = {}
return class_


@pytest.fixture
def moderation_service(pyramid_config):
svc = mock.create_autospec(AnnotationModerationService, spec_set=True, instance=True)
svc.all_hidden.return_value = []
svc.hidden.return_value = False
pyramid_config.register_service(svc, name='annotation_moderation')
return svc


@pytest.fixture
def thread_ids():
# Annotation reply ids are referred to as thread_ids in our code base.
return ['thread-id-1', 'thread-id-2']
36 changes: 33 additions & 3 deletions tests/h/search/index_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import pytest

import h.search.index
from h.services.annotation_moderation import AnnotationModerationService

from tests.common.matchers import Matcher


@pytest.mark.usefixtures("annotations")
@pytest.mark.usefixtures("annotations", "moderation_service")
class TestIndex(object):
def test_annotation_ids_are_used_as_elasticsearch_ids(self, es_client,
factories,
Expand All @@ -28,7 +29,7 @@ def test_annotation_ids_are_used_as_elasticsearch_ids(self, es_client,
id=annotation.id)
assert result["_id"] == annotation.id

def test_it_indexes_presented_annotation(self, factories, get_indexed_ann, index,
def test_it_indexes_presented_annotation(self, factories, get_indexed_ann, index, pyramid_request,
AnnotationSearchIndexPresenter):
annotation = factories.Annotation.build()
presenter = AnnotationSearchIndexPresenter.return_value
Expand All @@ -38,7 +39,7 @@ def test_it_indexes_presented_annotation(self, factories, get_indexed_ann, index
index(annotation)
indexed_ann = get_indexed_ann(annotation.id)

AnnotationSearchIndexPresenter.assert_called_once_with(annotation)
AnnotationSearchIndexPresenter.assert_called_once_with(annotation, pyramid_request)
assert indexed_ann == presenter.asdict.return_value

def test_it_can_index_an_annotation_with_no_document(self, factories,
Expand Down Expand Up @@ -256,6 +257,26 @@ def test_you_can_filter_annotations_by_thread_ids(self, factories, index, search

assert SearchResponseWithIDs([annotation2.id]) == response

def test_you_can_filter_annotations_by_hidden(self, AnnotationSearchIndexPresenter, factories, index, search):
annotation1 = factories.Annotation.build()
annotation2 = factories.Annotation.build()

presenter = AnnotationSearchIndexPresenter.return_value
presenter.asdict.return_value = {'id': annotation1.id,
'hidden': True}

index(annotation1)

presenter = AnnotationSearchIndexPresenter.return_value
presenter.asdict.return_value = {'id': annotation2.id,
'hidden': False}

index(annotation2)

response = search.filter("term", hidden=True).execute()

assert SearchResponseWithIDs([annotation1.id]) == response

@pytest.mark.parametrize("quote,query", [
("It is a truth universally acknowledged", "truth"),
("यह एक सत्य सार्वभौमिक रूप से स्वीकार किया जाता है", "सत्य"),
Expand Down Expand Up @@ -474,3 +495,12 @@ def _get(annotation_id):
index=es_client.index, doc_type=es_client.mapping_type,
id=annotation_id)["_source"]
return _get


@pytest.fixture
def moderation_service(pyramid_config):
svc = mock.create_autospec(AnnotationModerationService, spec_set=True, instance=True)
svc.all_hidden.return_value = []
svc.hidden.return_value = False
pyramid_config.register_service(svc, name='annotation_moderation')
return svc

0 comments on commit 91997c5

Please sign in to comment.