Skip to content

Add search result ordering #1004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/unit/cli/search/test_reindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_project_docs(db_session):
"_id": p.normalized_name,
"_type": "project",
"_source": {
"created": p.created,
"name": p.name,
"normalized_name": p.normalized_name,
"version": [r.version for r in prs],
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/packaging/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
import pretend

from warehouse.packaging import search
Expand Down Expand Up @@ -37,6 +38,7 @@ def test_build_search():
download_url="https://example.com/foobar/downloads/",
keywords="the, keywords, lol",
platform="any platform",
created=datetime.datetime(1956, 1, 31),
uploader=pretend.stub(
username="some-username",
name="the-users-name",
Expand All @@ -57,5 +59,6 @@ def test_build_search():
assert obj["download_url"] == "https://example.com/foobar/downloads/"
assert obj["keywords"] == "the, keywords, lol"
assert obj["platform"] == "any platform"
assert obj["created"] == datetime.datetime(1956, 1, 31)
assert obj["uploader_name"] == "the-users-name"
assert obj["uploader_username"] == "some-username"
70 changes: 68 additions & 2 deletions tests/unit/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ def test_with_a_query(self, monkeypatch, page):
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)

assert search(request) == {"page": page_obj, "term": params.get("q")}
assert search(request) == {
"page": page_obj,
"term": params.get("q"),
"order": params.get("o"),
}
assert page_cls.calls == [
pretend.call(suggest, url_maker=url_maker, page=page or 1),
]
Expand All @@ -143,6 +147,64 @@ def test_with_a_query(self, monkeypatch, page):
),
]

@pytest.mark.parametrize("page", [None, 1, 5])
def test_with_an_ordering(self, monkeypatch, page):
params = {"q": "foo bar", "o": "-created"}
if page is not None:
params["page"] = page
sort = pretend.stub()
suggest = pretend.stub(
sort=pretend.call_recorder(lambda *a, **kw: sort),
)
query = pretend.stub(
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
)
request = pretend.stub(
es=pretend.stub(
query=pretend.call_recorder(lambda *a, **kw: query),
),
params=params,
)

page_obj = pretend.stub()
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
monkeypatch.setattr(views, "ElasticsearchPage", page_cls)

url_maker = pretend.stub()
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)

assert search(request) == {
"page": page_obj,
"term": params.get("q"),
"order": params.get("o"),
}
assert page_cls.calls == [
pretend.call(sort, url_maker=url_maker, page=page or 1),
]
assert url_maker_factory.calls == [pretend.call(request)]
assert request.es.query.calls == [
pretend.call(
"multi_match",
query="foo bar",
fields=[
"name", "version", "author", "author_email", "maintainer",
"maintainer_email", "home_page", "license", "summary",
"description", "keywords", "platform", "download_url",
],
),
]
assert query.suggest.calls == [
pretend.call(
name="name_suggestion",
term={"field": "name"},
text="foo bar",
),
]
assert suggest.sort.calls == [
pretend.call("-created")
]

@pytest.mark.parametrize("page", [None, 1, 5])
def test_without_a_query(self, monkeypatch, page):
params = {}
Expand All @@ -162,7 +224,11 @@ def test_without_a_query(self, monkeypatch, page):
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)

assert search(request) == {"page": page_obj, "term": params.get("q")}
assert search(request) == {
"page": page_obj,
"term": params.get("q"),
"order": params.get("o"),
}
assert page_cls.calls == [
pretend.call(query, url_maker=url_maker, page=page or 1),
]
Expand Down
4 changes: 3 additions & 1 deletion warehouse/packaging/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from elasticsearch_dsl import DocType, String, analyzer, MetaField
from elasticsearch_dsl import DocType, String, analyzer, MetaField, Date

from warehouse.search import doc_type

Expand Down Expand Up @@ -39,6 +39,7 @@ class Project(DocType):
download_url = String(index="not_analyzed")
keywords = String(analyzer="snowball")
platform = String(index="not_analyzed")
created = Date()

uploader_name = String()
uploader_username = String()
Expand All @@ -63,6 +64,7 @@ def from_db(cls, release):
obj["download_url"] = release.download_url
obj["keywords"] = release.keywords
obj["platform"] = release.platform
obj["created"] = release.created

obj["uploader_name"] = release.uploader.name
obj["uploader_username"] = release.uploader.username
Expand Down
5 changes: 5 additions & 0 deletions warehouse/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ $(document).ready(function() {
positionWarning();
});

// Search ordering
$('#order').on("change", function(event) {
this.form.submit();
});

$.timeago.settings.cutoff = 7 * 24 * 60 * 60 * 1000; // One week

// document.l10n.ready.then(function() {
Expand Down
2 changes: 1 addition & 1 deletion warehouse/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

<form class="search-form -primary" action="{{ request.route_path('search') }}">
<label for="search" class="sr-only">Search PyPI</label>
<input id="search" class="search" type="text" name="q" {{ l20n("search") }} placeholder="Search Projects">
<input id="search" class="search" type="text" name="q" {{ l20n("search") }} placeholder="Search Projects" value="{{ term }}">
<input class="wh-button -dark" type="submit" {{ l20n("search") }} value="Search">
</form>
</div>
Expand Down
24 changes: 16 additions & 8 deletions warehouse/templates/search/results.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
{% macro project_snippet(item) -%}

{% set version = item.version[0] %}
{% set created = item.created %}
{% set uploader_username = item.uploader_username %}
{% set uploader_name = item.uploader_name|default(uploader_username, true) %}

<div class="package-snippet">
<h3 class="title"><a href="{{ request.route_path('packaging.project', name=item.normalized_name) }}">{{ item.name }}</a></h3>

<p class="meta" {{ l20n("packageSnippetMeta", version=version, user=uploader_name) }}>
<span class="version">{{ version }}</span> uploaded by <a href="{{ request.route_path('accounts.profile', username=uploader_username) }}">{{ uploader_name }}</a>
<span class="version">{{ version }}</span> uploaded by <a href="{{ request.route_path('accounts.profile', username=uploader_username) }}">{{ uploader_name }}</a> on {{ created|format_date() }}
</p>

<p class="description">{{ item.summary }}</p>
Expand All @@ -47,6 +48,10 @@ <h3 class="title"><a href="{{ request.route_path('packaging.project', name=item.
{%- endmacro %}


{% macro search_option(text, value, l20n_id) -%}
<option {{ l20n(l20n_id) }} value="{{ value }}" {{ 'selected' if value == order else ''}}>{{ text }}</option>
{%- endmacro %}

{% block content %}
<section class="horizontal-section -medium">
<div class="left-sidebar">
Expand Down Expand Up @@ -86,13 +91,16 @@ <h2 {{ l20n("filterProjects") }}>Filter Projects</h2>
<strong>{{ page.item_count }}</strong> projects.
</p>
{% endif %}
<p>
<label for="order" {{ l20n("orderPackagesBy") }}>Order packages by &nbsp;</label>
<select id="order">
<option {{ l20n("relevance") }}>Relevance</option>
<option {{ l20n("dateLastUpdated") }}>Date Last Updated</option>
</select>
</p>
<form action="{{ request.route_path('search') }}">
<p>
<input id="search" type="hidden" name="q" {{ l20n("search") }} value="{{ term }}">
<label for="order" {{ l20n("orderPackagesBy") }}>Order packages by &nbsp;</label>
<select id="order" name="o">
{{ search_option("Relevance", "", "relevance") }}
{{ search_option("Date Last Updated", "-created", "dateLastUpdated") }}
</select>
</p>
</form>
</section>

<div class="applied-filters">
Expand Down
9 changes: 8 additions & 1 deletion warehouse/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,20 @@ def search(request):
else:
query = request.es.query()

if request.params.get("o"):
query = query.sort(request.params["o"])

page = ElasticsearchPage(
query,
page=int(request.params.get("page", 1)),
url_maker=paginate_url_factory(request),
)

return {"page": page, "term": request.params.get("q")}
return {
"page": page,
"term": request.params.get("q"),
"order": request.params.get("o"),
}


@view_config(
Expand Down