mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat: display stats about total & active TUs on proposals
This patch brings in two new features: - when viewing proposal listings, there is a new Statistics section, containing the total and active number of Trusted Users found in the database. - when viewing a proposal directly, the number of active trusted users assigned when the proposal was added is now displayed in the details section. Closes #323 Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
0afa07ed3b
commit
49c5a3facf
6 changed files with 107 additions and 0 deletions
|
@ -2,6 +2,7 @@ import html
|
|||
import typing
|
||||
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Dict
|
||||
|
||||
from fastapi import APIRouter, Form, HTTPException, Request
|
||||
from fastapi.responses import RedirectResponse, Response
|
||||
|
@ -33,6 +34,21 @@ ADDVOTE_SPECIFICS = {
|
|||
}
|
||||
|
||||
|
||||
def populate_trusted_user_counts(context: Dict[str, Any]) -> None:
|
||||
tu_query = db.query(User).filter(
|
||||
or_(User.AccountTypeID == TRUSTED_USER_ID,
|
||||
User.AccountTypeID == TRUSTED_USER_AND_DEV_ID)
|
||||
)
|
||||
context["trusted_user_count"] = tu_query.count()
|
||||
|
||||
# In case any records have a None InactivityTS.
|
||||
active_tu_query = tu_query.filter(
|
||||
or_(User.InactivityTS.is_(None),
|
||||
User.InactivityTS == 0)
|
||||
)
|
||||
context["active_trusted_user_count"] = active_tu_query.count()
|
||||
|
||||
|
||||
@router.get("/tu")
|
||||
@requires_auth
|
||||
async def trusted_user(request: Request,
|
||||
|
@ -40,6 +56,8 @@ async def trusted_user(request: Request,
|
|||
cby: str = "desc", # current by
|
||||
poff: int = 0, # past offset
|
||||
pby: str = "desc"): # past by
|
||||
""" Proposal listings. """
|
||||
|
||||
if not request.user.has_credential(creds.TU_LIST_VOTES):
|
||||
return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER)
|
||||
|
||||
|
@ -102,6 +120,8 @@ async def trusted_user(request: Request,
|
|||
context["current_by_next"] = "asc" if current_by == "desc" else "desc"
|
||||
context["past_by_next"] = "asc" if past_by == "desc" else "desc"
|
||||
|
||||
populate_trusted_user_counts(context)
|
||||
|
||||
context["q"] = {
|
||||
"coff": current_off,
|
||||
"cby": current_by,
|
||||
|
|
|
@ -2334,3 +2334,7 @@ msgid "This action will close any pending package requests "
|
|||
"related to it. If %sComments%s are omitted, a closure "
|
||||
"comment will be autogenerated."
|
||||
msgstr ""
|
||||
|
||||
#: templates/partials/tu/proposal/details.html
|
||||
msgid "assigned"
|
||||
msgstr ""
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
</strong>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
{{ "Active" | tr }} {{ "Trusted Users" | tr }} {{ "assigned" | tr }}:
|
||||
{{ voteinfo.ActiveTUs }}
|
||||
</div>
|
||||
|
||||
{% set submitter = voteinfo.Submitter.Username %}
|
||||
{% set submitter_uri = "/account/%s" | format(submitter) %}
|
||||
{% set submitter = '<a href="%s">%s</a>' | format(submitter_uri, submitter) %}
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
{% extends "partials/layout.html" %}
|
||||
|
||||
{% block pageContent %}
|
||||
<div class="box">
|
||||
<h2>{{ "Statistics" | tr }}</h2>
|
||||
<table class="no-width">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-right">{{ "Total" | tr }} {{ "Trusted Users" | tr }}:</td>
|
||||
<td>{{ trusted_user_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right">{{ "Active" | tr }} {{ "Trusted Users" | tr }}:</td>
|
||||
<td>{{ active_trusted_user_count }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{%
|
||||
with table_class = "current-votes",
|
||||
total_votes = current_votes_count,
|
||||
|
|
|
@ -267,6 +267,48 @@ def test_tu_index(client, tu_user):
|
|||
assert int(vote_id.text.strip()) == vote_records[1].ID
|
||||
|
||||
|
||||
def test_tu_stats(client: TestClient, tu_user: User):
|
||||
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
||||
with client as request:
|
||||
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
root = parse_root(response.text)
|
||||
stats = root.xpath('//table[@class="no-width"]')[0]
|
||||
rows = stats.xpath("./tbody/tr")
|
||||
|
||||
# We have one trusted user.
|
||||
total = rows[0]
|
||||
label, count = total.xpath("./td")
|
||||
assert int(count.text.strip()) == 1
|
||||
|
||||
# And we have one active TU.
|
||||
active = rows[1]
|
||||
label, count = active.xpath("./td")
|
||||
assert int(count.text.strip()) == 1
|
||||
|
||||
with db.begin():
|
||||
tu_user.InactivityTS = time.utcnow()
|
||||
|
||||
with client as request:
|
||||
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
root = parse_root(response.text)
|
||||
stats = root.xpath('//table[@class="no-width"]')[0]
|
||||
rows = stats.xpath("./tbody/tr")
|
||||
|
||||
# We have one trusted user.
|
||||
total = rows[0]
|
||||
label, count = total.xpath("./td")
|
||||
assert int(count.text.strip()) == 1
|
||||
|
||||
# But we have no more active TUs.
|
||||
active = rows[1]
|
||||
label, count = active.xpath("./td")
|
||||
assert int(count.text.strip()) == 0
|
||||
|
||||
|
||||
def test_tu_index_table_paging(client, tu_user):
|
||||
ts = time.utcnow()
|
||||
|
||||
|
@ -515,6 +557,8 @@ def test_tu_proposal_unauthorized(client: TestClient, user: User,
|
|||
def test_tu_running_proposal(client: TestClient,
|
||||
proposal: Tuple[User, User, TUVoteInfo]):
|
||||
tu_user, user, voteinfo = proposal
|
||||
with db.begin():
|
||||
voteinfo.ActiveTUs = 1
|
||||
|
||||
# Initiate an authenticated GET request to /tu/{proposal_id}.
|
||||
proposal_id = voteinfo.ID
|
||||
|
@ -536,6 +580,11 @@ def test_tu_running_proposal(client: TestClient,
|
|||
'./div[contains(@class, "user")]/strong/a/text()')[0]
|
||||
assert username.strip() == user.Username
|
||||
|
||||
active = details.xpath('./div[contains(@class, "field")]')[1]
|
||||
content = active.text.strip()
|
||||
assert "Active Trusted Users assigned:" in content
|
||||
assert "1" in content
|
||||
|
||||
submitted = details.xpath(
|
||||
'./div[contains(@class, "submitted")]/text()')[0]
|
||||
assert re.match(r'^Submitted: \d{4}-\d{2}-\d{2} \d{2}:\d{2} \(.+\) by$',
|
||||
|
|
|
@ -282,3 +282,16 @@ pre.traceback {
|
|||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
/* A text aligning alias. */
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* By default, tables use 100% width, which we do not always want. */
|
||||
table.no-width {
|
||||
width: auto;
|
||||
}
|
||||
table.no-width > tbody > tr > td {
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue