Skip to content

Commit 7d1c73b

Browse files
committed
Add search result ordering
1 parent 3b4f0c0 commit 7d1c73b

File tree

8 files changed

+105
-13
lines changed

8 files changed

+105
-13
lines changed

tests/unit/cli/search/test_reindex.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def test_project_docs(db_session):
3838
"_id": p.normalized_name,
3939
"_type": "project",
4040
"_source": {
41+
"created": p.created,
4142
"name": p.name,
4243
"normalized_name": p.normalized_name,
4344
"version": [r.version for r in prs],

tests/unit/packaging/test_search.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13+
import datetime
1314
import pretend
1415

1516
from warehouse.packaging import search
@@ -37,6 +38,7 @@ def test_build_search():
3738
download_url="https://example.com/foobar/downloads/",
3839
keywords="the, keywords, lol",
3940
platform="any platform",
41+
created=datetime.datetime(1956, 1, 31),
4042
uploader=pretend.stub(
4143
username="some-username",
4244
name="the-users-name",
@@ -57,5 +59,6 @@ def test_build_search():
5759
assert obj["download_url"] == "https://example.com/foobar/downloads/"
5860
assert obj["keywords"] == "the, keywords, lol"
5961
assert obj["platform"] == "any platform"
62+
assert obj["created"] == datetime.datetime(1956, 1, 31)
6063
assert obj["uploader_name"] == "the-users-name"
6164
assert obj["uploader_username"] == "some-username"

tests/unit/test_views.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ def test_with_a_query(self, monkeypatch, page):
119119
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
120120
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
121121

122-
assert search(request) == {"page": page_obj, "term": params.get("q")}
122+
assert search(request) == {
123+
"page": page_obj,
124+
"term": params.get("q"),
125+
"order": params.get("o"),
126+
}
123127
assert page_cls.calls == [
124128
pretend.call(suggest, url_maker=url_maker, page=page or 1),
125129
]
@@ -143,6 +147,64 @@ def test_with_a_query(self, monkeypatch, page):
143147
),
144148
]
145149

150+
@pytest.mark.parametrize("page", [None, 1, 5])
151+
def test_with_an_ordering(self, monkeypatch, page):
152+
params = {"q": "foo bar", "o": "-created"}
153+
if page is not None:
154+
params["page"] = page
155+
sort = pretend.stub()
156+
suggest = pretend.stub(
157+
sort=pretend.call_recorder(lambda *a, **kw: sort),
158+
)
159+
query = pretend.stub(
160+
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
161+
)
162+
request = pretend.stub(
163+
es=pretend.stub(
164+
query=pretend.call_recorder(lambda *a, **kw: query),
165+
),
166+
params=params,
167+
)
168+
169+
page_obj = pretend.stub()
170+
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
171+
monkeypatch.setattr(views, "ElasticsearchPage", page_cls)
172+
173+
url_maker = pretend.stub()
174+
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
175+
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
176+
177+
assert search(request) == {
178+
"page": page_obj,
179+
"term": params.get("q"),
180+
"order": params.get("o"),
181+
}
182+
assert page_cls.calls == [
183+
pretend.call(sort, url_maker=url_maker, page=page or 1),
184+
]
185+
assert url_maker_factory.calls == [pretend.call(request)]
186+
assert request.es.query.calls == [
187+
pretend.call(
188+
"multi_match",
189+
query="foo bar",
190+
fields=[
191+
"name", "version", "author", "author_email", "maintainer",
192+
"maintainer_email", "home_page", "license", "summary",
193+
"description", "keywords", "platform", "download_url",
194+
],
195+
),
196+
]
197+
assert query.suggest.calls == [
198+
pretend.call(
199+
name="name_suggestion",
200+
term={"field": "name"},
201+
text="foo bar",
202+
),
203+
]
204+
assert suggest.sort.calls == [
205+
pretend.call("-created")
206+
]
207+
146208
@pytest.mark.parametrize("page", [None, 1, 5])
147209
def test_without_a_query(self, monkeypatch, page):
148210
params = {}
@@ -162,7 +224,11 @@ def test_without_a_query(self, monkeypatch, page):
162224
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
163225
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
164226

165-
assert search(request) == {"page": page_obj, "term": params.get("q")}
227+
assert search(request) == {
228+
"page": page_obj,
229+
"term": params.get("q"),
230+
"order": params.get("o"),
231+
}
166232
assert page_cls.calls == [
167233
pretend.call(query, url_maker=url_maker, page=page or 1),
168234
]

warehouse/packaging/search.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13-
from elasticsearch_dsl import DocType, String, analyzer, MetaField
13+
from elasticsearch_dsl import DocType, String, analyzer, MetaField, Date
1414

1515
from warehouse.search import doc_type
1616

@@ -39,6 +39,7 @@ class Project(DocType):
3939
download_url = String(index="not_analyzed")
4040
keywords = String(analyzer="snowball")
4141
platform = String(index="not_analyzed")
42+
created = Date()
4243

4344
uploader_name = String()
4445
uploader_username = String()
@@ -63,6 +64,7 @@ def from_db(cls, release):
6364
obj["download_url"] = release.download_url
6465
obj["keywords"] = release.keywords
6566
obj["platform"] = release.platform
67+
obj["created"] = release.created
6668

6769
obj["uploader_name"] = release.uploader.name
6870
obj["uploader_username"] = release.uploader.username

warehouse/static/js/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ $(document).ready(function() {
7070
positionWarning();
7171
});
7272

73+
// Search ordering
74+
$('#order').on("change", function(event) {
75+
this.form.submit();
76+
});
77+
7378
$.timeago.settings.cutoff = 7 * 24 * 60 * 60 * 1000; // One week
7479

7580
// document.l10n.ready.then(function() {

warehouse/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
<form class="search-form -primary" action="{{ request.route_path('search') }}">
5959
<label for="search" class="sr-only">Search PyPI</label>
60-
<input id="search" class="search" type="text" name="q" {{ l20n("search") }} placeholder="Search Projects">
60+
<input id="search" class="search" type="text" name="q" {{ l20n("search") }} placeholder="Search Projects" value="{{ term }}">
6161
<input class="wh-button -dark" type="submit" {{ l20n("search") }} value="Search">
6262
</form>
6363
</div>

warehouse/templates/search/results.html

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
{% macro project_snippet(item) -%}
1818

1919
{% set version = item.version[0] %}
20+
{% set created = item.created %}
2021
{% set uploader_username = item.uploader_username %}
2122
{% set uploader_name = item.uploader_name|default(uploader_username, true) %}
2223

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

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

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

4950

51+
{% macro search_option(text, value, l20n_id) -%}
52+
<option {{ l20n(l20n_id) }} value="{{ value }}" {{ 'selected' if value == order else ''}}>{{ text }}</option>
53+
{%- endmacro %}
54+
5055
{% block content %}
5156
<section class="horizontal-section -medium">
5257
<div class="left-sidebar">
@@ -86,13 +91,16 @@ <h2 {{ l20n("filterProjects") }}>Filter Projects</h2>
8691
<strong>{{ page.item_count }}</strong> projects.
8792
</p>
8893
{% endif %}
89-
<p>
90-
<label for="order" {{ l20n("orderPackagesBy") }}>Order packages by &nbsp;</label>
91-
<select id="order">
92-
<option {{ l20n("relevance") }}>Relevance</option>
93-
<option {{ l20n("dateLastUpdated") }}>Date Last Updated</option>
94-
</select>
95-
</p>
94+
<form action="{{ request.route_path('search') }}">
95+
<p>
96+
<input id="search" type="hidden" name="q" {{ l20n("search") }} value="{{ term }}">
97+
<label for="order" {{ l20n("orderPackagesBy") }}>Order packages by &nbsp;</label>
98+
<select id="order" name="o">
99+
{{ search_option("Relevance", "", "relevance") }}
100+
{{ search_option("Date Last Updated", "-created", "dateLastUpdated") }}
101+
</select>
102+
</p>
103+
</form>
96104
</section>
97105

98106
<div class="applied-filters">

warehouse/views.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,20 @@ def search(request):
171171
else:
172172
query = request.es.query()
173173

174+
if request.params.get("o"):
175+
query = query.sort(request.params["o"])
176+
174177
page = ElasticsearchPage(
175178
query,
176179
page=int(request.params.get("page", 1)),
177180
url_maker=paginate_url_factory(request),
178181
)
179182

180-
return {"page": page, "term": request.params.get("q")}
183+
return {
184+
"page": page,
185+
"term": request.params.get("q"),
186+
"order": request.params.get("o"),
187+
}
181188

182189

183190
@view_config(

0 commit comments

Comments
 (0)