Repository: AlexsLemonade/refinebio

Issue:
Return number of samples in the search filters
### Context

The API returns all the available filters for a given search query, the numbers next to each category.

![image](https://user-images.githubusercontent.com/1882507/52960613-2eb87780-3367-11e9-9a85-af497b5d95dd.png)

### Problem or idea

With https://github.com/AlexsLemonade/refinebio/pull/974, when we started using Elastic Search, the filters started returning the number of results matching each filter instead of the total number of samples.

### Solution or next step

Not sure how to tackle this one, tagging @Miserlou.

With some help I could try fixing this.

### New Issue Checklist

- [x] The title is short and descriptive.
- [x] You have explained the context that led you to write this issue.
- [x] You have reported a problem or idea.
- [x] You have proposed a solution or next step.



Discussion:

Resolving patch:
diff --git a/api/data_refinery_api/views.py b/api/data_refinery_api/views.py
--- a/api/data_refinery_api/views.py
+++ b/api/data_refinery_api/views.py
@@ -149,6 +149,26 @@ def get_paginated_response(self, data):
 # ElasticSearch
 ##
 from django_elasticsearch_dsl_drf.pagination import LimitOffsetPagination as ESLimitOffsetPagination
+from six import iteritems
+
+class FacetedSearchFilterBackendExtended(FacetedSearchFilterBackend):
+    def aggregate(self, request, queryset, view):
+        """Extends FacetedSearchFilterBackend to add sampple counts on each filter
+        https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/blob/master/src/django_elasticsearch_dsl_drf/filter_backends/faceted_search.py#L19
+
+        All we need to add is one line when building the facets:
+
+        .metric('total_samples', 'sum', field='num_processed_samples')
+
+        (Maybe there's a way to do this with the options in `ExperimentDocumentView`)
+        """
+        facets = self.construct_facets(request, view)
+        for field, facet in iteritems(facets):
+            agg = facet['facet'].get_aggregation()
+            queryset.aggs.bucket(field, agg)\
+                .metric('total_samples', 'sum', field='num_processed_samples')
+        return queryset
+
 
 class ExperimentDocumentView(DocumentViewSet):
     """ElasticSearch powered experiment search.
@@ -175,7 +195,7 @@ class ExperimentDocumentView(DocumentViewSet):
         OrderingFilterBackend,
         DefaultOrderingFilterBackend,
         CompoundSearchFilterBackend,
-        FacetedSearchFilterBackend
+        FacetedSearchFilterBackendExtended
     ]
 
     # Primitive
@@ -301,6 +321,32 @@ class ExperimentDocumentView(DocumentViewSet):
     }
     faceted_search_param = 'facet'
 
+    def list(self, request, *args, **kwargs):
+        """ Adds counts on certain filter fields to result JSON."""
+        response = super(ExperimentDocumentView, self).list(request, args, kwargs)
+        response.data['facets'] = self.transform_es_facets(response.data['facets'])
+        return response
+
+    def transform_es_facets(self, facets):
+        """Transforms Elastic Search facets into a set of objects where each one corresponds 
+        to a filter group. Example:
+
+        { technology: {rna-seq: 254, microarray: 8846, unknown: 0} }
+
+        Which means the users could attach `?technology=rna-seq` to the url and expect 254 
+        samples returned in the results.
+        """
+        result = {}
+        for field, facet in iteritems(facets):
+            filter_group = {}
+            for bucket in facet['buckets']:
+                if field == 'has_publication':
+                    filter_group[bucket['key_as_string']] = bucket['total_samples']['value']
+                else:
+                    filter_group[bucket['key']] = bucket['total_samples']['value']
+            result[field] = filter_group
+        return result
+
 ##
 # Search and Filter
 ##


Test patch:
diff --git a/api/data_refinery_api/tests.py b/api/data_refinery_api/tests.py
--- a/api/data_refinery_api/tests.py
+++ b/api/data_refinery_api/tests.py
@@ -1152,6 +1152,8 @@ def test_es_endpoint(self):
         experiment.title = "Hey Ho Let's Go"
         experiment.description = "This is a very exciting test experiment. Faygo soda. Blah blah blah."
         experiment.technology = "MICROARRAY"
+        experiment.num_processed_samples = 1 # added below
+        experiment.num_total_samples = 1
         experiment.save()
         self.experiment = experiment
 
@@ -1168,6 +1170,8 @@ def test_es_endpoint(self):
         sample = Sample()
         sample.title = "789"
         sample.accession_code = "789"
+        sample.is_processed = True
+        sample.organism = Organism.get_object_for_name("AILUROPODA_MELANOLEUCA")
         sample.save()
         self.sample = sample
 
@@ -1232,6 +1236,10 @@ def test_es_endpoint(self):
         # Sanity
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.json()['count'], 2)
+        # test sample counts in filters
+        self.assertEqual(response.json()['facets']['has_publication']['false'], 1)
+        self.assertEqual(response.json()['facets']['technology']['microarray'], 1)
+        self.assertEqual(response.json()['facets']['technology']['rna-seq'], 0)
 
         # Basic Search
         response = self.client.get('/es/?search=soda')