Skip to content

Show hidden annotations to group moderators #9485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

marcospri
Copy link
Contributor

@marcospri marcospri commented Apr 17, 2025

This PR modifies the default annotation filters in /search to return hidden (moderated) annotations to group moderators.

This alters the current behaviour where those annotations are only visible to their authors.

In a previous step we have refactor these to extract NIPSA-ed annotation filter to its own class:

#9574

read or which are explicitly excluded by the search query.
"""

class GroupAndModerationFilter:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current pattern of a stateful list of modifiers that are independent of each other looks fine in for very simple cases but it becomes tricky when you need to combine them.

We already have some of this classes that are incompatible (or contradictory) with each other.

So while I think combining these two in one class goes against the current style of this code I reckon it moves in the right direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GroupFilter and Hidden filter are always used together.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resulting __call__() method below is pretty long and complicated, I think maybe because it's handling several separate cases in one method: when no user is logged in, when the user is logged in but there are no matching groups, and when the user is logged in and there are matching groups. Maybe it just needs to delegate to helper methods for each case?

Copy link
Contributor Author

@marcospri marcospri May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had another go at this after I understood it a bit better.

I reckon this is better now but let me know.

No groups

This means the current user can't read any groups (any groups in general or any of the requested groups).

We explicitly filter groups=[] to basically don't return any annotations.

This is the same behaviour as before but made explicit here for an early return.

If we just let this go to the for loop it wouldn't add any group filter.

No user

We just filter by group because we don't need to check by role in the group or authorship of the annos, just list the readable annos.

Moderator in a group

Return all annos in the group. Don't filter out hidden ones.

Nipsaed ones are now handled in a different filter class.

Not a moderator

Hidde moderated annos but include the ones authored by the user.

@@ -2,7 +2,7 @@

import elasticsearch_dsl
import pytest
import webob
from webob.multidict import MultiDict
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't resist to import this

@marcospri marcospri force-pushed the show-moderated-annos branch from e569e19 to 4be16bb Compare May 7, 2025 10:32
Comment on lines +9 to +12
from h.security.identity import Identity
from h.security.permissions import Permission
from h.security.permits import identity_permits
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a circular import issue while importing these directly from identity.

@@ -17,6 +16,8 @@ def identity_permits(
:param context: Context object representing the objects acted upon
:param permission: Permission requested
"""
from h.security.permission_map import PERMISSION_MAP
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking circular import in other modules here.

@@ -76,28 +77,26 @@ def filter_by_name(self, name=None):
.order_by(Group.created.desc())
)

def groupids_readable_by(self, user, group_ids=None):
def groups_readable_by(self, user: User | None, group_ids=None) -> list[Group]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return the full group objects instead of just the pubids.

user,
group_service,
identity_permits,
make_annotation,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't particularly like this type of fixture.

This is just a function that is itself parameterized based on on other fixtures.

So whether or not you use is_nipsaed and is_hidden that function is going to be called 4 times.

I'd much prefer to just have regular parameters in that function that I can chose to call with fixture values if I need to.

I prefer to have the rest of the code reviewed and then focus on a test refactor if needed.

@marcospri marcospri requested a review from seanh May 7, 2025 10:38
Copy link
Member

@seanh seanh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted a few questions about the search query filtering stuff and whether each of the different cases is handled correctly? I think it might be good to try and split the different cases into separate methods.

I can see why the group and hidden filters need to be combined: for each group we need to know whether the user is a moderator of that group in order to know whether to filter out hidden annotations. Makes sense 👍

I think there might be an opportunity to split out a separate NIPSAFilter, just to simplify GroupAndHiddenFilter a bit by not having to deal with NIPSA? I think NIPSA'd annotations are always hidden in all cases, if I'm not mistaken.

h/search/core.py Outdated
@@ -50,9 +50,8 @@ def __init__(
query.Limiter(),
query.DeletedFilter(),
query.AuthFilter(request),
query.GroupFilter(request),
query.GroupAndModerationFilter(request),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so GroupFilter and HiddenFilter are replaced with a single GroupAndModerationFilter

Comment on lines 221 to 233
not_nipsa = ~Q("term", nipsa=True)
not_hidden = ~Q("term", hidden=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so GroupAndHiddenFilter handles nipsa=True and hidden=True annotations, as well as groups 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not_nipsa always included in the returned result? Could it be moved to a separate NIPSAFilter that just returns search.filter(~Q("term", nipsa=True))? That might simplify this method by not having to worry about NIPSA.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did NIPSAFilter in a separate PR 👍

# If there not a logged in user
if groups:
# Apply the group filter as it is
search = search.filter("terms", group=[g.pubid for g in groups])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here groups would be the result of groups_readably_by(None, group_ids), so the groups that a not-logged-in user can read.

read or which are explicitly excluded by the search query.
"""

class GroupAndModerationFilter:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resulting __call__() method below is pretty long and complicated, I think maybe because it's handling several separate cases in one method: when no user is logged in, when the user is logged in but there are no matching groups, and when the user is logged in and there are matching groups. Maybe it just needs to delegate to helper methods for each case?

@marcospri marcospri force-pushed the show-moderated-annos branch from 4be16bb to 6149e3a Compare May 19, 2025 12:26
@marcospri marcospri changed the base branch from main to refactor-nipsa-filter May 19, 2025 12:26
@marcospri marcospri force-pushed the show-moderated-annos branch 2 times, most recently from 1a2aacc to 1966a84 Compare May 19, 2025 14:06
@marcospri marcospri force-pushed the refactor-nipsa-filter branch 4 times, most recently from 16aa7c5 to 6db6bdb Compare May 22, 2025 10:47
Base automatically changed from refactor-nipsa-filter to main May 22, 2025 10:56
@marcospri marcospri force-pushed the show-moderated-annos branch 2 times, most recently from cab33d1 to d4dbf37 Compare May 28, 2025 13:46
@marcospri marcospri self-assigned this May 30, 2025
@marcospri marcospri force-pushed the show-moderated-annos branch from d4dbf37 to 05d0ee7 Compare June 9, 2025 07:33
return search.filter("terms", group=[])

if not self.user:
return search.filter(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No logged in user, show all visible annos in the requested (& visible groups).

@marcospri marcospri marked this pull request as ready for review June 9, 2025 07:41
@marcospri marcospri requested a review from seanh June 9, 2025 07:41
@seanh seanh force-pushed the show-moderated-annos branch from 05d0ee7 to 4c5233f Compare June 17, 2025 15:24
@seanh
Copy link
Member

seanh commented Jun 17, 2025

Rebased..

@seanh seanh force-pushed the show-moderated-annos branch from 4c5233f to 4450ae1 Compare June 19, 2025 17:32
@seanh seanh marked this pull request as draft June 19, 2025 17:32
@seanh seanh self-assigned this Jun 19, 2025
@seanh seanh force-pushed the show-moderated-annos branch from 4450ae1 to 44a4ea6 Compare June 24, 2025 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants