Skip to content

feat: "Report User" feature#7341

Open
sandrade-dcl wants to merge 47 commits intodevfrom
feat/add-report-user-option-in-user-context-menus
Open

feat: "Report User" feature#7341
sandrade-dcl wants to merge 47 commits intodevfrom
feat/add-report-user-option-in-user-context-menus

Conversation

@sandrade-dcl
Copy link
Contributor

@sandrade-dcl sandrade-dcl commented Mar 5, 2026

Pull Request Description

Fix #7299
Fix #7300
Fix #7479

What does this PR change?

It implements the Report User feature in the Explorer side:

  • Addition of the new "Report" option in all user context menus: This option allow the player to report any user, neing redirected to a Web Form where he will be able to send all the information to Decentraland.
image
  • Modification of the current "Blocked Screen" modal: Now this modal shows more information about why the player has been banned (ban period and reason).
image
  • New Ban Warning notification: The player will receive a notification informing about a possible banning if he continues violating the rules. Clicking on it, the user will be redirected to the support webpage.
image
  • New Banned notification: The player will receive a notification informing about he was banned. Clicking on it, the "Blocked Screen" modal will be opened.
image

Test Steps

Report an user

  1. Execute the build using these app args: --debug --report-user --dclenv zone.
  2. Open any user context menu (from the friends list, from the passport or from a community member) and check that you see the new option "Report".
  3. Click on the "Report" option and check that the new Reporting Web Form opens.

When your user is flagged

  1. Execute the build using these app args: --debug --report-user --dclenv zone.
  2. Once you are inside Decentraland, got o https://tools.decentraland.systems/user_moderation to send a ban warning to your address.
  3. Once he send you the warning, check that a new notification appears telling you that you have been flagged.
  4. Click in the notification and you should be redirected to the support webpage.

When your user is banned

  1. Execute the build using these app args: --debug --report-user --dclenv zone.
  2. Once you are inside Decentraland, go to https://tools.decentraland.systems/user_moderation to ban your address.
  3. Once he marks you as banned, check that a new notification appears telling you that you have been banned.
  4. Click in the notification and the "Blocked Screen" modal should be opened.
  5. Exit the app and enter again. Check that the "Blocked Screen" modal appears right after you log in and it doesn't allow you to continue.

Check that the feature flag is working properly

  1. Execute the build without using the app arg --report-user.
  2. Check that the new "Report" option doesn't appear in any user context menu.

Code Review Reference

Please review our Code Review Standards before submitting.

@sandrade-dcl sandrade-dcl self-assigned this Mar 5, 2026
@sandrade-dcl sandrade-dcl added the shape-up It has been shaped and is awaiting the cycle. label Mar 5, 2026
@sandrade-dcl sandrade-dcl changed the title feat: [Report User] Add new context menu for reporting users feat: [Report User] Add new option in the user's context menus for reporting users Mar 5, 2026
@claude
Copy link
Contributor

claude bot commented Mar 13, 2026

Claude finished @sandrade-dcl's task in 5m 5s —— View job


PR Review — feat: "Report User" feature

  • Gather context and read changed files
  • Analyze code quality, bugs, performance issues
  • Post inline comments for specific issues
  • Post final review summary

Summary

The implementation is well-structured overall. The feature flag gating, URL formatting, and confirmation dialog flow are clean. Two issues require attention:


Issues Found

1. Missing exception handling — BannedNotificationHandler.cs:57

The FetchBanStatusAndShowBlockedScreenAsync UniTaskVoid local function has no try/catch. A thrown OperationCanceledException or any other exception from mvcManager.ShowAsync(...) will go unhandled. Per CLAUDE.md: "Always catch exceptions: Ignore OperationCanceledException, Log/report all others via ReportHub.LogException."

See inline comment for the suggested fix. Fix this →

2. Empty "Reason: " label when reason is null — BlockedScreenController.cs:46 ⚠️

The reason field is appended whenever expiresAt is non-empty, but reason can be null/empty. This results in the UI displaying "Reason: " with no value. Guard with a null/empty check.

See inline comment for the suggested fix. Fix this →


REVIEW_RESULT: FAIL ❌

sandrade-dcl and others added 3 commits March 13, 2026 12:09
…rd/BannedNotificationHandler.cs

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Signed-off-by: Santi Andrade <sandrade@decentraland.org>
@sandrade-dcl
Copy link
Contributor Author

@claude re-review

@claude
Copy link
Contributor

claude bot commented Mar 13, 2026

Claude finished @sandrade-dcl's task in 1m 55s —— View job


PR Re-Review — feat: "Report User" feature

  • Gather context from previous review comments
  • Check if previously raised issues have been addressed
  • Post re-review summary

Previously Raised Issues — Status

Issue File Status
Duplicate webBrowser.OpenUrl call CommunitiesBrowserController.cs ✅ Fixed
Missing Dispose() unsubscribe BannedNotificationHandler.cs:46 ✅ Author confirmed not needed
Missing exception handling in UniTaskVoid BannedNotificationHandler.cs:58 ⚠️ Partially addressed — see below
Empty "Reason: " label when reason is null BlockedScreenController.cs:46 ✅ Author confirmed backend always sends a value

New Blocking Issue Found

Compilation error: dangling block in BannedNotificationHandler.cs

The try-catch fix was applied inside FetchBanStatusAndShowBlockedScreenAsync (lines 60–74) — which is correct. However, the original function body was left behind as a bare block at lines 75–85 inside OnBannedNotificationClicked. This dangling block:

  1. References ct — a parameter of the local function, not in scope in the outer method.
  2. Uses await in a non-async method (private void OnBannedNotificationClicked).

This will not compile. The fix is to delete lines 75–85 (the dangling block).

See inline comment for the exact change. Fix this →


REVIEW_RESULT: FAIL ❌

@sandrade-dcl
Copy link
Contributor Author

@claude all suggested changes applied!

@sandrade-dcl
Copy link
Contributor Author

@claude re-review

@claude
Copy link
Contributor

claude bot commented Mar 13, 2026

Claude finished @sandrade-dcl's task in 2m 39s —— View job


PR Re-Review — feat: "Report User" feature

  • Gather context from previous review comments
  • Verify previously raised issues are resolved
  • Post re-review summary

Previously Raised Issues — Status

Issue File Status
Missing exception handling in UniTaskVoid BannedNotificationHandler.cs:58 ✅ Fixed — proper try-catch added
Dangling block with out-of-scope ct and illegal await BannedNotificationHandler.cs ✅ Fixed — dangling block removed
Duplicate webBrowser.OpenUrl call CommunitiesBrowserController.cs ✅ Fixed (from prior review)
Empty "Reason: " label when reason is null BlockedScreenController.cs:46 ✅ Accepted — author confirmed backend always sends a value

New Blocking Issue Found

Missing exception handling in all ReportUser async flows

Every new ShowReportConfirmationDialogAsync / OpenReportUserFormAsync local function is async UniTask called with .Forget() and has no try-catch. If selfProfile.ProfileAsync(ct) throws, the exception is silently swallowed. Per CLAUDE.md: "Always catch exceptions: Ignore OperationCanceledException, Log/report all others via ReportHub.LogException."

Affected locations (all the same fix — wrap the body in try/catch):

File Method Line
PassportController.cs ShowReportConfirmationDialogAsync 962
RequestsSectionController.cs ShowReportConfirmationDialogAsync 122
MembersListController.cs ShowReportConfirmationDialogAsync 257
GenericUserProfileContextMenuController.cs ShowReportConfirmationDialogAsync 393
CommunitiesBrowserController.cs OpenReportUserFormAsync 925

See the inline comment on PassportController.cs for the suggested fix pattern. Apply the same try-catch to all five locations. Fix this →


REVIEW_RESULT: FAIL ❌

@sandrade-dcl
Copy link
Contributor Author

@claude re-review

@claude
Copy link
Contributor

claude bot commented Mar 13, 2026

Claude finished @sandrade-dcl's task in 1m 3s —— View job


PR Re-Review — feat: "Report User" feature

  • Gather context from previous review comments
  • Verify previously raised issues are resolved
  • Post re-review summary

Previously Raised Issues — Status

Issue File Status
Missing exception handling in UniTaskVoid BannedNotificationHandler.cs ✅ Fixed
Dangling block with out-of-scope ct BannedNotificationHandler.cs ✅ Fixed
Missing exception handling in ReportUser flows PassportController.cs ✅ Fixed
Missing exception handling in ReportUser flows RequestsSectionController.cs ✅ Fixed
Missing exception handling in ReportUser flows GenericUserProfileContextMenuController.cs ✅ Fixed
Missing exception handling in ReportUser flows CommunitiesBrowserController.cs ✅ Fixed
Missing exception handling in ReportUser flows MembersListController.cs ❌ Still missing — see below

Remaining Issue

MembersListController.csShowReportConfirmationDialogAsync has no try-catch

The fix was applied to all four other ShowReportConfirmationDialogAsync / OpenReportUserFormAsync methods, but the one in MembersListController.cs (lines 257–274) was missed. It is an async UniTask called with .Forget(), and any thrown exception from ReportUserConfirmationDialog.ShowAsync or selfProfile.ProfileAsync will be silently swallowed.

See inline comment for the exact fix. Fix this →


REVIEW_RESULT: FAIL ❌

@sandrade-dcl
Copy link
Contributor Author

@claude re-review

@claude
Copy link
Contributor

claude bot commented Mar 13, 2026

Claude finished @sandrade-dcl's task in 32s —— View job


PR Re-Review — feat: "Report User" feature

  • Gather context from previous review comments
  • Verify previously raised issues are resolved
  • Post re-review summary

Previously Raised Issues — Status

Issue File Status
Missing exception handling in UniTaskVoid BannedNotificationHandler.cs ✅ Fixed
Dangling block with out-of-scope ct and illegal await BannedNotificationHandler.cs ✅ Fixed
Duplicate webBrowser.OpenUrl call CommunitiesBrowserController.cs ✅ Fixed
Empty "Reason: " label when reason is null BlockedScreenController.cs ✅ Accepted (backend always sends a value)
Missing exception handling in ReportUser flows PassportController.cs ✅ Fixed
Missing exception handling in ReportUser flows RequestsSectionController.cs ✅ Fixed
Missing exception handling in ReportUser flows GenericUserProfileContextMenuController.cs ✅ Fixed
Missing exception handling in ReportUser flows CommunitiesBrowserController.cs ✅ Fixed
Missing exception handling in ReportUser flows MembersListController.cs ✅ Fixed

All previously raised issues have been addressed. No new blocking issues found.

REVIEW_RESULT: PASS ✅

@MauroCicerchia MauroCicerchia mentioned this pull request Mar 17, 2026
Copy link

@decentraland-bot decentraland-bot left a comment

Choose a reason for hiding this comment

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

Code Review: Report User Feature

Overall

This is a substantial feature adding user reporting, ban notifications, and an enhanced blocked screen across multiple surfaces (passport, friends list, communities, context menus). The feature is properly gated behind the REPORT_USER feature flag with a --report-user app arg override. The overall architecture is sound, but there are several issues worth addressing.


🔴 Issues

1. Duplicated report-user logic (DRY violation)
The "show confirmation dialog → fetch self profile → open web form" pattern is copy-pasted nearly identically across 5+ controllers:

  • PassportController.ReportUserClicked
  • GenericUserProfileContextMenuController.OnReportUserClicked
  • MembersListController.ReportUserClickedAsync
  • RequestsSectionController.ReportUserClicked
  • CommunitiesBrowserView.OnReportUser + CommunitiesBrowserController.OpenReportUserForm

Each one creates its own CancellationTokenSource, calls ReportUserConfirmationDialog.ShowAsync, fetches self profile, and formats the URL. This should be extracted into a shared helper (e.g. a static method on ReportUserConfirmationDialog or a dedicated ReportUserService) that takes IConfirmationDialogOpener, ISelfProfile, IWebBrowser, IDecentralandUrlsSource, a reported user ID, and a CancellationToken.

2. BannedNotificationHandler doesn't unsubscribe from notification bus
Dispose() only calls cts.SafeCancelAndDispose(), but never unsubscribes from NotificationsBusController.Instance.SubscribeToNotificationTypeClick. If the notification bus holds a strong reference to the handler's callbacks, this is a memory leak.

3. MembersListView.contextMenuSettings changed from private to public
The [field: SerializeField] property was changed from private to public just so MembersListController.ReportUserClickedAsync can access view.contextMenuSettings.ReportSprite. This breaks encapsulation. Consider exposing only the needed sprite via a read-only property instead.


🟡 Suggestions

4. Hardcoded color parsed at runtime in multiple places
ColorUtility.TryParseHtmlString("#FF2D55", out Color redColor) is called in at least 5 different spots (PassportController, GenericUserProfileContextMenuController, MembersListView, CommunityRequestsReceivedGroupView, RequestsSectionController). Consider defining a shared static readonly Color constant to avoid redundant parsing and ensure consistency.

5. Default color check in ButtonWithDelegateContextMenuControlSettings
Using textColor == default(Color) to detect whether a color was provided is fragile — default(Color) is (0,0,0,0) (transparent black), which could be a valid intentional value. A nullable Color? parameter would be safer, though this is a minor concern for current usage.

6. AnimatePersistentNotificationCanvasGroupAsyncDOFade in finally without cancellation token
In the finally block, notificationCanvasGroup.DOFade(0, ANIMATION_DURATION).ToUniTask() runs without a cancellation token. If the notification system is being torn down, this tween could run against a destroyed object. Consider guarding this or passing a fallback token.

7. Inconsistent confirmation dialog ownership
Most surfaces handle the confirmation dialog in the controller layer, but CommunitiesBrowserView.OnReportUser handles it in the view layer and then invokes ReportUserRequested to the controller for the URL open. This architectural inconsistency makes the flow harder to follow. (Would be naturally solved by extracting the shared helper from point #1.)


✅ What looks good

  • Feature flag integration is clean — FeatureId.REPORT_USER with both FF and app arg support.
  • ModerationDataProvider cleanly encapsulates the new API call using signed fetch.
  • BlockedScreenController.FormatRemainingBanTime handles edge cases well (expired bans, hour/day formatting).
  • Proper CTS lifecycle management throughout (SafeRestart, SafeCancelAndDispose on close/dispose).
  • GetBanStatusData gracefully falls back to isBanned = false on API failure during startup.
  • Notification types (BAN_WARNING, BANNED, BAN_LIFTED) are well-structured with persistent display behavior.

⚠️ CI Status

  • Test (playmode): ❌ FAILURE
  • Test (editmode): ❌ CANCELLED
  • Build: 🔄 IN PROGRESS

The playmode test failure needs investigation before merge.

📝 Missing Tests

No tests were added for the new code. At minimum, unit tests for FormatRemainingBanTime, ModerationDataProvider, and ReportUserConfirmationDialog would be valuable.


Requested by Santiago Andrade Lara via Slack

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

Labels

shape-up It has been shaped and is awaiting the cycle.

Projects

None yet

3 participants