Skip to content

Commit 3f2e745

Browse files
committed
Add extension full-text search
1 parent 5d76bbf commit 3f2e745

5 files changed

Lines changed: 56 additions & 3 deletions

File tree

db_migrations/004.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import datetime
2+
3+
from ext_api.db import extension_collection, migration_collection
4+
5+
__version__ = 4
6+
7+
8+
def run_migration():
9+
extension_collection.create_index([("ProjectPath", "text"), ("Description", "text")])
10+
migration_collection.insert_one({"Version": __version__, "CreatedAt": datetime.datetime.now(datetime.UTC)})

ext_api/db.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
migration_collection: Collection[Migration] = db.Migrations # type: ignore
1919
extension_collection: Collection[Extension] = db.Extensions # type: ignore
2020

21-
__version__: int = 3
21+
__version__: int = 4
2222

2323

2424
class DbMigrationError(Exception):
@@ -100,3 +100,4 @@ def create_indexes() -> None:
100100
extension_collection.create_index("User")
101101
extension_collection.create_index([("Published", 1), ("CreatedAt", -1)])
102102
extension_collection.create_index([("Published", 1), ("GithubStars", -1)])
103+
extension_collection.create_index([("ProjectPath", "text"), ("Description", "text")])

ext_api/repositories/extensions.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,22 @@ def get_extensions(
8888
sort_by: str = "GithubStars",
8989
sort_order: int = -1,
9090
versions: list[str] | None = None,
91+
search_query: str | None = None,
9192
) -> GetExtensionsResult:
9293
query: dict[str, Any] = {"Published": True}
9394

9495
if versions:
9596
query["SupportedVersions"] = {"$in": versions}
9697

97-
cursor = extension_collection.find(query).sort(sort_by, sort_order).skip(offset)
98+
if search_query:
99+
query["$text"] = {"$search": search_query}
100+
101+
cursor = extension_collection.find(query)
102+
if search_query:
103+
cursor = cursor.sort([("score", {"$meta": "textScore"}), (sort_by, sort_order)])
104+
else:
105+
cursor = cursor.sort(sort_by, sort_order)
106+
cursor = cursor.skip(offset)
98107

99108
if limit:
100109
cursor = cursor.limit(limit + 1)

ext_api/server.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def get_extensions_route() -> dict[str, Any]:
7878
Returns all extensions
7979
8080
Query params:
81+
* q: string. Full-text search query across project path and description.
8182
* versions: string. Version of Ulauncher Extension API.
8283
Could be comma-separated list of versions.
8384
Returns all extensions if not specified.
@@ -87,6 +88,10 @@ def get_extensions_route() -> dict[str, Any]:
8788
versions_query = request.GET.get("versions")
8889
versions: list[str] = versions_query.split(",") if versions_query else []
8990
try:
91+
q = request.GET.get("q")
92+
search_query = q.strip() if q is not None else None
93+
if q is not None:
94+
assert search_query, 'query argument "q" cannot be empty'
9095
sort_by = request.GET.get("sort_by") or allowed_sort_by[0]
9196
sort_order = request.GET.get("sort_order") or allowed_sort_order[0]
9297
assert sort_by in allowed_sort_by, "allowed sorty_by: " + ", ".join(allowed_sort_by)
@@ -100,7 +105,14 @@ def get_extensions_route() -> dict[str, Any]:
100105
except (AssertionError, ValueError) as e:
101106
return ErrorResponse(e, 400) # type: ignore
102107

103-
result = get_extensions(offset=offset, limit=limit, sort_by=sort_by, sort_order=int(sort_order), versions=versions)
108+
result = get_extensions(
109+
offset=offset,
110+
limit=limit,
111+
sort_by=sort_by,
112+
sort_order=int(sort_order),
113+
versions=versions,
114+
search_query=search_query,
115+
)
104116

105117
return {"data": result["data"], "offset": offset, "has_more": result["has_more"]}
106118

tests/integration/test_api_integration.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,24 @@ def test_extensions_can_be_filtered_by_api_version(api_client: Any, auth_header:
132132
assert len(data) == 1
133133
assert data[0]["ID"] == "github-stub-owner-legacy-repo"
134134
assert data[0]["SupportedVersions"] == ["3"]
135+
136+
137+
def test_extensions_can_be_searched_by_project_path_and_description(
138+
api_client: Any, auth_header: dict[str, str]
139+
) -> None:
140+
_create_extension(api_client, auth_header, "https://github.com/stub-owner/stub-repo", "Stub Extension")
141+
_create_extension(api_client, auth_header, "https://github.com/stub-owner/legacy-repo", "Legacy Extension")
142+
143+
description_response = api_client.request("GET", "/extensions?q=legacy")
144+
assert description_response.status == 200
145+
description_payload = api_client.parse_json(description_response)
146+
description_data = cast("list[dict[str, Any]]", description_payload["data"])
147+
assert len(description_data) == 1
148+
assert description_data[0]["ID"] == "github-stub-owner-legacy-repo"
149+
150+
project_path_response = api_client.request("GET", '/extensions?q="stub-owner/stub-repo"')
151+
assert project_path_response.status == 200
152+
project_path_payload = api_client.parse_json(project_path_response)
153+
project_path_data = cast("list[dict[str, Any]]", project_path_payload["data"])
154+
assert len(project_path_data) == 1
155+
assert project_path_data[0]["ID"] == "github-stub-owner-stub-repo"

0 commit comments

Comments
 (0)