mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
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:
parent
31bb1a73c0
commit
46df64d208
12 changed files with 188 additions and 124 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import fr.free.nrw.commons.upload.depicts.Claims
|
||||
import fr.free.nrw.commons.wikidata.WikidataConstants
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
|
||||
|
|
@ -34,7 +35,7 @@ interface WikiBaseInterface {
|
|||
</MwPostResponse> */
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@FormUrlEncoded
|
||||
@POST(WikidataConstants.MW_API_PREFIX + "action=wbeditentity&site=commonswiki&clear=1")
|
||||
@POST(WikidataConstants.MW_API_PREFIX + "action=wbeditentity&site=commonswiki")
|
||||
fun postEditEntityByFilename(
|
||||
@Field("title") filename: String,
|
||||
@Field("token") editToken: String,
|
||||
|
|
@ -59,4 +60,19 @@ interface WikiBaseInterface {
|
|||
@Field("language") language: String?,
|
||||
@Field("value") captionValue: String?
|
||||
): Observable<MwPostResponse>
|
||||
|
||||
@GET(WikidataConstants.MW_API_PREFIX + "action=wbgetclaims")
|
||||
fun getClaimsByProperty(
|
||||
@Query("entity") entityId: String,
|
||||
@Query("property") property: String
|
||||
) : Observable<Claims>
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@FormUrlEncoded
|
||||
@POST(WikidataConstants.MW_API_PREFIX + "action=wbeditentity")
|
||||
fun postDeleteClaims(
|
||||
@Field("token") editToken: String,
|
||||
@Field("id") entityId: String,
|
||||
@Field("data") data: String
|
||||
): Observable<MwPostResponse>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package fr.free.nrw.commons.upload.depicts
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import fr.free.nrw.commons.wikidata.model.Statement_partial
|
||||
|
||||
data class Claims(
|
||||
@SerializedName(value = "claims")
|
||||
val claims: Map<String, List<Statement_partial>> = emptyMap()
|
||||
)
|
||||
|
|
@ -69,7 +69,7 @@ class DepictEditHelper @Inject constructor (notificationHelper: NotificationHelp
|
|||
*/
|
||||
private fun addDepiction(media: Media, depictions: List<String>): Observable<Boolean> {
|
||||
Timber.d("thread is adding depiction %s", Thread.currentThread().name)
|
||||
return wikidataEditService.updateDepictsProperty(media.filename, depictions)
|
||||
return wikidataEditService.updateDepictsProperty(media.pageId, depictions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,112 +1,99 @@
|
|||
package fr.free.nrw.commons.upload.depicts
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Dao class for DepictsRoomDataBase
|
||||
*/
|
||||
@Dao
|
||||
abstract class DepictsDao {
|
||||
/** The maximum number of depicts allowed in the database. */
|
||||
private val maxItemsAllowed = 10
|
||||
|
||||
/**
|
||||
* insert Depicts in DepictsRoomDataBase
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract suspend fun insert(depictedItem: Depicts)
|
||||
|
||||
/**
|
||||
* get all Depicts from roomdatabase
|
||||
*/
|
||||
@Query("Select * From depicts_table order by lastUsed DESC")
|
||||
abstract suspend fun getAllDepict(): List<Depicts>
|
||||
abstract suspend fun getAllDepicts(): List<Depicts>
|
||||
|
||||
/**
|
||||
* get all Depicts which need to delete from roomdatabase
|
||||
*/
|
||||
@Query("Select * From depicts_table order by lastUsed DESC LIMIT :n OFFSET 10")
|
||||
abstract suspend fun getItemToDelete(n: Int): List<Depicts>
|
||||
abstract suspend fun getDepictsForDeletion(n: Int): List<Depicts>
|
||||
|
||||
/**
|
||||
* Delete Depicts from roomdatabase
|
||||
*/
|
||||
@Delete
|
||||
abstract suspend fun delete(depicts: Depicts)
|
||||
|
||||
lateinit var allDepict: List<Depicts>
|
||||
lateinit var listOfDelete: List<Depicts>
|
||||
|
||||
/**
|
||||
* get all depicts from DepictsRoomDatabase
|
||||
* Gets all Depicts objects from the database, ordered by lastUsed in descending order.
|
||||
*
|
||||
* @return A list of Depicts objects.
|
||||
*/
|
||||
fun depictsList(): List<Depicts> {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
allDepict = getAllDepict()
|
||||
}
|
||||
}
|
||||
return allDepict
|
||||
fun depictsList(): Deferred<List<Depicts>> = CoroutineScope(Dispatchers.IO).async {
|
||||
getAllDepicts()
|
||||
}
|
||||
|
||||
/**
|
||||
* insert Depicts in DepictsRoomDataBase
|
||||
* Inserts a Depicts object into the database.
|
||||
*
|
||||
* @param depictedItem The Depicts object to insert.
|
||||
*/
|
||||
fun insertDepict(depictes: Depicts) {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
insert(depictes)
|
||||
}
|
||||
}
|
||||
private fun insertDepict(depictedItem: Depicts) = CoroutineScope(Dispatchers.IO).launch {
|
||||
insert(depictedItem)
|
||||
}
|
||||
|
||||
/**
|
||||
* get all Depicts item which need to delete
|
||||
* Gets a list of Depicts objects that need to be deleted from the database.
|
||||
*
|
||||
* @param n The number of depicts to delete.
|
||||
* @return A list of Depicts objects to delete.
|
||||
*/
|
||||
fun getItemTodelete(number: Int): List<Depicts> {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
listOfDelete = getItemToDelete(number)
|
||||
}
|
||||
}
|
||||
return listOfDelete
|
||||
private suspend fun depictsForDeletion(n: Int): Deferred<List<Depicts>> = CoroutineScope(Dispatchers.IO).async {
|
||||
getDepictsForDeletion(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* delete Depicts in DepictsRoomDataBase
|
||||
* Deletes a Depicts object from the database.
|
||||
*
|
||||
* @param depicts The Depicts object to delete.
|
||||
*/
|
||||
fun deleteDepicts(depictes: Depicts) {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
delete(depictes)
|
||||
}
|
||||
}
|
||||
private suspend fun deleteDepicts(depicts: Depicts) = CoroutineScope(Dispatchers.IO).launch {
|
||||
delete(depicts)
|
||||
}
|
||||
|
||||
/**
|
||||
* save Depicts in DepictsRoomDataBase
|
||||
* Saves a list of DepictedItems in the DepictsRoomDataBase.
|
||||
*/
|
||||
fun savingDepictsInRoomDataBase(listDepictedItem: List<DepictedItem>) {
|
||||
var numberofItemInRoomDataBase: Int
|
||||
val maxNumberOfItemSaveInRoom = 10
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
for (depictsItem in listDepictedItem) {
|
||||
depictsItem.isSelected = false
|
||||
insertDepict(Depicts(depictsItem, Date()))
|
||||
}
|
||||
|
||||
for (depictsItem in listDepictedItem) {
|
||||
depictsItem.isSelected = false
|
||||
insertDepict(Depicts(depictsItem, Date()))
|
||||
// Deletes old Depicts objects from the database if
|
||||
// the number of depicts exceeds the maximum allowed.
|
||||
deleteOldDepictions(depictsList().await().size)
|
||||
}
|
||||
}
|
||||
|
||||
numberofItemInRoomDataBase = depictsList().size
|
||||
// delete the depictItem from depictsroomdataBase when number of element in depictsroomdataBase is greater than 10
|
||||
if (numberofItemInRoomDataBase > maxNumberOfItemSaveInRoom) {
|
||||
private suspend fun deleteOldDepictions(depictsListSize: Int) {
|
||||
if(depictsListSize > maxItemsAllowed) {
|
||||
val depictsForDeletion = depictsForDeletion(depictsListSize).await()
|
||||
|
||||
val listOfDepictsToDelete: List<Depicts> =
|
||||
getItemTodelete(numberofItemInRoomDataBase)
|
||||
for (i in listOfDepictsToDelete) {
|
||||
deleteDepicts(i)
|
||||
for(depicts in depictsForDeletion) {
|
||||
deleteDepicts(depicts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
|||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.*
|
||||
|
|
@ -223,9 +226,8 @@ class DepictsPresenter @Inject constructor(
|
|||
if (error is InvalidLoginTokenException) {
|
||||
view.navigateToLoginScreen();
|
||||
} else {
|
||||
Timber.e(
|
||||
"Failed to update depictions"
|
||||
)
|
||||
view.dismissProgressDialog()
|
||||
Timber.e("Failed to update depictions")
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -268,10 +270,13 @@ class DepictsPresenter @Inject constructor(
|
|||
*/
|
||||
fun getRecentDepictedItems(): MutableList<DepictedItem> {
|
||||
val depictedItemList: MutableList<DepictedItem> = ArrayList()
|
||||
val depictsList = depictsDao.depictsList()
|
||||
for (i in depictsList.indices) {
|
||||
val depictedItem = depictsList[i].item
|
||||
depictedItemList.add(depictedItem)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val depictsList = depictsDao.depictsList().await()
|
||||
|
||||
for (i in depictsList.indices) {
|
||||
val depictedItem = depictsList[i].item
|
||||
depictedItemList.add(depictedItem)
|
||||
}
|
||||
}
|
||||
return depictedItemList
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class WikiBaseClientUnitTest {
|
|||
"M123", "test", "en", "caption"
|
||||
)).thenReturn(Observable.just(mwPostResponse))
|
||||
|
||||
val result = wikiBaseClient.addLabelstoWikidata(123L, "en", "caption").blockingFirst()
|
||||
val result = wikiBaseClient.addLabelsToWikidata(123L, "en", "caption").blockingFirst()
|
||||
|
||||
assertSame(mwPostResponse, result)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue