From 9cc8d68ceeed7fe14bfcd84914f4bd54775036af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Bedna=C5=99=C3=ADk?= <jan.bednarik@gmail.com>
Date: Sat, 10 Nov 2018 22:22:35 +0100
Subject: [PATCH] Sort reports

---
 openlobby/core/api/schema.py                  | 19 ++++++++--
 openlobby/core/models.py                      |  6 ++++
 openlobby/core/search.py                      | 18 ++++++++--
 tests/dummy.py                                |  2 +-
 tests/schema/snapshots/snap_test_authors.py   |  2 +-
 .../snapshots/snap_test_search_reports.py     |  4 +--
 tests/schema/test_search_reports.py           | 35 +++++++++++++++++++
 tests/test_search.py                          | 33 +++++++++++++++--
 8 files changed, 108 insertions(+), 11 deletions(-)

diff --git a/openlobby/core/api/schema.py b/openlobby/core/api/schema.py
index 4864d20..8fa85f8 100644
--- a/openlobby/core/api/schema.py
+++ b/openlobby/core/api/schema.py
@@ -6,10 +6,11 @@ from .paginator import Paginator
 from .sanitizers import extract_text
 from .. import search
 from ..models import OpenIdClient
-from ..models import User, Report, UserSort
+from ..models import User, Report, UserSort, ReportSort
 
 
 UserSortEnum = graphene.Enum.from_enum(UserSort)
+ReportSortEnum = graphene.Enum.from_enum(ReportSort)
 
 
 class AuthorsConnection(relay.Connection):
@@ -50,6 +51,10 @@ class Query:
         description="Fulltext search in Reports. Returns first 10 nodes if pagination is not specified.",
         query=graphene.String(description="Text to search for."),
         highlight=graphene.Boolean(default_value=False, description=highlight_help),
+        sort=ReportSortEnum(),
+        reversed=graphene.Boolean(
+            default_value=False, description="Reverse order of sort."
+        ),
     )
     viewer = graphene.Field(types.User, description="Active user viewing API.")
     login_shortcuts = graphene.List(
@@ -84,10 +89,20 @@ class Query:
 
     def resolve_search_reports(self, info, **kwargs):
         paginator = Paginator(**kwargs)
+
         query = kwargs.get("query", "")
         query = extract_text(query)
-        params = {"highlight": kwargs.get("highlight")}
+
+        params = {
+            "highlight": kwargs.get("highlight"),
+            "reversed": kwargs.get("reversed"),
+        }
+
+        if "sort" in kwargs:
+            params["sort"] = ReportSort(kwargs["sort"])
+
         response = search.search_reports(paginator, query=query, **params)
+
         total = response.hits.total
         page_info = paginator.get_page_info(total)
 
diff --git a/openlobby/core/models.py b/openlobby/core/models.py
index a7c9aed..41e365a 100644
--- a/openlobby/core/models.py
+++ b/openlobby/core/models.py
@@ -15,6 +15,12 @@ class UserSort(Enum):
     TOTAL_REPORTS = "total_reports"
 
 
+class ReportSort(Enum):
+    DATE = "date"
+    PUBLISHED = "published"
+    RELEVANCE = "relevance"
+
+
 class CustomUserManager(UserManager):
     def with_total_reports(self):
         return self.get_queryset().annotate(
diff --git a/openlobby/core/search.py b/openlobby/core/search.py
index b9b2df0..84fcf16 100644
--- a/openlobby/core/search.py
+++ b/openlobby/core/search.py
@@ -1,4 +1,5 @@
 from .documents import ReportDoc
+from .models import ReportSort
 
 
 HIGHLIGHT_PARAMS = {
@@ -8,7 +9,15 @@ HIGHLIGHT_PARAMS = {
 }
 
 
-def search_reports(paginator, *, query=None, highlight=False, author_id=None):
+def search_reports(
+    paginator,
+    *,
+    query=None,
+    highlight=False,
+    author_id=None,
+    sort=ReportSort.PUBLISHED,
+    reversed=False,
+):
     fields = [
         "title",
         "body",
@@ -31,7 +40,12 @@ def search_reports(paginator, *, query=None, highlight=False, author_id=None):
     if highlight:
         s = s.highlight(*fields, **HIGHLIGHT_PARAMS)
 
-    s = s.sort("-published")
+    if sort == ReportSort.PUBLISHED:
+        s = s.sort("published" if reversed else "-published")
+    elif sort == ReportSort.DATE:
+        s = s.sort("date" if reversed else "-date")
+    elif sort == ReportSort.RELEVANCE:
+        s = s.sort({"_score": {"order": "asc" if reversed else "desc"}}, "-published")
 
     s = s[paginator.slice_from : paginator.slice_to]
     return s.execute()
diff --git a/tests/dummy.py b/tests/dummy.py
index fc2e23a..27c05e6 100644
--- a/tests/dummy.py
+++ b/tests/dummy.py
@@ -56,7 +56,7 @@ reports = [
     },
     {
         "id": 3,
-        "date": arrow.get(2018, 1, 5).datetime,
+        "date": arrow.get(2018, 1, 2).datetime,
         "published": arrow.get(2018, 1, 6).datetime,
         "edited": arrow.get(2018, 1, 6, 7).datetime,
         "title": "The Return of the King",
diff --git a/tests/schema/snapshots/snap_test_authors.py b/tests/schema/snapshots/snap_test_authors.py
index 83db2ae..a8d2ba1 100644
--- a/tests/schema/snapshots/snap_test_authors.py
+++ b/tests/schema/snapshots/snap_test_authors.py
@@ -232,7 +232,7 @@ snapshots['test_with_reports 1'] = {
                                     'cursor': 'MQ==',
                                     'node': {
                                         'body': 'Aragorn is the King. And we have lost the Ring.',
-                                        'date': '2018-01-05 00:00:00+00:00',
+                                        'date': '2018-01-02 00:00:00+00:00',
                                         'edited': '2018-01-06 07:00:00+00:00',
                                         'extra': None,
                                         'id': 'UmVwb3J0OjM=',
diff --git a/tests/schema/snapshots/snap_test_search_reports.py b/tests/schema/snapshots/snap_test_search_reports.py
index 958d01e..8717665 100644
--- a/tests/schema/snapshots/snap_test_search_reports.py
+++ b/tests/schema/snapshots/snap_test_search_reports.py
@@ -23,7 +23,7 @@ snapshots['test_all 1'] = {
                             'totalReports': 2
                         },
                         'body': 'Aragorn is the King. And we have lost the Ring.',
-                        'date': '2018-01-05 00:00:00+00:00',
+                        'date': '2018-01-02 00:00:00+00:00',
                         'edited': '2018-01-06 07:00:00+00:00',
                         'extra': None,
                         'hasRevisions': False,
@@ -153,7 +153,7 @@ snapshots['test_highlight 1'] = {
                             'totalReports': 2
                         },
                         'body': 'Aragorn is the King. And we have lost the <mark>Ring</mark>.',
-                        'date': '2018-01-05 00:00:00+00:00',
+                        'date': '2018-01-02 00:00:00+00:00',
                         'edited': '2018-01-06 07:00:00+00:00',
                         'extra': None,
                         'hasRevisions': False,
diff --git a/tests/schema/test_search_reports.py b/tests/schema/test_search_reports.py
index e803019..2717c6b 100644
--- a/tests/schema/test_search_reports.py
+++ b/tests/schema/test_search_reports.py
@@ -1,4 +1,5 @@
 import pytest
+from graphql_relay import from_global_id
 
 from ..dummy import prepare_reports
 
@@ -231,3 +232,37 @@ def test_last_before(call_api, snapshot):
     """
     response = call_api(query)
     snapshot.assert_match(response)
+
+
+@pytest.mark.parametrize(
+    "params, expected_ids",
+    [
+        ("sort: PUBLISHED", [3, 2, 1]),
+        ("sort: PUBLISHED, reversed: true", [1, 2, 3]),
+        ("sort: DATE", [2, 3, 1]),
+        ("sort: DATE, reversed: true", [1, 3, 2]),
+        ("sort: RELEVANCE", [3, 2, 1]),
+        ("sort: RELEVANCE, reversed: true", [3, 2, 1]),
+        ('query: "ring", sort: RELEVANCE', [1, 3]),
+        ('query: "ring", sort: RELEVANCE, reversed: true', [3, 1]),
+    ],
+)
+def test_sort(params, expected_ids, call_api, snapshot):
+    prepare_reports()
+    query = f"""
+    query {{
+        searchReports ({params}) {{
+            totalCount
+            edges {{
+                cursor
+                node {{
+                    id
+                }}
+            }}
+        }}
+    }}
+    """
+    response = call_api(query)
+    ids = [edge["node"]["id"] for edge in response["data"]["searchReports"]["edges"]]
+    ids = [int(id) for type, id in map(from_global_id, ids)]
+    assert ids == expected_ids
diff --git a/tests/test_search.py b/tests/test_search.py
index aadd37d..c1efcd7 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -1,6 +1,7 @@
 import pytest
 
 from openlobby.core.api.paginator import Paginator, encode_cursor
+from openlobby.core.models import ReportSort
 from openlobby.core.search import search_reports
 
 from .dummy import prepare_reports
@@ -13,7 +14,7 @@ pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures("django_es")]
     "query, expected_ids",
     [("", [3, 2, 1]), ("sauron", [3, 2]), ("towers", [2]), ("Aragorn Gandalf", [3, 1])],
 )
-def test_search_reports(query, expected_ids):
+def test_search_reports__query(query, expected_ids):
     prepare_reports()
     paginator = Paginator()
     response = search_reports(paginator, query=query)
@@ -35,9 +36,8 @@ def test_search_reports__highlight():
 )
 def test_search_reports__pagination(first, after, expected_ids):
     prepare_reports()
-    query = ""
     paginator = Paginator(first=first, after=after)
-    response = search_reports(paginator, query=query)
+    response = search_reports(paginator)
     assert expected_ids == [int(r.meta.id) for r in response]
 
 
@@ -58,3 +58,30 @@ def test_search_reports__by_author__pagination(first, after, expected_ids):
     paginator = Paginator(first=first, after=after)
     response = search_reports(paginator, author_id=author_id)
     assert expected_ids == [int(r.meta.id) for r in response]
+
+
+def test_search_reports__sort__default():
+    prepare_reports()
+    paginator = Paginator()
+    response = search_reports(paginator)
+    assert [3, 2, 1] == [int(r.meta.id) for r in response]
+
+
+@pytest.mark.parametrize(
+    "query, sort, reversed, expected_ids",
+    [
+        (None, ReportSort.PUBLISHED, False, [3, 2, 1]),
+        (None, ReportSort.PUBLISHED, True, [1, 2, 3]),
+        (None, ReportSort.DATE, False, [2, 3, 1]),
+        (None, ReportSort.DATE, True, [1, 3, 2]),
+        (None, ReportSort.RELEVANCE, False, [3, 2, 1]),
+        (None, ReportSort.RELEVANCE, True, [3, 2, 1]),
+        ("ring", ReportSort.RELEVANCE, False, [1, 3]),
+        ("ring", ReportSort.RELEVANCE, True, [3, 1]),
+    ],
+)
+def test_search_reports__sort(query, sort, reversed, expected_ids):
+    prepare_reports()
+    paginator = Paginator()
+    response = search_reports(paginator, query=query, sort=sort, reversed=reversed)
+    assert expected_ids == [int(r.meta.id) for r in response]
-- 
GitLab