mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Move notification API into main commons codebase (#5465)
* Moved the notification API calls out of the data client * Converted the NofificationClient to kotlin and improved its test
This commit is contained in:
parent
1948bab873
commit
3c1cdf18a1
8 changed files with 210 additions and 94 deletions
|
|
@ -19,6 +19,7 @@ import fr.free.nrw.commons.media.PageMediaInterface;
|
|||
import fr.free.nrw.commons.media.WikidataMediaInterface;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.mwapi.UserInterface;
|
||||
import fr.free.nrw.commons.notification.NotificationInterface;
|
||||
import fr.free.nrw.commons.review.ReviewInterface;
|
||||
import fr.free.nrw.commons.upload.UploadInterface;
|
||||
import fr.free.nrw.commons.upload.WikiBaseInterface;
|
||||
|
|
@ -265,6 +266,14 @@ public class NetworkingModule {
|
|||
.get(commonsWikiSite, BuildConfig.COMMONS_URL, ThanksInterface.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public NotificationInterface provideNotificationInterface(
|
||||
@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||
return ServiceFactory
|
||||
.get(commonsWikiSite, BuildConfig.COMMONS_URL, NotificationInterface.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public UserInterface provideUserInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package fr.free.nrw.commons.notification;
|
||||
|
||||
import fr.free.nrw.commons.notification.models.Notification;
|
||||
import org.wikipedia.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
|
||||
|
||||
@Singleton
|
||||
public class NotificationClient {
|
||||
|
||||
private final Service service;
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
|
||||
@Inject
|
||||
public NotificationClient(@Named("commons-service") Service service, @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
||||
this.service = service;
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
}
|
||||
|
||||
public Single<List<Notification>> getNotifications(boolean archived) {
|
||||
return service.getAllNotifications("wikidatawiki|commonswiki|enwiki", archived ? "read" : "!read", null)
|
||||
.map(mwQueryResponse -> mwQueryResponse.query().notifications().list())
|
||||
.flatMap(Observable::fromIterable)
|
||||
.map(notification -> Notification.from(notification))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public Observable<Boolean> markNotificationAsRead(String notificationId) {
|
||||
try {
|
||||
return service.markRead(csrfTokenClient.getTokenBlocking(), notificationId, "")
|
||||
.map(mwQueryResponse -> mwQueryResponse.success());
|
||||
} catch (Throwable throwable) {
|
||||
return Observable.just(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package fr.free.nrw.commons.notification
|
||||
|
||||
import fr.free.nrw.commons.di.NetworkingModule
|
||||
import fr.free.nrw.commons.notification.models.Notification
|
||||
import fr.free.nrw.commons.notification.models.NotificationType
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.util.DateUtil
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
import org.wikipedia.notifications.Notification as WikimediaNotification
|
||||
|
||||
@Singleton
|
||||
class NotificationClient @Inject constructor(
|
||||
@param:Named(NetworkingModule.NAMED_COMMONS_CSRF) private val csrfTokenClient: CsrfTokenClient,
|
||||
private val service: NotificationInterface
|
||||
) {
|
||||
fun getNotifications(archived: Boolean): Single<List<Notification>> =
|
||||
service.getAllNotifications(
|
||||
wikiList = "wikidatawiki|commonswiki|enwiki",
|
||||
filter = if (archived) "read" else "!read",
|
||||
continueStr = null
|
||||
).map {
|
||||
it.query()?.notifications()?.list() ?: emptyList()
|
||||
}.flatMap {
|
||||
Observable.fromIterable(it)
|
||||
}.map {
|
||||
it.toCommonsNotification()
|
||||
}.toList()
|
||||
|
||||
fun markNotificationAsRead(notificationId: String?): Observable<Boolean> {
|
||||
return try {
|
||||
service.markRead(
|
||||
token = csrfTokenClient.tokenBlocking,
|
||||
readList = notificationId,
|
||||
unreadList = ""
|
||||
).map(MwQueryResponse::success)
|
||||
} catch (throwable: Throwable) {
|
||||
Observable.just(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun WikimediaNotification.toCommonsNotification() = Notification(
|
||||
notificationType = NotificationType.UNKNOWN,
|
||||
notificationText = contents?.compactHeader ?: "",
|
||||
date = DateUtil.getMonthOnlyDateString(timestamp),
|
||||
link = contents?.links?.primary?.url ?: "",
|
||||
iconUrl = "",
|
||||
notificationId = id().toString()
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package fr.free.nrw.commons.notification
|
||||
|
||||
import io.reactivex.Observable
|
||||
import org.wikipedia.dataclient.Service
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface NotificationInterface {
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@GET(Service.MW_API_PREFIX + "action=query&meta=notifications¬format=model¬limit=max")
|
||||
fun getAllNotifications(
|
||||
@Query("notwikis") wikiList: String?,
|
||||
@Query("notfilter") filter: String?,
|
||||
@Query("notcontinue") continueStr: String?
|
||||
): Observable<MwQueryResponse?>
|
||||
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(Service.MW_API_PREFIX + "action=echomarkread")
|
||||
fun markRead(
|
||||
@Field("token") token: String,
|
||||
@Field("list") readList: String?,
|
||||
@Field("unreadlist") unreadList: String?
|
||||
): Observable<MwQueryResponse?>
|
||||
}
|
||||
|
|
@ -1,29 +1,13 @@
|
|||
package fr.free.nrw.commons.notification.models
|
||||
|
||||
import org.wikipedia.util.DateUtil
|
||||
|
||||
/**
|
||||
* Created by root on 18.12.2017.
|
||||
*/
|
||||
data class Notification(var notificationType: NotificationType,
|
||||
var notificationText: String,
|
||||
var date: String,
|
||||
var link: String,
|
||||
var iconUrl: String,
|
||||
var notificationId: String) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun from(wikiNotification: org.wikipedia.notifications.Notification): Notification {
|
||||
val contents = wikiNotification.contents
|
||||
val notificationLink = if (contents == null || contents.links == null || contents.links!!.primary == null) "" else contents.links!!.primary!!.url
|
||||
return Notification(
|
||||
NotificationType.UNKNOWN,
|
||||
contents?.compactHeader ?: "",
|
||||
DateUtil.getMonthOnlyDateString(wikiNotification.timestamp),
|
||||
notificationLink,
|
||||
"", wikiNotification.id().toString())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
data class Notification(
|
||||
var notificationType: NotificationType,
|
||||
var notificationText: String,
|
||||
var date: String,
|
||||
var link: String,
|
||||
var iconUrl: String,
|
||||
var notificationId: String
|
||||
)
|
||||
|
|
@ -1,35 +1,47 @@
|
|||
package fr.free.nrw.commons.notification
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import fr.free.nrw.commons.TestCommonsApplication
|
||||
import fr.free.nrw.commons.notification.models.NotificationType
|
||||
import io.reactivex.Observable
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.Service
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||
import org.wikipedia.json.GsonUtil
|
||||
import org.wikipedia.notifications.Notification
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@Config(sdk = [21], application = TestCommonsApplication::class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class NotificationClientTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var service: Service
|
||||
private lateinit var service: NotificationInterface
|
||||
|
||||
@Mock
|
||||
private lateinit var csrfTokenClient: CsrfTokenClient
|
||||
|
||||
@Mock
|
||||
private lateinit var mQueryResponse: MwQueryResponse
|
||||
|
||||
@Mock
|
||||
private lateinit var mQueryResult: MwQueryResult
|
||||
|
||||
@Mock
|
||||
private lateinit var mQueryResultNotificationsList: MwQueryResult.NotificationList
|
||||
@Mock
|
||||
private lateinit var notificationsList: List<Notification>
|
||||
|
||||
private lateinit var notificationClient: NotificationClient
|
||||
|
||||
|
|
@ -39,8 +51,8 @@ class NotificationClientTest {
|
|||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
notificationClient = NotificationClient(service, csrfTokenClient)
|
||||
MockitoAnnotations.openMocks(this)
|
||||
notificationClient = NotificationClient(csrfTokenClient, service)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,12 +61,40 @@ class NotificationClientTest {
|
|||
|
||||
@Test
|
||||
fun getNotificationTest() {
|
||||
Mockito.`when`(service.getAllNotifications(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(Observable.just(mQueryResponse))
|
||||
Mockito.`when`(service.getAllNotifications(anyString(), anyString(), any()))
|
||||
.thenReturn(Observable.just(mQueryResponse))
|
||||
Mockito.`when`(mQueryResponse.query()).thenReturn(mQueryResult)
|
||||
Mockito.`when`(mQueryResult.notifications()).thenReturn(mQueryResultNotificationsList)
|
||||
Mockito.`when`(mQueryResultNotificationsList.list()).thenReturn(notificationsList)
|
||||
notificationClient.getNotifications(true)
|
||||
verify(service).getAllNotifications(eq("wikidatawiki|commonswiki|enwiki"), eq("read"), eq(null))
|
||||
Mockito.`when`(mQueryResultNotificationsList.list()).thenReturn(
|
||||
listOf(
|
||||
createWikimediaNotification(
|
||||
primaryUrl = "foo",
|
||||
compactHeader = "header",
|
||||
timestamp = "2024-01-22T10:12:00Z",
|
||||
notificationId = 1234L
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val result = notificationClient.getNotifications(true).test().values()
|
||||
|
||||
verify(service).getAllNotifications(
|
||||
eq("wikidatawiki|commonswiki|enwiki"),
|
||||
eq("read"),
|
||||
eq(null)
|
||||
)
|
||||
|
||||
val notificationList = result.first()
|
||||
assertEquals(1, notificationList.size)
|
||||
|
||||
with(notificationList.first()) {
|
||||
assertEquals(NotificationType.UNKNOWN, notificationType)
|
||||
assertEquals("header", notificationText)
|
||||
assertEquals("January 22", date)
|
||||
assertEquals("foo", link)
|
||||
assertEquals("", iconUrl)
|
||||
assertEquals("1234", notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,10 +103,33 @@ class NotificationClientTest {
|
|||
@Test
|
||||
fun markNotificationAsReadTest() {
|
||||
Mockito.`when`(csrfTokenClient.tokenBlocking).thenReturn("test")
|
||||
Mockito.`when`(service.markRead(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(Observable.just(mQueryResponse))
|
||||
Mockito.`when`(service.markRead(anyString(), anyString(), anyString()))
|
||||
.thenReturn(Observable.just(mQueryResponse))
|
||||
Mockito.`when`(mQueryResponse.success()).thenReturn(true)
|
||||
notificationClient.markNotificationAsRead("test")
|
||||
verify(service).markRead(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
|
||||
verify(service).markRead(anyString(), anyString(), anyString())
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun createWikimediaNotification(
|
||||
primaryUrl: String, compactHeader: String, timestamp: String, notificationId: Long
|
||||
) = Notification().apply {
|
||||
setId(notificationId)
|
||||
|
||||
setTimestamp(Notification.Timestamp().apply {
|
||||
setUtciso8601(timestamp)
|
||||
})
|
||||
|
||||
contents = Notification.Contents().apply {
|
||||
setCompactHeader(compactHeader)
|
||||
|
||||
links = Notification.Links().apply {
|
||||
setPrimary(
|
||||
GsonUtil.getDefaultGson().toJsonTree(Notification.Link().apply {
|
||||
setUrl(primaryUrl)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -244,17 +244,6 @@ public interface Service {
|
|||
|
||||
// ------- Notifications -------
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@GET(MW_API_PREFIX + "action=query&meta=notifications¬format=model¬limit=max")
|
||||
@NonNull Observable<MwQueryResponse> getAllNotifications(@Query("notwikis") @Nullable String wikiList,
|
||||
@Query("notfilter") @Nullable String filter,
|
||||
@Query("notcontinue") @Nullable String continueStr);
|
||||
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(MW_API_PREFIX + "action=echomarkread")
|
||||
@NonNull Observable<MwQueryResponse> markRead(@Field("token") @NonNull String token, @Field("list") @Nullable String readList, @Field("unreadlist") @Nullable String unreadList);
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@GET(MW_API_PREFIX + "action=query&meta=notifications¬prop=list¬filter=!read¬limit=1")
|
||||
@NonNull Observable<MwQueryResponse> getLastUnreadNotification();
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ public class Notification {
|
|||
return id;
|
||||
}
|
||||
|
||||
public void setId(final long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long key() {
|
||||
return id + wiki().hashCode();
|
||||
}
|
||||
|
|
@ -72,10 +76,18 @@ public class Notification {
|
|||
return contents;
|
||||
}
|
||||
|
||||
public void setContents(@Nullable final Contents contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
@NonNull public Date getTimestamp() {
|
||||
return timestamp != null ? timestamp.date() : new Date();
|
||||
}
|
||||
|
||||
public void setTimestamp(@Nullable final Timestamp timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@NonNull String getUtcIso8601() {
|
||||
return StringUtils.defaultString(timestamp != null ? timestamp.utciso8601 : null);
|
||||
}
|
||||
|
|
@ -127,6 +139,10 @@ public class Notification {
|
|||
public static class Timestamp {
|
||||
@SuppressWarnings("unused,NullableProblems") @Nullable private String utciso8601;
|
||||
|
||||
public void setUtciso8601(@Nullable final String utciso8601) {
|
||||
this.utciso8601 = utciso8601;
|
||||
}
|
||||
|
||||
public Date date() {
|
||||
try {
|
||||
return DateUtil.iso8601DateParse(utciso8601);
|
||||
|
|
@ -148,6 +164,10 @@ public class Notification {
|
|||
return StringUtils.defaultString(url);
|
||||
}
|
||||
|
||||
public void setUrl(@Nullable final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@NonNull public String getTooltip() {
|
||||
return StringUtils.defaultString(tooltip);
|
||||
}
|
||||
|
|
@ -166,6 +186,10 @@ public class Notification {
|
|||
@SuppressWarnings("unused,NullableProblems") @Nullable private List<Link> secondary;
|
||||
private Link primaryLink;
|
||||
|
||||
public void setPrimary(@Nullable final JsonElement primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
@Nullable public Link getPrimary() {
|
||||
if (primary == null) {
|
||||
return null;
|
||||
|
|
@ -215,6 +239,10 @@ public class Notification {
|
|||
return StringUtils.defaultString(compactHeader);
|
||||
}
|
||||
|
||||
public void setCompactHeader(@Nullable final String compactHeader) {
|
||||
this.compactHeader = compactHeader;
|
||||
}
|
||||
|
||||
@NonNull public String getBody() {
|
||||
return StringUtils.defaultString(body);
|
||||
}
|
||||
|
|
@ -226,6 +254,10 @@ public class Notification {
|
|||
@Nullable public Links getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
public void setLinks(@Nullable final Links links) {
|
||||
this.links = links;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnreadNotificationWikiItem {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue