Skip to content

Fix #3546: Notify all uploaders when nominating file for deletion#6729

Open
kushwaha-Khushi-0612 wants to merge 3 commits intocommons-app:mainfrom
kushwaha-Khushi-0612:fix-3546-notify-all-uploaders
Open

Fix #3546: Notify all uploaders when nominating file for deletion#6729
kushwaha-Khushi-0612 wants to merge 3 commits intocommons-app:mainfrom
kushwaha-Khushi-0612:fix-3546-notify-all-uploaders

Conversation

@kushwaha-Khushi-0612
Copy link

Fix for Issue #3546

Problem

When a file with multiple revisions is nominated for deletion, only the first uploader is notified on their talk page.

This happens because:

  • MediaClient.getImageInfoList() returns only the first ImageInfo object
  • DeleteHelper stops processing after the first emission

Solution

This PR ensures that all uploaders of a file revision receive a deletion notification.

Changes:

  • Added API call with iilimit=max to fetch all file revisions
  • Exposed full imageInfo list via MwQueryPage.imageInfoList()
  • Updated MediaClient.getImageInfoList() to return all revisions
  • Modified deletion logic to notify all unique uploaders
  • Added unit test verifying multiple uploaders are notified

Testing

Added unit test makeDeletionNotifiesAllUploaders to ensure notifications are sent to all uploaders.

Result

All users who uploaded a revision of a file now receive deletion notifications.


deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
}

Choose a reason for hiding this comment

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

This is the test which i added for furture also for testing this notification thing.

@github-actions
Copy link

✅ Generated APK variants!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the deletion nomination flow to notify all uploaders of a file (instead of only a single creator), by fetching uploader info from file revision history via the MediaWiki imageinfo API.

Changes:

  • Add an API endpoint and client helper to fetch a file’s imageinfo revision list (uploaders/timestamps).
  • Update DeleteHelper to notify each distinct uploader’s talk page after a successful nomination flow.
  • Extend DeleteHelperTest with mocks and a new test covering multiple uploader notifications.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt Injects MediaClient and changes nomination flow to notify all uploaders’ talk pages.
app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt Adds a MediaWiki API endpoint intended to fetch all file revisions (imageinfo).
app/src/main/java/fr/free/nrw/commons/media/MediaClient.kt Adds getImageInfoList() wrapper to extract ImageInfo entries from the API response.
app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwQueryPage.kt Adds imageInfoList() accessor returning all imageinfo items from the page.
app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt Updates tests for new DeleteHelper dependency and adds multi-uploader notification test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +158 to +170
mediaClient.getImageInfoList(media.filename!!)
.toObservable()
.flatMap { imageInfos ->

val uploaders = imageInfos
.map { it.getUser() }
.filter { it.isNotEmpty() }
.toSet()

if (uploaders.isEmpty()) {
// No uploaders found, still return success
return@flatMap Observable.just(true)
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

delete() now fetches uploaders via mediaClient.getImageInfoList(...), but any network/API error from that call will fail the whole deletion flow (even though the nomination edits already succeeded). Consider handling errors from getImageInfoList (e.g., fallback to notifying creator or treating uploader-fetch failure as non-fatal) so users don’t see a failed deletion due only to uploader lookup issues.

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +168

if (uploaders.isEmpty()) {
// No uploaders found, still return success
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

creator is still computed earlier in delete() (and can throw if null), but the new uploader-notification logic no longer uses it. This is now dead code and also unnecessarily blocks nominations when getAuthorOrUser() is missing. Either remove the creator lookup/exception, or use it as a fallback uploader when the imageinfo list is empty/unavailable.

Suggested change
if (uploaders.isEmpty()) {
// No uploaders found, still return success
.ifEmpty {
// Fallback to creator when no uploader is available from image info
creator?.takeIf { it.isNotEmpty() }?.let { setOf(it) } ?: emptySet()
}
if (uploaders.isEmpty()) {
// No uploaders (including creator) found, still return success

Copilot uses AI. Check for mistakes.

val uploaders = imageInfos
.map { it.getUser() }
.filter { it.isNotEmpty() }
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

When building uploaders, the filter uses isNotEmpty(), which will still keep usernames that are only whitespace. Using isNotBlank() (and/or trimming) would avoid attempting to write to invalid talk page titles.

Suggested change
.filter { it.isNotEmpty() }
.filter { it.isNotBlank() }

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +129
/**
* Fetches all file revisions (for getting all uploaders)
* Uses iilimit=max to get all imageInfo versions
*
* @param title the file name to get all revisions for
* @return
*/
@GET("w/api.php?action=query&format=json&formatversion=2&prop=imageinfo&iiprop=user|timestamp&iilimit=max")
fun getAllFileRevisions(
@Query("titles") title: String?,
): Single<MwQueryResponse>
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

getAllFileRevisions is documented as fetching “all file revisions”, but iilimit=max only returns the maximum per request and MediaWiki will still paginate via continue/iicontinue for files with many revisions. Either implement continuation support (loop until no continue) or rename/reword the API/docs to avoid implying it always returns all revisions.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +146
fun getImageInfoList(titles: String?): Single<List<ImageInfo>> =
mediaInterface.getAllFileRevisions(titles)
.map { response ->
response.query()?.pages()?.flatMap { page ->
page.imageInfoList()
} ?: emptyList()
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

getImageInfoList() currently makes a single getAllFileRevisions call and ignores any continue values in the MwQueryResponse, so it will silently return a partial revision list for files with lots of history. If the goal is “notify all uploaders”, this should either page through continuation tokens or make it explicit that the list may be truncated.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +59
* Get all imageInfo objects (all file revisions)
* Used for finding all uploaders when nominating for deletion
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

imageInfoList() is described as returning “all file revisions”, but it only returns whatever imageinfo entries were included in the API response (which may be paginated/truncated). Consider adjusting the KDoc to avoid implying completeness unless continuation is handled elsewhere.

Suggested change
* Get all imageInfo objects (all file revisions)
* Used for finding all uploaders when nominating for deletion
* Get the imageInfo objects (file revisions) included in this API response.
* Used for finding uploaders when nominating for deletion.
* Note: this may not include all file revisions if the API results are paginated.

Copilot uses AI. Check for mistakes.

val userPageString = "\n{{subst:idw|${media.filename}}} ~~~~"

val creator = media.getAuthorOrUser()
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about getting the list here itself and simplifying the logic? L169 in the diff seems to get the same value.

@RitikaPahwa4444
Copy link
Collaborator

@sivaraam could you please help test this PR or share the exact steps to reproduce this (or maybe an example image on Commons having multiple uploaders)? I'm not sure if cross-account revisions are common 🤔

@sivaraam
Copy link
Member

sivaraam commented Mar 18, 2026

@RitikaPahwa4444 it is likely better to test this in the beta cluster as there you could freely create multiple accounts and test by uploading multiple versions of a file and nominating the same. Anyways, an example version of the file that has multiple uploaders (see "File history" section): https://commons.wikimedia.org/wiki/File:%E4%B8%AD%E8%8B%8F%E5%8F%8B%E8%B0%8A%E7%BA%AA%E5%BF%B5%E5%A1%94_2020.jpg

When you nominate that file, nomination request should go to both User:Finavon and User:MintCandy user pages.

In Commons, cross-account revisions are not rare given the wiki's nature. So, it should be possible to find the same

@sivaraam
Copy link
Member

Alternatively, if prod is the only way to test this. You could also feel free to upload a new version to one of the following uploads of mine and nominate it for deletion (test deletion of course).

In prod, some more images with multiple author uploads can be found via this Quarry query: https://quarry.wmcloud.org/query/103353

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.

4 participants