add recommended as a sort, and default to recommended, downloads

This commit is contained in:
Andrew Williamson 2019-05-16 18:48:39 +01:00
Родитель 983a751f68
Коммит fa5ba91601
5 изменённых файлов: 96 добавлений и 13 удалений

Просмотреть файл

@ -79,13 +79,20 @@ This endpoint allows you to search through public add-ons.
passed and when filtering to only return featured or
recommended add-ons.
rating Bayesian rating, descending.
recommended Recommended add-ons above non-recommend add-ons. Only
available combined with another sort - ignored on its own.
relevance Search query relevance, descending.
updated Last updated date, descending.
users Average number of daily users, descending.
============== ==========================================================
The default is to sort by relevance if a search query (``q``) is present,
otherwise sort by number of weekly downloads, descending.
The new default behavior is to sort by relevance if a search query (``q``)
is present; otherwise place recommended add-ons first, then non recommended
add-ons, then sorted by number of weekly downloads, descending. (``sort=recommended,downloads``).
This is the default on AMO dev server.
The default on AMO production currently is to sort by relevance if a search
query (``q``) is present; otherwise sort by number of weekly downloads, descending.
You can combine multiple parameters by separating them with a comma.
For instance, to sort search results by downloads and then by creation

Просмотреть файл

@ -324,6 +324,8 @@ v4 API changelog
* 2019-05-09: added ``is_recommended`` to addons API. https://github.com/mozilla/addons-server/issues/11278
* 2019-05-16: added /reviewers/canned-responses/ endpoint. https://github.com/mozilla/addons-server/issues/11276
* 2019-05-23: added ``is_recommended`` to addons autocomplete API also. https://github.com/mozilla/addons-server/issues/11439
* 2019-05-23: changed the addons search API default sort when no query string is passed - now ``sort=recommended,downloads``.
Also made ``recommended`` sort available generally to the addons search API. https://github.com/mozilla/addons-server/issues/11432
----------------
v5 API changelog

Просмотреть файл

@ -13,6 +13,7 @@ from elasticsearch import Elasticsearch
from unittest.mock import patch
from rest_framework.test import APIRequestFactory
from waffle import switch_is_active
from waffle.testutils import override_switch
from olympia import amo
from olympia.addons.models import (
@ -1012,6 +1013,7 @@ class TestAddonSearchView(ESTestCase):
def perform_search(self, url, data=None, expected_status=200,
expected_queries=0, **headers):
switch_is_active('api-recommendations-priority') # just to cache it
with self.assertNumQueries(expected_queries):
response = self.client.get(url, data, **headers)
assert response.status_code == expected_status, response.content
@ -1834,6 +1836,43 @@ class TestAddonSearchView(ESTestCase):
# No results, but no 500 either.
assert data['count'] == 0
def test_with_recommended_addons(self):
addon1 = addon_factory(weekly_downloads=666)
addon2 = addon_factory(weekly_downloads=555)
addon3 = addon_factory(weekly_downloads=444)
addon4 = addon_factory(weekly_downloads=333)
addon5 = addon_factory(weekly_downloads=222)
self.refresh()
# Default case first - no recommended addons
data = self.perform_search(self.url) # No query.
ids = [result['id'] for result in data['results']]
assert ids == [addon1.id, addon2.id, addon3.id, addon4.id, addon5.id]
# Now made some of the add-ons recommended
DiscoveryItem.objects.create(addon=addon2, recommendable=True)
addon2.current_version.update(recommendation_approved=True)
# del addon2.is_recommended
DiscoveryItem.objects.create(addon=addon4, recommendable=True)
addon4.current_version.update(recommendation_approved=True)
# del addon4.is_recommended
self.refresh()
data = self.perform_search(self.url) # No query.
ids = [result['id'] for result in data['results']]
# addon2 and addon4 will be first because they're recommended
assert ids == [addon2.id, addon4.id, addon1.id, addon3.id, addon5.id]
# But if the waffle is off the recommended add-ons don't have priority.
with override_switch('api-recommendations-priority', active=False):
assert not switch_is_active('api-recommendations-priority')
data = self.perform_search(self.url) # No query.
ids = [result['id'] for result in data['results']]
assert ids == [
addon1.id, addon2.id, addon3.id, addon4.id, addon5.id]
class TestAddonAutoCompleteSearchView(ESTestCase):
client_class = APITestClient
@ -1966,6 +2005,7 @@ class TestAddonAutoCompleteSearchView(ESTestCase):
self.refresh()
# page_size should be ignored, we should get 10 results.
switch_is_active('api-recommendations-priority') # just to cache it
data = self.perform_search(self.url, {'page_size': 1})
assert 'count' not in data
assert 'next' not in data

Просмотреть файл

@ -850,6 +850,7 @@ class SortingFilter(BaseFilterBackend):
'name': 'name.raw',
'random': '_score',
'rating': '-bayesian_rating',
'recommended': '-is_recommended',
'relevance': '_score',
'updated': '-last_updated',
'users': '-average_daily_users',
@ -862,11 +863,6 @@ class SortingFilter(BaseFilterBackend):
if sort_param is not None:
split_sort_params = sort_param.split(',')
try:
order_by = [self.SORTING_PARAMS[name] for name in
split_sort_params]
except KeyError:
raise serializers.ValidationError('Invalid "sort" parameter.')
# Random sort is a bit special.
# First, it can't be combined with other sorts.
@ -895,10 +891,24 @@ class SortingFilter(BaseFilterBackend):
'when the "featured" or "recommended" parameter is '
'also present, and the "q" parameter absent.')
# The default sort depends on the presence of a query: we sort by
# relevance if we have a query, otherwise by downloads.
if not order_by:
sort_param = 'relevance' if search_query_param else 'downloads'
order_by = [self.SORTING_PARAMS[sort_param]]
# Having just recommended sort doesn't make any sense, so ignore it
if sort_param == 'recommended':
sort_param = None
if sort_param is None:
# The default sort depends on the presence of a query: we sort by
# relevance if we have a query, otherwise by recommended,downloads.
recommended_waffle_on = switch_is_active(
'api-recommendations-priority')
split_sort_params = (
['relevance'] if search_query_param else
['recommended', 'downloads'] if recommended_waffle_on else
['downloads'])
try:
order_by = [self.SORTING_PARAMS[name] for name in
split_sort_params]
except KeyError:
raise serializers.ValidationError('Invalid "sort" parameter.')
return qs.sort(*order_by)

Просмотреть файл

@ -377,16 +377,29 @@ class TestSortingFilter(FilterTestsBase):
# queryset object.
return {key[1:]: {'order': 'desc'}} if key.startswith('-') else key
@override_switch('api-recommendations-priority', active=False)
def test_sort_default_recommendations_waffle_off(self):
qs = self._filter(data={'q': 'something'})
assert qs['sort'] == [self._reformat_order('_score')]
qs = self._filter()
assert qs['sort'] == [
self._reformat_order('-weekly_downloads')]
@override_switch('api-recommendations-priority', active=True)
def test_sort_default(self):
qs = self._filter(data={'q': 'something'})
assert qs['sort'] == [self._reformat_order('_score')]
qs = self._filter()
assert qs['sort'] == [self._reformat_order('-weekly_downloads')]
assert qs['sort'] == [
self._reformat_order('-is_recommended'),
self._reformat_order('-weekly_downloads')]
def test_sort_query(self):
SORTING_PARAMS = copy.copy(SortingFilter.SORTING_PARAMS)
SORTING_PARAMS.pop('random') # Tested separately below.
SORTING_PARAMS.pop('recommended') # Tested separately below.
for param in SORTING_PARAMS:
qs = self._filter(data={'sort': param})
@ -472,6 +485,17 @@ class TestSortingFilter(FilterTestsBase):
{'random_score': {}}
]
@override_switch('api-recommendations-priority', active=True)
def test_sort_recommended_only(self):
# If you try to sort by just recommended it gets ignored
qs = self._filter(data={'q': 'something', 'sort': 'recommended'})
assert qs['sort'] == [self._reformat_order('_score')]
qs = self._filter(data={'sort': 'recommended'})
assert qs['sort'] == [
self._reformat_order('-is_recommended'),
self._reformat_order('-weekly_downloads')]
class TestSearchParameterFilter(FilterTestsBase):
filter_classes = [SearchParameterFilter]