Prevent deletion of other structured data when editing depicts (#5741)

* restructure :  minor changes to comments to improve readability

* api: remove clear flag to prevent deletion of structured data

* WikiBaseInterface: add new api methods

Get Method: to get claims for an entity
Post method: to delete claims

* WikiBaseClient: add methods to handle response for new APIs

* typo:  update call to method with updated typo

* DepictEditHelper: call update property method with entity id

* refactor: dismiss progress dialog on error

* DepictsDao: remove usage of runBlocking as it was blocking main thread

Refactor methods to perform well with coroutines

* refactor: update usage of method to match changes in DepictsDao

* refactor: use named parameters to improve readability

* claims: add new data classes to represent remove claims

* WikidataEditService: modify update depicts property method

Performs deletion of old claims and creation of new claims

* refactor: make methods more organized
This commit is contained in:
Rohit Verma 2024-08-26 12:13:50 +05:30 committed by GitHub
parent 31bb1a73c0
commit 46df64d208
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 188 additions and 124 deletions

View file

@ -8,7 +8,6 @@ import fr.free.nrw.commons.upload.WikiBaseInterface
import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
@ -42,12 +41,29 @@ class WikiBaseClient @Inject constructor(
}
}
fun getClaimIdsByProperty(fileEntityId: String, property: String ): Observable<List<String>> {
return wikiBaseInterface.getClaimsByProperty(fileEntityId, property).map { claimsResponse ->
claimsResponse.claims[property]?.mapNotNull { claim -> claim.id } ?: emptyList()
}
}
fun postDeleteClaims(entityId: String, data: String): Observable<Boolean> {
return csrfToken().switchMap { editToken ->
wikiBaseInterface.postDeleteClaims(editToken, entityId, data)
.map { response: MwPostResponse -> response.successVal == 1 }
}
}
fun getFileEntityId(uploadResult: UploadResult): Observable<Long> {
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
.map { response: MwQueryResponse -> response.query()!!.pages()!![0].pageId().toLong() }
}
fun addLabelstoWikidata(fileEntityId: Long, languageCode: String?, captionValue: String?): Observable<MwPostResponse> {
fun addLabelsToWikidata(
fileEntityId: Long,
languageCode: String?,
captionValue: String?
): Observable<MwPostResponse> {
return csrfToken().switchMap { editToken ->
wikiBaseInterface.addLabelstoWikidata(
PAGE_ID_PREFIX + fileEntityId,

View file

@ -8,7 +8,6 @@ import android.content.Context;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.UploadResult;
@ -19,9 +18,11 @@ import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.model.DataValue;
import fr.free.nrw.commons.wikidata.model.DataValue.ValueString;
import fr.free.nrw.commons.wikidata.model.EditClaim;
import fr.free.nrw.commons.wikidata.model.RemoveClaim;
import fr.free.nrw.commons.wikidata.model.Snak_partial;
import fr.free.nrw.commons.wikidata.model.Statement_partial;
import fr.free.nrw.commons.wikidata.model.WikiBaseMonolingualTextValue;
import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
@ -34,7 +35,6 @@ import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse;
import timber.log.Timber;
/**
@ -72,9 +72,10 @@ public class WikidataEditService {
* to the wikibase API to set tag against the entity.
*/
@SuppressLint("CheckResult")
private Observable<Boolean> addDepictsProperty(final String fileEntityId,
final List<String> depictedItems) {
private Observable<Boolean> addDepictsProperty(
final String fileEntityId,
final List<String> depictedItems
) {
final EditClaim data = editClaim(
ConfigUtils.isBetaFlavour() ? Collections.singletonList("Q10")
// Wikipedia:Sandbox (Q10)
@ -100,45 +101,54 @@ public class WikidataEditService {
* Takes depicts ID as a parameter and create a uploadable data with the Id
* and send the data for POST operation
*
* @param filename name of the file
* @param depictedItems ID of the selected depict item
* @param fileEntityId ID of the file
* @param depictedItems IDs of the selected depict item
* @return Observable<Boolean>
*/
@SuppressLint("CheckResult")
public Observable<Boolean> updateDepictsProperty(final String filename,
final List<String> depictedItems) {
public Observable<Boolean> updateDepictsProperty(
final String fileEntityId,
final List<String> depictedItems
) {
final String entityId = PAGE_ID_PREFIX + fileEntityId;
final List<String> claimIds = getDepictionsClaimIds(entityId);
final EditClaim data = editClaim(
final RemoveClaim data = removeClaim( /* Please consider removeClaim scenario for BetaDebug */
ConfigUtils.isBetaFlavour() ? Collections.singletonList("Q10")
// Wikipedia:Sandbox (Q10)
: depictedItems
: claimIds
);
return wikiBaseClient.postEditEntityByFilename(filename,
gson.toJson(data))
.doOnNext(success -> {
if (success) {
Timber.d("DEPICTS property was set successfully for %s", filename);
} else {
Timber.d("Unable to set DEPICTS property for %s", filename);
}
})
return wikiBaseClient.postDeleteClaims(entityId, gson.toJson(data))
.doOnError(throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
Observable.error(throwable);
Timber.e(throwable, "Error occurred while removing existing claims for DEPICTS property");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
}).switchMap(success-> {
if(success) {
Timber.d("DEPICTS property was deleted successfully");
return addDepictsProperty(fileEntityId, depictedItems);
} else {
Timber.e(throwable, "Error occurred while setting DEPICTS property");
ViewUtil.showLongToast(context, throwable.toString());
Timber.d("Unable to delete DEPICTS property");
return Observable.empty();
}
});
}
})
.subscribeOn(Schedulers.io());
@SuppressLint("CheckResult")
private List<String> getDepictionsClaimIds(final String entityId) {
return wikiBaseClient.getClaimIdsByProperty(entityId, WikidataProperties.DEPICTS.getPropertyName())
.subscribeOn(Schedulers.io())
.blockingFirst();
}
private EditClaim editClaim(final List<String> entityIds) {
return EditClaim.from(entityIds, WikidataProperties.DEPICTS.getPropertyName());
}
private RemoveClaim removeClaim(final List<String> claimIds) {
return RemoveClaim.from(claimIds);
}
/**
* Show a success toast when the edit is made successfully
*/
@ -156,11 +166,10 @@ public class WikidataEditService {
* @param fileEntityId
* @return
*/
@SuppressLint("CheckResult")
private Observable<Boolean> addCaption(final long fileEntityId, final String languageCode,
final String captionValue) {
return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue)
return wikiBaseClient.addLabelsToWikidata(fileEntityId, languageCode, captionValue)
.doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse))
.doOnError(throwable -> {
Timber.e(throwable, "Error occurred while setting Captions");
@ -220,8 +229,10 @@ public class WikidataEditService {
}
}
public Observable addDepictionsAndCaptions(final UploadResult uploadResult,
final Contribution contribution) {
public Observable<Boolean> addDepictionsAndCaptions(
final UploadResult uploadResult,
final Contribution contribution
) {
return wikiBaseClient.getFileEntityId(uploadResult)
.doOnError(throwable -> {
Timber

View file

@ -11,19 +11,19 @@ data class EditClaim(val claims: List<Statement_partial>) {
entityIds.forEach {
list.add(
Statement_partial(
Snak_partial(
"value",
propertyName,
DataValue.EntityId(
mainSnak = Snak_partial(
snakType = "value",
property = propertyName,
dataValue = DataValue.EntityId(
WikiBaseEntityValue(
"item",
it,
it.removePrefix("Q").toLong()
entityType = "item",
id = it,
numericId = it.removePrefix("Q").toLong()
)
)
),
"statement",
"preferred"
type = "statement",
rank = "preferred"
)
)
}

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.wikidata.model
data class RemoveClaim(val claims: List<ClaimRemoveRequest>) {
companion object {
@JvmStatic
fun from(claimIds: List<String>): RemoveClaim {
val claimsToRemove = mutableListOf<ClaimRemoveRequest>()
claimIds.forEach {
claimsToRemove.add(
ClaimRemoveRequest(id = it, remove = "")
)
}
return RemoveClaim(claimsToRemove)
}
}
}
data class ClaimRemoveRequest(val id: String, val remove: String)

View file

@ -5,15 +5,15 @@ import com.google.gson.annotations.SerializedName
/*"mainsnak": {
"snaktype": "value",
"property": "P17",
"datatype": "wikibase-item",
"datavalue": {
"value": {
"entity-type": "item",
"id": "Q30",
"numeric-id": 30
},
"entity-type": "item",
"numeric-id": 30,
"id": "Q30"
},
"type": "wikibase-entityid"
}
},
"datatype": "wikibase-item",
}*/
data class Snak_partial(
@SerializedName("snaktype") val snakType: String,

View file

@ -3,9 +3,9 @@ package fr.free.nrw.commons.wikidata.model
import com.google.gson.annotations.SerializedName
/*{
"id": "q60$5083E43C-228B-4E3E-B82A-4CB20A22A3FB",
"mainsnak": {},
"type": "statement",
"id": "q60$5083E43C-228B-4E3E-B82A-4CB20A22A3FB",
"rank": "normal",
"qualifiers": {
"P580": [],