Commit 7fcd875e by Mushtaq Ali

Add support to search by multiple usage ids in /search end point - TNL-5604

parent eae09382
......@@ -2,3 +2,4 @@ Oleg Marshev <oleg@edx.org>
Tim Babych <tim.babych@gmail.com>
Christina Roberts <christina@edx.org>
Ben McMorran <ben.mcmorran@gmail.com>
Mushtaq Ali <mushtaak@gmail.com>
......@@ -680,6 +680,32 @@ class AnnotationSearchViewTests(BaseAnnotationViewTests):
search_and_verify("First", "First one", [])
search_and_verify("Second", "Second note", ["tag1", "tag2"])
@ddt.data(True, False)
def test_usage_id_search(self, is_es_disabled):
"""
Verifies the search with usage id.
"""
self._create_annotation(text=u'First one', usage_id='test-1')
self._create_annotation(text=u'Second note', usage_id='test-2')
self._create_annotation(text=u'Third note', usage_id='test-3')
@patch('django.conf.settings.ES_DISABLED', is_es_disabled)
def verify_usage_id_search(usage_ids):
"""
Verify search results based on usage id operation.
Arguments:
usage_ids: List. The identifier string of the annotations XBlock(s).
"""
results = self._get_search_results(usage_id=usage_ids)
self.assertEqual(len(results), len(usage_ids))
# Here we are reverse-traversing usage_ids because response has descending ordered rows.
for index, u_id in enumerate(usage_ids[::-1]):
self.assertEqual(results[index]['usage_id'], u_id)
verify_usage_id_search(usage_ids=['test-1'])
verify_usage_id_search(usage_ids=['test-1', 'test-2', 'test-3'])
def test_search_deleted(self):
"""
Tests for search method to not return deleted notes.
......
......@@ -35,10 +35,12 @@ class AnnotationSearchView(GenericAPIView):
"""
**Use Case**
* Search and return a paginated list of annotations for a user.
* Search and return a list of annotations for a user.
The annotations are always sorted in descending order by updated date.
Response is paginated by default except usage_id based search.
Each page in the list contains 25 annotations by default. The page
size can be altered by passing parameter "page_size=<page_size>".
......@@ -58,6 +60,8 @@ class AnnotationSearchView(GenericAPIView):
GET /api/v1/search/
GET /api/v1/search/?course_id={course_id}&user={user_id}
GET /api/v1/search/?course_id={course_id}&user={user_id}&usage_id={usage_id}
GET /api/v1/search/?course_id={course_id}&user={user_id}&usage_id={usage_id}&usage_id={usage_id} ...
**Query Parameters for GET**
......@@ -67,6 +71,8 @@ class AnnotationSearchView(GenericAPIView):
* user: Anonymized user id.
* usage_id: The identifier string of the annotations XBlock.
* text: Student's thoughts on the quote
* highlight: dict. Only used when search from ElasticSearch. It contains two keys:
......@@ -109,31 +115,49 @@ class AnnotationSearchView(GenericAPIView):
* updated: DateTime. When was the last time annotation was updated.
"""
params = {}
query_params = {}
search_with_usage_id = False
def get(self, *args, **kwargs): # pylint: disable=unused-argument
"""
Search annotations in most appropriate storage
"""
self.query_params = {}
self.search_with_usage_id = False
self.params = self.request.query_params.dict()
usage_ids = self.request.query_params.getlist('usage_id')
if len(usage_ids) > 0:
self.search_with_usage_id = True
self.query_params['usage_id__in'] = usage_ids
if 'course_id' in self.params:
self.query_params['course_id'] = self.params['course_id']
# search in DB when ES is not available or there is no need to bother it
if settings.ES_DISABLED or 'text' not in self.request.query_params.dict():
if settings.ES_DISABLED or 'text' not in self.params:
if 'user' in self.params:
self.query_params['user_id'] = self.params['user']
return self.get_from_db(*args, **kwargs)
else:
if 'user' in self.params:
self.query_params['user'] = self.params['user']
return self.get_from_es(*args, **kwargs)
def get_from_db(self, *args, **kwargs): # pylint: disable=unused-argument
"""
Search annotations in database.
"""
params = self.request.query_params.dict()
query = Note.objects.filter(
**{f: v for (f, v) in params.items() if f in ('course_id', 'usage_id')}
).order_by('-updated')
query = Note.objects.filter(**self.query_params).order_by('-updated')
if 'user' in params:
query = query.filter(user_id=params['user'])
if 'text' in self.params:
query = query.filter(Q(text__icontains=self.params['text']) | Q(tags__icontains=self.params['text']))
if 'text' in params:
query = query.filter(Q(text__icontains=params['text']) | Q(tags__icontains=params['text']))
# Do not send paginated result if usage id based search.
if self.search_with_usage_id:
serializer = NoteSerializer(query, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
page = self.paginate_queryset(query)
serializer = NoteSerializer(page, many=True)
......@@ -144,16 +168,13 @@ class AnnotationSearchView(GenericAPIView):
"""
Search annotations in ElasticSearch.
"""
params = self.request.query_params.dict()
query = SearchQuerySet().models(Note).filter(
**{f: v for (f, v) in params.items() if f in ('user', 'course_id', 'usage_id')}
)
query = SearchQuerySet().models(Note).filter(**self.query_params)
if 'text' in params:
clean_text = query.query.clean(params['text'])
if 'text' in self.params:
clean_text = query.query.clean(self.params['text'])
query = query.filter(SQ(data=clean_text))
if params.get('highlight'):
if self.params.get('highlight'):
opts = {
'pre_tags': ['{elasticsearch_highlight_start}'],
'post_tags': ['{elasticsearch_highlight_end}'],
......@@ -161,6 +182,11 @@ class AnnotationSearchView(GenericAPIView):
}
query = query.highlight(**opts)
# Do not send paginated result if usage id based search.
if self.search_with_usage_id:
serializer = NotesElasticSearchSerializer(query, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
page = self.paginate_queryset(query)
serializer = NotesElasticSearchSerializer(page, many=True)
response = self.get_paginated_response(serializer.data)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment