feat: add paging to package depends & required by

This patch does not include a javascript implementating, but
provides a pure HTML/HTTP method of paging through these lists.

Also fixes erroneous limiting. We now use a hardcoded limit of 20
by default.

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2022-03-30 16:16:47 -07:00
parent d8564e446b
commit ed41a4fe19
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
7 changed files with 125 additions and 16 deletions

View file

@ -214,7 +214,7 @@ def query_notified(query: List[models.Package],
return output return output
def pkg_required(pkgname: str, provides: List[str], limit: int) \ def pkg_required(pkgname: str, provides: List[str]) \
-> List[PackageDependency]: -> List[PackageDependency]:
""" """
Get dependencies that match a string in `[pkgname] + provides`. Get dependencies that match a string in `[pkgname] + provides`.
@ -227,8 +227,8 @@ def pkg_required(pkgname: str, provides: List[str], limit: int) \
targets = set([pkgname] + provides) targets = set([pkgname] + provides)
query = db.query(PackageDependency).join(Package).filter( query = db.query(PackageDependency).join(Package).filter(
PackageDependency.DepName.in_(targets) PackageDependency.DepName.in_(targets)
).order_by(Package.Name.asc()).limit(limit) ).order_by(Package.Name.asc())
return query.all() return query
@register_filter("source_uri") @register_filter("source_uri")

View file

@ -11,16 +11,25 @@ from aurweb.models.package_request import PENDING_ID, PackageRequest
from aurweb.models.package_vote import PackageVote from aurweb.models.package_vote import PackageVote
from aurweb.scripts import notify from aurweb.scripts import notify
from aurweb.templates import make_context as _make_context from aurweb.templates import make_context as _make_context
from aurweb.templates import make_variable_context as _make_variable_context
def make_context(request: Request, pkgbase: PackageBase) -> Dict[str, Any]: async def make_variable_context(request: Request, pkgbase: PackageBase) \
-> Dict[str, Any]:
ctx = await _make_variable_context(request, pkgbase.Name)
return make_context(request, pkgbase, ctx)
def make_context(request: Request, pkgbase: PackageBase,
context: Dict[str, Any] = None) -> Dict[str, Any]:
""" Make a basic context for package or pkgbase. """ Make a basic context for package or pkgbase.
:param request: FastAPI request :param request: FastAPI request
:param pkgbase: PackageBase instance :param pkgbase: PackageBase instance
:return: A pkgbase context without specific differences :return: A pkgbase context without specific differences
""" """
context = _make_context(request, pkgbase.Name) if not context:
context = _make_context(request, pkgbase.Name)
context["git_clone_uri_anon"] = config.get("options", "git_clone_uri_anon") context["git_clone_uri_anon"] = config.get("options", "git_clone_uri_anon")
context["git_clone_uri_priv"] = config.get("options", "git_clone_uri_priv") context["git_clone_uri_priv"] = config.get("options", "git_clone_uri_priv")

View file

@ -2,7 +2,7 @@ from collections import defaultdict
from http import HTTPStatus from http import HTTPStatus
from typing import Any, Dict, List from typing import Any, Dict, List
from fastapi import APIRouter, Form, Request, Response from fastapi import APIRouter, Form, Query, Request, Response
import aurweb.filters # noqa: F401 import aurweb.filters # noqa: F401
@ -33,7 +33,7 @@ async def packages_get(request: Request, context: Dict[str, Any],
context["O"] = offset context["O"] = offset
# Limit PP to options.max_search_results # Limit PP to options.max_search_results
max_search_results = aurweb.config.getint("options", "max_search_results") max_search_results = config.getint("options", "max_search_results")
context["PP"] = per_page = min(per_page, max_search_results) context["PP"] = per_page = min(per_page, max_search_results)
# Query search by. # Query search by.
@ -123,7 +123,22 @@ async def packages(request: Request) -> Response:
@router.get("/packages/{name}") @router.get("/packages/{name}")
async def package(request: Request, name: str) -> Response: async def package(request: Request, name: str,
all_deps: bool = Query(default=False),
all_reqs: bool = Query(default=False)) -> Response:
"""
Get a package by name.
By default, we limit the number of depends and requires results
to 20. To bypass this and load all of them, which should be triggered
via a "Show more" link near the limited listing.
:param name: Package.Name
:param all_deps: Boolean indicating whether we should load all depends
:param all_reqs: Boolean indicating whether we should load all requires
:return: FastAPI Response
"""
# Get the Package. # Get the Package.
pkg = get_pkg_or_base(name, models.Package) pkg = get_pkg_or_base(name, models.Package)
pkgbase = pkg.PackageBase pkgbase = pkg.PackageBase
@ -139,23 +154,41 @@ async def package(request: Request, name: str) -> Response:
rels_data["r"].append(rel) rels_data["r"].append(rel)
# Add our base information. # Add our base information.
context = pkgbaseutil.make_context(request, pkgbase) context = await pkgbaseutil.make_variable_context(request, pkgbase)
context.update(
{
"all_deps": all_deps,
"all_reqs": all_reqs
}
)
context["package"] = pkg context["package"] = pkg
# Package sources. # Package sources.
context["sources"] = pkg.package_sources.order_by( context["sources"] = pkg.package_sources.order_by(
models.PackageSource.Source.asc()).all() models.PackageSource.Source.asc()).all()
# Listing metadata.
context["max_listing"] = max_listing = 20
# Package dependencies. # Package dependencies.
max_depends = config.getint("options", "max_depends") deps = pkg.package_dependencies.order_by(
context["dependencies"] = pkg.package_dependencies.order_by(
models.PackageDependency.DepTypeID.asc(), models.PackageDependency.DepTypeID.asc(),
models.PackageDependency.DepName.asc() models.PackageDependency.DepName.asc()
).limit(max_depends).all() )
context["depends_count"] = deps.count()
if not all_deps:
deps = deps.limit(max_listing)
context["dependencies"] = deps.all()
# Package requirements (other packages depend on this one). # Package requirements (other packages depend on this one).
context["required_by"] = pkgutil.pkg_required( reqs = pkgutil.pkg_required(
pkg.Name, [p.RelName for p in rels_data.get("p", [])], max_depends) pkg.Name, [p.RelName for p in rels_data.get("p", [])])
context["reqs_count"] = reqs.count()
if not all_reqs:
reqs = reqs.limit(max_listing)
context["required_by"] = reqs.all()
context["licenses"] = pkg.package_licenses context["licenses"] = pkg.package_licenses

View file

@ -100,6 +100,8 @@ async def make_variable_context(request: Request, title: str, next: str = None):
for k, v in to_copy.items(): for k, v in to_copy.items():
context[k] = v context[k] = v
context["q"] = dict(request.query_params)
return context return context

View file

@ -2338,3 +2338,11 @@ msgstr ""
#: templates/partials/tu/proposal/details.html #: templates/partials/tu/proposal/details.html
msgid "assigned" msgid "assigned"
msgstr "" msgstr ""
#: templaets/partials/packages/package_metadata.html
msgid "Show %d more"
msgstr ""
#: templates/partials/packages/package_metadata.html
msgid "dependencies"
msgstr ""

View file

@ -1,5 +1,5 @@
<div id="pkgdeps" class="listing"> <div id="pkgdeps" class="listing">
<h3>{{ "Dependencies" | tr }} ({{ dependencies | length }})</h3> <h3>{{ "Dependencies" | tr }} ({{ depends_count }})</h3>
<ul id="pkgdepslist"> <ul id="pkgdepslist">
{% for dep in dependencies %} {% for dep in dependencies %}
{# Collect provides for `dep`. #} {# Collect provides for `dep`. #}
@ -34,11 +34,18 @@
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}
{% if not all_deps and depends_count > max_listing %}
<li>
<a href="/packages/{{ package.Name }}?{{ q | extend_query(['all_deps', '1']) | urlencode }}#pkgdeps">
{{ "Show %d more" | tr | format(depends_count - (dependencies | length)) }} {{ "dependencies" | tr }}...
</a>
</li>
{% endif %}
</ul> </ul>
</div> </div>
<div id="pkgreqs" class="listing"> <div id="pkgreqs" class="listing">
<h3>{{ "Required by" | tr }} ({{ required_by | length }})</h3> <h3>{{ "Required by" | tr }} ({{ reqs_count }})</h3>
<ul id="pkgreqslist"> <ul id="pkgreqslist">
{% for dep in required_by %} {% for dep in required_by %}
<li> <li>
@ -55,6 +62,11 @@
<em>{{ dep | dep_extra }}</em> <em>{{ dep | dep_extra }}</em>
</li> </li>
{% endfor %} {% endfor %}
{% if not all_reqs and (required_by | length) > max_listing %}
<a href="/packages/{{ name }}?{{ q | extend_query(['all_reqs', '1']) | urlencode }}#pkgreqs">
{{ "Show %d more" | tr | format(reqs_count - (required_by | length)) }}...
</a>
{% endif %}
</ul> </ul>
</div> </div>

View file

@ -276,6 +276,51 @@ def test_package(client: TestClient, package: Package):
assert conflicts[0].text.strip() == ", ".join(expected) assert conflicts[0].text.strip() == ", ".join(expected)
def paged_depends_required(client: TestClient, package: Package):
maint = package.PackageBase.Maintainer
new_pkgs = []
with db.begin():
# Create 25 new packages that'll be used to depend on our package.
for i in range(26):
base = db.create(PackageBase, Name=f"new_pkg{i}", Maintainer=maint)
new_pkgs.append(db.create(Package, Name=base.Name))
# Create 25 deps.
for i in range(25):
create_package_dep(package, f"dep_{i}")
with db.begin():
# Create depends on this package so we get some required by listings.
for new_pkg in new_pkgs:
create_package_dep(new_pkg, package.Name)
with client as request:
resp = request.get(package_endpoint(package))
assert resp.status_code == int(HTTPStatus.OK)
# Test depends show link.
assert "Show 5 more" in resp.text
# Test required by show more link, we added 26 packages.
assert "Show 6 more" in resp.text
# Follow both links at the same time.
with client as request:
resp = request.get(
package_endpoint(package),
params={
"all_deps": True,
"all_reqs": True,
}
)
assert resp.status_code == int(HTTPStatus.OK)
# We're should see everything and have no link.
assert "Show 5 more" not in resp.text
assert "Show 6 more" not in resp.text
def test_package_comments(client: TestClient, user: User, package: Package): def test_package_comments(client: TestClient, user: User, package: Package):
now = (time.utcnow()) now = (time.utcnow())
with db.begin(): with db.begin():