support pagination in TestAPI 31/36231/2
authorSerenaFeng <feng.xiaowei@zte.com.cn>
Tue, 20 Jun 2017 09:40:38 +0000 (17:40 +0800)
committerSerenaFeng <feng.xiaowei@zte.com.cn>
Tue, 20 Jun 2017 09:45:42 +0000 (17:45 +0800)
In this patch, pagination is supported, so you can go through results
leveraging: http://testresults.opnfv.org/test/api/v1/results?page=2

Change-Id: Ibe31c787643f27dbb06c4899e713b3c8e716e784
Signed-off-by: SerenaFeng <feng.xiaowei@zte.com.cn>
utils/test/testapi/3rd_party/static/testapi-ui/components/results/resultsController.js
utils/test/testapi/etc/config.ini
utils/test/testapi/opnfv_testapi/common/config.py
utils/test/testapi/opnfv_testapi/resources/handlers.py
utils/test/testapi/opnfv_testapi/resources/result_handlers.py
utils/test/testapi/opnfv_testapi/tests/unit/fake_pymongo.py

index 93a549a..9e3540d 100644 (file)
             ctrl.resultsRequest =
                 $http.get(content_url).success(function (data) {
                     ctrl.data = data;
-                    ctrl.totalItems = 20 // ctrl.data.pagination.total_pages * ctrl.itemsPerPage;
-                    ctrl.currentPage = 1 // ctrl.data.pagination.current_page;
+                    ctrl.totalItems = ctrl.data.pagination.total_pages * ctrl.itemsPerPage;
+                    ctrl.currentPage = ctrl.data.pagination.current_page;
                 }).error(function (error) {
                     ctrl.data = null;
                     ctrl.totalItems = 0;
index 692e488..cf46bf7 100644 (file)
@@ -10,6 +10,10 @@ dbname = test_results_collection
 # Listening port
 url = http://localhost:8000/api/v1
 port = 8000
+
+# Number of results for one page (integer value)
+#results_per_page = 20
+
 # With debug_on set to true, error traces will be shown in HTTP responses
 debug = True
 authenticate = False
index 46765ff..f73c0ab 100644 (file)
@@ -17,6 +17,7 @@ class Config(object):
     def __init__(self):
         self.file = self.CONFIG if self.CONFIG else self._default_config()
         self._parse()
+        self._parse_per_page()
         self.static_path = os.path.join(
             os.path.dirname(os.path.normpath(__file__)),
             os.pardir,
@@ -37,6 +38,10 @@ class Config(object):
         [setattr(self, '{}_{}'.format(section, k), self._parse_value(v))
          for k, v in config.items(section)]
 
+    def _parse_per_page(self):
+        if not hasattr(self, 'api_results_per_page'):
+            self.api_results_per_page = 20
+
     @staticmethod
     def _parse_value(value):
         try:
index 2fc31ca..42372e8 100644 (file)
@@ -104,17 +104,35 @@ class GenericApiHandler(web.RequestHandler):
         if query is None:
             query = {}
         data = []
+        sort = kwargs.get('sort')
+        page = kwargs.get('page')
+        last = kwargs.get('last')
+        per_page = kwargs.get('per_page')
+
         cursor = self._eval_db(self.table, 'find', query)
-        if 'sort' in kwargs:
-            cursor = cursor.sort(kwargs.get('sort'))
-        if 'last' in kwargs:
-            cursor = cursor.limit(kwargs.get('last'))
+        if sort:
+            cursor = cursor.sort(sort)
+        if last and last != 0:
+            cursor = cursor.limit(last)
+        if page:
+            records_count = yield cursor.count()
+            total_pages, remainder = divmod(records_count, per_page)
+            if remainder > 0:
+                total_pages += 1
+            cursor = cursor.skip((page - 1) * per_page).limit(per_page)
         while (yield cursor.fetch_next):
             data.append(self.format_data(cursor.next_object()))
         if res_op is None:
             res = {self.table: data}
         else:
             res = res_op(data, *args)
+        if page:
+            res.update({
+                'pagination': {
+                    'current_page': page,
+                    'total_pages': total_pages
+                }
+            })
         self.finish_request(res)
 
     @web.asynchronous
index 824a89e..208af6d 100644 (file)
@@ -11,12 +11,15 @@ from datetime import timedelta
 
 from bson import objectid
 
+from opnfv_testapi.common import config
 from opnfv_testapi.common import message
 from opnfv_testapi.common import raises
 from opnfv_testapi.resources import handlers
 from opnfv_testapi.resources import result_models
 from opnfv_testapi.tornado_swagger import swagger
 
+CONF = config.Config()
+
 
 class GenericResultHandler(handlers.GenericApiHandler):
     def __init__(self, application, request, **kwargs):
@@ -135,22 +138,28 @@ class ResultsCLHandler(GenericResultHandler):
             @type last: L{string}
             @in last: query
             @required last: False
+            @param page: which page to list
+            @type page: L{int}
+            @in page: query
+            @required page: False
             @param trust_indicator: must be float
             @type trust_indicator: L{float}
             @in trust_indicator: query
             @required trust_indicator: False
         """
+        limitations = {'sort': [('start_date', -1)]}
         last = self.get_query_argument('last', 0)
         if last is not None:
             last = self.get_int('last', last)
+            limitations.update({'last': last})
 
-        page = self.get_query_argument('page', 0)
-        if page:
-            last = 20
+        page = self.get_query_argument('page', 1)
+        if page is not None:
+            page = self.get_int('page', page)
+            limitations.update({'page': page,
+                                'per_page': CONF.api_results_per_page})
 
-        self._list(query=self.set_query(),
-                   sort=[('start_date', -1)],
-                   last=last)
+        self._list(query=self.set_query(), **limitations)
 
     @swagger.operation(nickname="createTestResult")
     def post(self):
index ef74a08..b2564a6 100644 (file)
@@ -20,18 +20,18 @@ def thread_execute(method, *args, **kwargs):
 class MemCursor(object):
     def __init__(self, collection):
         self.collection = collection
-        self.count = len(self.collection)
+        self.length = len(self.collection)
         self.sorted = []
 
     def _is_next_exist(self):
-        return self.count != 0
+        return self.length != 0
 
     @property
     def fetch_next(self):
         return thread_execute(self._is_next_exist)
 
     def next_object(self):
-        self.count -= 1
+        self.length -= 1
         return self.collection.pop()
 
     def sort(self, key_or_list):
@@ -48,10 +48,25 @@ class MemCursor(object):
 
     def limit(self, limit):
         if limit != 0 and limit < len(self.collection):
-            self.collection = self.collection[0:limit]
-            self.count = limit
+            self.collection = self.collection[0: limit]
+            self.length = limit
         return self
 
+    def skip(self, skip):
+        if skip < self.length and (skip > 0):
+            self.collection = self.collection[self.length - skip: -1]
+            self.length -= skip
+        elif skip >= self.length:
+            self.collection = []
+            self.length = 0
+        return self
+
+    def _count(self):
+        return self.length
+
+    def count(self):
+        return thread_execute(self._count)
+
 
 class MemDb(object):