feat: allow users to hide deleted comments

Closes: #435

Signed-off-by: Leonidas Spyropoulos <artafinde@archlinux.org>
This commit is contained in:
Leonidas Spyropoulos 2023-04-21 19:47:55 +01:00
parent 174af5f025
commit 6ede837b4f
No known key found for this signature in database
GPG key ID: 59E43E106B247368
9 changed files with 132 additions and 5 deletions

View file

@ -209,6 +209,7 @@ def make_account_form_context(
context["cn"] = args.get("CN", user.CommentNotify) context["cn"] = args.get("CN", user.CommentNotify)
context["un"] = args.get("UN", user.UpdateNotify) context["un"] = args.get("UN", user.UpdateNotify)
context["on"] = args.get("ON", user.OwnershipNotify) context["on"] = args.get("ON", user.OwnershipNotify)
context["hdc"] = args.get("HDC", user.HideDeletedComments)
context["inactive"] = args.get("J", user.InactivityTS != 0) context["inactive"] = args.get("J", user.InactivityTS != 0)
else: else:
context["username"] = args.get("U", str()) context["username"] = args.get("U", str())
@ -227,6 +228,7 @@ def make_account_form_context(
context["cn"] = args.get("CN", True) context["cn"] = args.get("CN", True)
context["un"] = args.get("UN", False) context["un"] = args.get("UN", False)
context["on"] = args.get("ON", True) context["on"] = args.get("ON", True)
context["hdc"] = args.get("HDC", False)
context["inactive"] = args.get("J", False) context["inactive"] = args.get("J", False)
context["password"] = args.get("P", str()) context["password"] = args.get("P", str())
@ -253,6 +255,7 @@ async def account_register(
CN: bool = Form(default=False), # Comment Notify CN: bool = Form(default=False), # Comment Notify
CU: bool = Form(default=False), # Update Notify CU: bool = Form(default=False), # Update Notify
CO: bool = Form(default=False), # Owner Notify CO: bool = Form(default=False), # Owner Notify
HDC: bool = Form(default=False), # Hide Deleted Comments
captcha: str = Form(default=str()), captcha: str = Form(default=str()),
): ):
context = await make_variable_context(request, "Register") context = await make_variable_context(request, "Register")
@ -281,6 +284,7 @@ async def account_register_post(
CN: bool = Form(default=False), CN: bool = Form(default=False),
UN: bool = Form(default=False), UN: bool = Form(default=False),
ON: bool = Form(default=False), ON: bool = Form(default=False),
HDC: bool = Form(default=False),
captcha: str = Form(default=None), captcha: str = Form(default=None),
captcha_salt: str = Form(...), captcha_salt: str = Form(...),
): ):
@ -334,6 +338,7 @@ async def account_register_post(
CommentNotify=CN, CommentNotify=CN,
UpdateNotify=UN, UpdateNotify=UN,
OwnershipNotify=ON, OwnershipNotify=ON,
HideDeletedComments=HDC,
ResetKey=resetkey, ResetKey=resetkey,
AccountType=atype, AccountType=atype,
) )
@ -417,6 +422,7 @@ async def account_edit_post(
CN: bool = Form(default=False), # Comment Notify CN: bool = Form(default=False), # Comment Notify
UN: bool = Form(default=False), # Update Notify UN: bool = Form(default=False), # Update Notify
ON: bool = Form(default=False), # Owner Notify ON: bool = Form(default=False), # Owner Notify
HDC: bool = Form(default=False), # Hide Deleted Comments
T: int = Form(default=None), T: int = Form(default=None),
passwd: str = Form(default=str()), passwd: str = Form(default=str()),
): ):

View file

@ -108,6 +108,12 @@ Users = Table(
Column("OwnershipNotify", TINYINT(1), nullable=False, server_default=text("1")), Column("OwnershipNotify", TINYINT(1), nullable=False, server_default=text("1")),
Column("SSOAccountID", String(255), nullable=True, unique=True), Column("SSOAccountID", String(255), nullable=True, unique=True),
Index("UsersAccountTypeID", "AccountTypeID"), Index("UsersAccountTypeID", "AccountTypeID"),
Column(
"HideDeletedComments",
TINYINT(unsigned=True),
nullable=False,
server_default=text("0"),
),
mysql_engine="InnoDB", mysql_engine="InnoDB",
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
mysql_collate="utf8mb4_general_ci", mysql_collate="utf8mb4_general_ci",

View file

@ -22,6 +22,7 @@ def simple(
CN: bool = False, CN: bool = False,
UN: bool = False, UN: bool = False,
ON: bool = False, ON: bool = False,
HDC: bool = False,
S: bool = False, S: bool = False,
user: models.User = None, user: models.User = None,
**kwargs, **kwargs,
@ -41,6 +42,7 @@ def simple(
user.CommentNotify = strtobool(CN) user.CommentNotify = strtobool(CN)
user.UpdateNotify = strtobool(UN) user.UpdateNotify = strtobool(UN)
user.OwnershipNotify = strtobool(ON) user.OwnershipNotify = strtobool(ON)
user.HideDeletedComments = strtobool(HDC)
@db.retry_deadlock @db.retry_deadlock

View file

@ -0,0 +1,33 @@
"""Add HideDeletedComments to User
Revision ID: e4e49ffce091
Revises: 9e3158957fd7
Create Date: 2023-04-19 23:24:25.854874
"""
from alembic import op
from sqlalchemy.exc import OperationalError
from aurweb.models.user import User
# revision identifiers, used by Alembic.
revision = "e4e49ffce091"
down_revision = "9e3158957fd7"
branch_labels = None
depends_on = None
table = User.__table__
def upgrade():
try:
op.add_column(table.name, table.c.HideDeletedComments)
except OperationalError:
print(
f"Column HideDeletedComments already exists in '{table.name}',"
f" skipping migration."
)
def downgrade():
op.drop_column(table.name, "HideDeletedComments")

View file

@ -1402,6 +1402,10 @@ msgstr ""
msgid "Specify multiple SSH Keys separated by new line, empty lines are ignored." msgid "Specify multiple SSH Keys separated by new line, empty lines are ignored."
msgstr "" msgstr ""
#: templates/partials/account_form.html
msgid "Hide deleted comments"
msgstr ""
#: template/account_edit_form.php #: template/account_edit_form.php
msgid "SSH Public Key" msgid "SSH Public Key"
msgstr "" msgstr ""

View file

@ -4,7 +4,7 @@
{% endif %} {% endif %}
{% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %}
{% if not (request.user.HideDeletedComments and comment.DelTS) %}
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %} {% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
<h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}"> <h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}">
{{ {{
@ -38,3 +38,4 @@
{% include "partials/comment_content.html" %} {% include "partials/comment_content.html" %}
{% endif %} {% endif %}
{% endif %}

View file

@ -182,7 +182,7 @@
maxlength="50" name="K" value="{{ pgp }}"> maxlength="50" name="K" value="{{ pgp }}">
</p> </p>
<!-- Homepage --> <!-- Language -->
<p> <p>
<label for="id_language"> <label for="id_language">
{% trans %}Language{% endtrans %}: {% trans %}Language{% endtrans %}:
@ -202,10 +202,10 @@
</select> </select>
</p> </p>
<!-- Homepage --> <!-- Timezone -->
<p> <p>
<label for="id_timezone"> <label for="id_timezone">
{% trans %}Timezone{% endtrans %} {% trans %}Timezone{% endtrans %}:
</label> </label>
<select id="id_timezone" name="TZ"> <select id="id_timezone" name="TZ">
@ -219,6 +219,19 @@
</select> </select>
</p> </p>
<!-- Hide Deleted Comments -->
<p>
<label for="id_hidedeletedcomments">
{% trans %}Hide deleted comments{% endtrans %}:
</label>
<input id="id_hidedeletedcomments" type="checkbox" name="HDC"
{% if hdc %}
checked="checked"
{% endif %}
>
</p>
</fieldset> </fieldset>
{% if form_type == "UpdateAccount" %} {% if form_type == "UpdateAccount" %}

View file

@ -6,6 +6,7 @@
{% endif %} {% endif %}
{% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %}
{% if not (request.user.HideDeletedComments and comment.DelTS) %}
<h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}"> <h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}">
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %} {% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
{% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %} {% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %}
@ -41,3 +42,4 @@
{% include "partials/comment_content.html" %} {% include "partials/comment_content.html" %}
{% endif %} {% endif %}
{% endif %}

View file

@ -122,6 +122,22 @@ def tu_user():
yield tu_user yield tu_user
@pytest.fixture
def user_who_hates_grey_comments() -> User:
"""Yield a specific User who doesn't like grey comments."""
account_type = db.query(AccountType, AccountType.ID == USER_ID).first()
with db.begin():
user_who_hates_grey_comments = db.create(
User,
Username="test_hater",
Email="test_hater@example.org",
Passwd="testPassword",
AccountType=account_type,
HideDeletedComments=True,
)
yield user_who_hates_grey_comments
@pytest.fixture @pytest.fixture
def package(maintainer: User) -> Package: def package(maintainer: User) -> Package:
"""Yield a Package created by user.""" """Yield a Package created by user."""
@ -193,6 +209,23 @@ def comment(user: User, package: Package) -> PackageComment:
yield comment yield comment
@pytest.fixture
def deleted_comment(user: User, package: Package) -> PackageComment:
pkgbase = package.PackageBase
now = time.utcnow()
with db.begin():
comment = db.create(
PackageComment,
User=user,
PackageBase=pkgbase,
Comments="Test comment.",
RenderedComment=str(),
CommentTS=now,
DelTS=now,
)
yield comment
@pytest.fixture @pytest.fixture
def packages(maintainer: User) -> list[Package]: def packages(maintainer: User) -> list[Package]:
"""Yield 55 packages named pkg_0 .. pkg_54.""" """Yield 55 packages named pkg_0 .. pkg_54."""
@ -409,7 +442,9 @@ def test_paged_depends_required(client: TestClient, package: Package):
assert "Show 6 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, user_who_hates_grey_comments: User, package: Package
):
now = time.utcnow() now = time.utcnow()
with db.begin(): with db.begin():
comment = db.create( comment = db.create(
@ -419,6 +454,14 @@ def test_package_comments(client: TestClient, user: User, package: Package):
Comments="Test comment", Comments="Test comment",
CommentTS=now, CommentTS=now,
) )
deleted_comment = db.create(
PackageComment,
PackageBase=package.PackageBase,
User=user,
Comments="Deleted Test comment",
CommentTS=now,
DelTS=now - 1,
)
cookies = {"AURSID": user.login(Request(), "testPassword")} cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request: with client as request:
@ -426,12 +469,29 @@ def test_package_comments(client: TestClient, user: User, package: Package):
resp = request.get(package_endpoint(package)) resp = request.get(package_endpoint(package))
assert resp.status_code == int(HTTPStatus.OK) assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text)
expected = [comment.Comments, deleted_comment.Comments]
comments = root.xpath(
'.//div[contains(@class, "package-comments")]'
'/div[@class="article-content"]/div/text()'
)
assert len(comments) == 2
for i, row in enumerate(expected):
assert comments[i].strip() == row
cookies = {"AURSID": user_who_hates_grey_comments.login(Request(), "testPassword")}
with client as request:
request.cookies = cookies
resp = request.get(package_endpoint(package))
assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text) root = parse_root(resp.text)
expected = [comment.Comments] expected = [comment.Comments]
comments = root.xpath( comments = root.xpath(
'.//div[contains(@class, "package-comments")]' './/div[contains(@class, "package-comments")]'
'/div[@class="article-content"]/div/text()' '/div[@class="article-content"]/div/text()'
) )
assert len(comments) == 1 # Deleted comment is hidden
for i, row in enumerate(expected): for i, row in enumerate(expected):
assert comments[i].strip() == row assert comments[i].strip() == row