Skip to content

Add Admin API endpoints to list, fetch and delete room reports#19648

Open
H-Shay wants to merge 24 commits into
element-hq:developfrom
H-Shay:shay/room_report_endpoint
Open

Add Admin API endpoints to list, fetch and delete room reports#19648
H-Shay wants to merge 24 commits into
element-hq:developfrom
H-Shay:shay/room_report_endpoint

Conversation

@H-Shay

@H-Shay H-Shay commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Adds Admin API endpoints to manage room reports. Follow on from #17270, which added the endpoints to report rooms.

@H-Shay H-Shay requested a review from a team as a code owner April 3, 2026 22:28
@H-Shay H-Shay changed the title Add admin endpoints to list, fetch and delete room reports Add Admin API endpoints to list, fetch and delete room reports Apr 3, 2026
@MadLittleMods MadLittleMods added A-Admin-API A-Abuse Reports, media quarantine, policy servers, etc labels Apr 7, 2026
@H-Shay

H-Shay commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

test failure looks to be flake

@MadLittleMods MadLittleMods left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not a full review

Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/rest/admin/room_reports.py
@H-Shay H-Shay requested a review from MadLittleMods May 18, 2026 18:47
Comment thread docs/admin_api/room_reports.md Outdated
Comment thread docs/admin_api/room_reports.md Outdated
Comment thread synapse/rest/admin/room_reports.py
Comment thread synapse/rest/admin/room_reports.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
@H-Shay H-Shay requested a review from MadLittleMods May 26, 2026 21:42
Comment thread docs/admin_api/room_reports.md Outdated
Comment thread synapse/rest/admin/room_reports.py
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread docs/admin_api/room_reports.md Outdated
}
],
"next_batch": 2,
"total": 4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should consider if total is worth returning (probably not) given the COUNT(*) will have to scan the whole table.

I'm guessing this was done to match the event reports endpoint (introduced in matrix-org/synapse#8217).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's useful to know the total number of reports associated with a given room or user id. The speed of the query is much less of a concern.

@MadLittleMods MadLittleMods Jun 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's useful (same reason offset pagination is useful) but it paints ourselves into a corner. At some scale, this gets too slow to use (on top of the resource strain on the database).

See https://wiki.postgresql.org/wiki/Count_estimate

In this case, we can get our own count estimate by just looking at the stream id. Overall, I think we should just skip. If it's something necessary, it can be introduced in its own PR with its own justifications and analysis.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looking at the id does not give the number of reports a given user has submitted or are against a given room, which the current count does (when provided the relevant filter). These are both signals that are actively used in production (for events, but we would use them for rooms as soon as this endpoint is available) so I guess this needs to be moved to another PR if it's an absolute blocker for this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in a935f33

Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
@H-Shay H-Shay requested a review from MadLittleMods June 1, 2026 22:02
Comment thread synapse/storage/databases/main/room.py Outdated
Comment thread synapse/storage/databases/main/room.py Outdated
"get_event_reports_paginate", _get_event_reports_paginate_txn
)

async def delete_room_report(self, report_id: int) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unused

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in bbf7ad9

Comment thread docs/admin_api/room_reports.md Outdated
Comment thread changelog.d/19648.feature Outdated
Comment thread docs/admin_api/room_reports.md Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
self.assertEqual(400, channel.code, msg=channel.json_body)
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
self.assertEqual(
"The report_id parameter must be a string representing a positive integer.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
"The report_id parameter must be a string representing a positive integer.",
"The report_id parameter must be a string representing a room report ID (positive integer).",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ohh, this is checking the human readable error. I thought this was the assertion message when this failed.

We should shy away from asserting human readable messages as they change over time, can be translated, etc.

Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment on lines +389 to +390
# first created room report gets `id`=2
self.url = "/_synapse/admin/v1/room_reports/2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# first created room report gets `id`=2
self.url = "/_synapse/admin/v1/room_reports/2"
# first created room report gets `id`=2
self.fetch_report_url = "/_synapse/admin/v1/room_reports/2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updated ✅

@H-Shay

H-Shay commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Test failure is surprising but not related to this PR AFAICT. The test changes are split over two commits so I am not going to link them in each comment, but everything else should have a response.

@H-Shay H-Shay requested a review from MadLittleMods June 3, 2026 21:37
) -> tuple[int, JsonDict]:
await assert_requester_is_admin(self._auth, request)

message = "The report_id parameter must be a string representing a room report ID (positive integer)."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might as well inline this or at-least move this to where its used

Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread tests/rest/admin/test_room_reports.py
Comment thread tests/rest/admin/test_room_reports.py Outdated
Comment thread tests/rest/admin/test_room_reports.py Outdated
)
self.assertEqual(200, channel.code, msg=channel.json_body)

def _check_fields(self, content: list[JsonDict]) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like there is an update but it's not quite what I had in mind.

I'd expect to just match the report ID's in order.

And then _check_expected_room_report_fields(...) would be like the previous _check_fields(...) which checked the shape.

And we would have:

    def _check_expected_room_reports_response(self, response_json: JsonDict) -> None:
        for room_report in todo:
            _check_expected_room_report_fields(room_report)

self._check_expected_room_report_fields(
channel.json_body["room_reports"], [self.room_reports[3]]
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sanity check reports returned are for the room

self.assertEqual(200, channel.code, msg=channel.json_body)
second_page = channel.json_body["room_reports"]
self.assertEqual(len(second_page), 5)
self.assertNotIn("next_batch", channel.json_body)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
self.assertNotIn("next_batch", channel.json_body)
# Nothing more to paginate
self.assertNotIn("next_batch", channel.json_body)

shorthand=False,
)
self.assertEqual(200, channel.code, msg=channel.json_body)
# there is no endpoint to get the id of a room report, so we fetch it from the db

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a bit unfortunate. I assumed that submitting a report would return some sort of ID.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +217 to +222
self.hs.get_datastores().main.db_pool.simple_select_one(
table="room_reports",
keyvalues={"room_id": room_id},
retcols=("id",),
allow_none=False,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks sketchy as it doesn't define a ORDER.

If sorting is not chosen, the rows will be returned in an unspecified order

-- https://www.postgresql.org/docs/current/queries-order.html

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

is this a concern if there are no duplicate rows? each room_id reported is unique, and the underlying transaction raises an error if more than one row matches, eg:

raise StoreError(500, "More than one row matched (%s)" % (table,))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, you can have multiple reports per room and per user.

We should have a query that makes sure we grab the latest report from the given user/room.

self.hs.get_datastores().main.db_pool.simple_delete(
"room_stats_state",
{"room_id": self.room_reports[1]["room_id"]},
desc="_",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can have a good description like test_delete_room_from_room_stats_state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Abuse Reports, media quarantine, policy servers, etc A-Admin-API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants