Skip to content

Commit 2b57d8a

Browse files
Merge branch 'main' into Notification_time_ago
2 parents f7ec959 + 6cc7458 commit 2b57d8a

File tree

62 files changed

+1319
-139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1319
-139
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ android {
3737
applicationId 'org.wikipedia'
3838
minSdkVersion 21
3939
targetSdkVersion 33
40-
versionCode 50433
40+
versionCode 50434
4141
testApplicationId 'org.wikipedia.test'
4242
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
4343
testInstrumentationRunnerArguments clearPackageData: 'true'

app/src/main/java/org/wikipedia/WikipediaApp.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class WikipediaApp : Application() {
5050
val mainThreadHandler by lazy { Handler(mainLooper) }
5151
val languageState by lazy { AppLanguageState(this) }
5252
val appSessionEvent by lazy { AppSessionEvent() }
53+
5354
val userAgent by lazy {
5455
var channel = ReleaseUtil.getChannel(this)
5556
channel = if (channel.isBlank()) "" else " $channel"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.wikipedia.analytics.eventplatform
2+
3+
import org.wikipedia.settings.PrefsIoUtil
4+
import kotlin.random.Random
5+
6+
open class ABTest(private val abTestName: String, private val abTestGroupCount: Int) {
7+
8+
val group: Int
9+
get() {
10+
testGroup = PrefsIoUtil.getInt(AB_TEST_KEY_PREFIX + abTestName, -1)
11+
if (testGroup == -1) {
12+
assignGroup()
13+
PrefsIoUtil.setInt(AB_TEST_KEY_PREFIX + abTestName, testGroup)
14+
}
15+
return testGroup
16+
}
17+
18+
protected var testGroup: Int = -1
19+
20+
protected open fun assignGroup() {
21+
testGroup = Random(System.currentTimeMillis()).nextInt(Int.MAX_VALUE).mod(abTestGroupCount)
22+
}
23+
24+
companion object {
25+
private const val AB_TEST_KEY_PREFIX = "ab_test_"
26+
const val GROUP_SIZE_2 = 2
27+
const val GROUP_1 = 0
28+
const val GROUP_2 = 1
29+
const val GROUP_3 = 2
30+
}
31+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.wikipedia.analytics.eventplatform
2+
3+
import kotlinx.coroutines.runBlocking
4+
import org.wikipedia.WikipediaApp
5+
import org.wikipedia.auth.AccountUtil
6+
import org.wikipedia.settings.Prefs
7+
import org.wikipedia.util.log.L
8+
9+
class MachineGeneratedArticleDescriptionABCTest : ABTest("mBART25", GROUP_SIZE_2) {
10+
11+
override fun assignGroup() {
12+
super.assignGroup()
13+
if (AccountUtil.isLoggedIn) {
14+
runBlocking {
15+
try {
16+
MachineGeneratedArticleDescriptionsAnalyticsHelper.setUserExperienced()
17+
if (testGroup == GROUP_2 && Prefs.suggestedEditsMachineGeneratedDescriptionsIsExperienced) {
18+
testGroup = GROUP_3
19+
}
20+
} catch (e: Exception) {
21+
L.e(e)
22+
}
23+
}
24+
}
25+
MachineGeneratedArticleDescriptionsAnalyticsHelper().logGroupAssigned(WikipediaApp.instance, testGroup)
26+
}
27+
}
Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,121 @@
11
package org.wikipedia.analytics.eventplatform
22

33
import android.content.Context
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.withContext
6+
import org.wikipedia.WikipediaApp
7+
import org.wikipedia.auth.AccountUtil
8+
import org.wikipedia.dataclient.ServiceFactory
9+
import org.wikipedia.page.PageTitle
10+
import org.wikipedia.settings.Prefs
11+
import org.wikipedia.util.ActiveTimer
412

5-
object MachineGeneratedArticleDescriptionsAnalyticsHelper {
13+
class MachineGeneratedArticleDescriptionsAnalyticsHelper {
14+
15+
private var apiFailed = false
16+
var apiOrderList = emptyList<String>()
17+
var displayOrderList = emptyList<String>()
18+
private var chosenSuggestion = ""
19+
val timer = ActiveTimer()
620

721
fun articleDescriptionEditingStart(context: Context) {
8-
EventPlatformClient.submit(
9-
BreadCrumbLogEvent(
10-
BreadCrumbViewUtil.getReadableScreenName(context),
11-
"ArticleDescriptionEditing.start"
12-
)
13-
)
22+
log(context, composeGroupString() + ".start")
1423
}
1524

1625
fun articleDescriptionEditingEnd(context: Context) {
17-
EventPlatformClient.submit(
18-
BreadCrumbLogEvent(
19-
BreadCrumbViewUtil.getReadableScreenName(context),
20-
"ArticleDescriptionEditing.end"
21-
)
22-
)
26+
log(context, composeGroupString() + ".end")
27+
}
28+
29+
fun logAttempt(context: Context, finalDescription: String, wasChosen: Boolean, wasModified: Boolean, title: PageTitle) {
30+
log(context, composeLogString(title) + ".attempt:$finalDescription" +
31+
".suggestion1:${encode(apiOrderList.first())}" + (if (apiOrderList.size > 1) ".suggestion2:${encode(apiOrderList.last())}" else "") +
32+
getOrderString(wasChosen, chosenSuggestion) + ".modified:$wasModified.timeSpentMs:${timer.elapsedMillis}")
33+
}
34+
35+
fun logSuccess(context: Context, finalDescription: String, wasChosen: Boolean, wasModified: Boolean, title: PageTitle, revId: Long) {
36+
log(context, composeLogString(title) + ".success:$finalDescription" +
37+
".suggestion1:${encode(apiOrderList.first())}" + (if (apiOrderList.size > 1) ".suggestion2:${encode(apiOrderList.last())}" else "") +
38+
getOrderString(wasChosen, chosenSuggestion) + ".modified:$wasModified.timeSpentMs:${timer.elapsedMillis}.revId:$revId")
39+
}
40+
41+
fun logSuggestionsReceived(context: Context, isBlp: Boolean, title: PageTitle) {
42+
apiFailed = false
43+
log(context, composeLogString(title) + ".blp:$isBlp.count:${apiOrderList.size}.suggestion1:${encode(apiOrderList.first())}" +
44+
if (apiOrderList.size > 1) ".suggestion2:${encode(apiOrderList.last())}" else "")
45+
}
46+
47+
fun logSuggestionsShown(context: Context, title: PageTitle) {
48+
log(context, composeLogString(title) + ".count:${displayOrderList.size}.display1:${encode(displayOrderList.first())}" +
49+
if (displayOrderList.size > 1) ".display2:${encode(displayOrderList.last())}" else "")
50+
}
51+
52+
fun logSuggestionChosen(context: Context, suggestion: String, title: PageTitle) {
53+
chosenSuggestion = suggestion
54+
log(context, composeLogString(title) + ".selected:${encode(suggestion)}${getOrderString(true, suggestion)}")
55+
}
56+
57+
fun logSuggestionsDismissed(context: Context, title: PageTitle) {
58+
log(context, composeLogString(title) + ".suggestionsDialog.dismissed")
59+
}
60+
61+
fun logSuggestionReported(context: Context, suggestion: String, reportReasonsList: List<String>, title: PageTitle) {
62+
val reportReasons = reportReasonsList.joinToString("|")
63+
log(context, composeLogString(title) + ".reportDialog.suggestion:${encode(suggestion)}${getOrderString(true, suggestion)}.reasons:$reportReasons.reported")
64+
}
65+
66+
fun logReportDialogDismissed(context: Context) {
67+
log(context, composeGroupString() + ".reportDialog.dismissed")
68+
}
69+
70+
fun logOnboardingShown(context: Context) {
71+
log(context, composeGroupString() + ".onboardingShown")
72+
}
73+
74+
fun logGroupAssigned(context: Context, testGroup: Int) {
75+
log(context, "$MACHINE_GEN_DESC_SUGGESTIONS.groupAssigned:$testGroup")
76+
}
77+
78+
fun logApiFailed(context: Context, throwable: Throwable, title: PageTitle) {
79+
log(context, composeLogString(title) + ".apiError:${throwable.message}")
80+
apiFailed = true
81+
}
82+
83+
private fun log(context: Context, logString: String) {
84+
if (!isUserInExperiment || apiFailed) {
85+
return
86+
}
87+
EventPlatformClient.submit(BreadCrumbLogEvent(BreadCrumbViewUtil.getReadableScreenName(context), logString))
88+
}
89+
90+
private fun getOrderString(wasChosen: Boolean, suggestion: String): String {
91+
return ".chosenApiIndex:${if (!wasChosen) -1 else apiOrderList.indexOf(suggestion) + 1}" +
92+
".chosenDisplayIndex:${if (!wasChosen) -1 else displayOrderList.indexOf(suggestion) + 1}"
93+
}
94+
95+
private fun composeLogString(title: PageTitle): String {
96+
return "${composeGroupString()}.lang:${title.wikiSite.languageCode}.title:${encode(title.prefixedText)}"
97+
}
98+
99+
private fun composeGroupString(): String {
100+
return "$MACHINE_GEN_DESC_SUGGESTIONS.group:${abcTest.group}.experienced:${Prefs.suggestedEditsMachineGeneratedDescriptionsIsExperienced}"
101+
}
102+
103+
companion object {
104+
private const val MACHINE_GEN_DESC_SUGGESTIONS = "machineSuggestions"
105+
val abcTest = MachineGeneratedArticleDescriptionABCTest()
106+
var isUserInExperiment = false
107+
108+
// HACK: We're using periods and colons as delimiting characters in these events, so let's
109+
// urlencode just those characters, if they appear in our strings.
110+
private fun encode(str: String): String {
111+
return str.replace(".", "%2E").replace(":", "%3A")
112+
}
113+
114+
suspend fun setUserExperienced() =
115+
withContext(Dispatchers.Default) {
116+
val totalContributions = ServiceFactory.get(WikipediaApp.instance.wikiSite)
117+
.globalUserInfo(AccountUtil.userName!!).query?.globalUserInfo?.editCount ?: 0
118+
Prefs.suggestedEditsMachineGeneratedDescriptionsIsExperienced = totalContributions > 50
119+
}
23120
}
24121
}

app/src/main/java/org/wikipedia/dataclient/Service.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ interface Service {
7575
fun getInfoByPageId(@Query("pageids") pageIds: String): Observable<MwQueryResponse>
7676

7777
@GET(MW_API_PREFIX + "action=query&prop=info|description|pageimages&inprop=varianttitles|displaytitle&redirects=1")
78-
suspend fun getPageTitlesByPageId(@Query("pageids") pageIds: String): MwQueryResponse
78+
suspend fun getPageTitlesByPageIdsOrTitles(@Query("pageids") pageIds: String? = null, @Query("titles") titles: String? = null): MwQueryResponse
7979

8080
@GET(MW_API_PREFIX + "action=query")
8181
suspend fun getPageIds(@Query("titles") titles: String): MwQueryResponse

app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResult.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class MwQueryResult {
2222
@SerialName("wikimediaeditortaskscounts") val editorTaskCounts: EditorTaskCounts? = null
2323
@SerialName("usercontribs") val userContributions: List<UserContribution> = emptyList()
2424
@SerialName("allusers") val allUsers: List<UserInfo>? = null
25+
@SerialName("globaluserinfo") val globalUserInfo: UserInfo? = null
2526

2627
private val redirects: MutableList<Redirect>? = null
2728
private val converted: MutableList<ConvertedTitle>? = null

app/src/main/java/org/wikipedia/dataclient/wikidata/Entities.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.wikipedia.dataclient.wikidata
22

3+
import kotlinx.serialization.SerialName
34
import kotlinx.serialization.Serializable
45
import kotlinx.serialization.json.JsonArray
56
import kotlinx.serialization.json.JsonElement
@@ -27,7 +28,7 @@ class Entities : MwResponse() {
2728
val sitelinks: Map<String, SiteLink> = emptyMap()
2829
val statements: JsonElement? = null
2930
val missing: JsonElement? = null
30-
val lastRevId: Long = 0
31+
@SerialName("lastrevid") val lastRevId: Long = 0
3132

3233
fun getStatements(): Map<String, List<Claims.Claim>> {
3334
return if (statements != null && statements !is JsonArray) {

app/src/main/java/org/wikipedia/descriptions/DescriptionEditActivity.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,52 @@ package org.wikipedia.descriptions
22

33
import android.content.Context
44
import android.content.Intent
5+
import android.os.Bundle
56
import androidx.annotation.ColorInt
67
import org.wikipedia.Constants
78
import org.wikipedia.Constants.InvokeSource
89
import org.wikipedia.R
910
import org.wikipedia.activity.SingleFragmentActivity
11+
import org.wikipedia.analytics.eventplatform.ABTest.Companion.GROUP_1
12+
import org.wikipedia.analytics.eventplatform.MachineGeneratedArticleDescriptionsAnalyticsHelper
1013
import org.wikipedia.history.HistoryEntry
1114
import org.wikipedia.page.ExclusiveBottomSheetPresenter
1215
import org.wikipedia.page.PageActivity
1316
import org.wikipedia.page.PageTitle
1417
import org.wikipedia.page.linkpreview.LinkPreviewDialog
1518
import org.wikipedia.readinglist.AddToReadingListDialog
19+
import org.wikipedia.settings.Prefs
1620
import org.wikipedia.suggestededits.PageSummaryForEdit
1721
import org.wikipedia.util.ClipboardUtil
1822
import org.wikipedia.util.DeviceUtil
1923
import org.wikipedia.util.FeedbackUtil
2024
import org.wikipedia.util.ShareUtil
2125
import org.wikipedia.views.ImagePreviewDialog
26+
import org.wikipedia.views.SuggestedArticleDescriptionsDialog
2227

2328
class DescriptionEditActivity : SingleFragmentActivity<DescriptionEditFragment>(), DescriptionEditFragment.Callback, LinkPreviewDialog.Callback {
2429
enum class Action {
2530
ADD_DESCRIPTION, TRANSLATE_DESCRIPTION, ADD_CAPTION, TRANSLATE_CAPTION, ADD_IMAGE_TAGS
2631
}
2732

33+
override fun onCreate(savedInstanceState: Bundle?) {
34+
super.onCreate(savedInstanceState)
35+
val action = intent.getSerializableExtra(Constants.INTENT_EXTRA_ACTION) as Action
36+
val pageTitle = intent.getParcelableExtra<PageTitle>(EXTRA_TITLE)!!
37+
38+
MachineGeneratedArticleDescriptionsAnalyticsHelper.isUserInExperiment = (action == Action.ADD_DESCRIPTION &&
39+
pageTitle.description.isNullOrEmpty() &&
40+
SuggestedArticleDescriptionsDialog.availableLanguages.contains(pageTitle.wikiSite.languageCode))
41+
42+
val shouldShowAIOnBoarding = MachineGeneratedArticleDescriptionsAnalyticsHelper.isUserInExperiment &&
43+
MachineGeneratedArticleDescriptionsAnalyticsHelper.abcTest.group != GROUP_1
44+
45+
if (action == Action.ADD_DESCRIPTION && Prefs.isDescriptionEditTutorialEnabled) {
46+
Prefs.isDescriptionEditTutorialEnabled = false
47+
startActivity(DescriptionEditTutorialActivity.newIntent(this, shouldShowAIOnBoarding))
48+
}
49+
}
50+
2851
public override fun createFragment(): DescriptionEditFragment {
2952
val invokeSource = intent.getSerializableExtra(Constants.INTENT_EXTRA_INVOKE_SOURCE) as InvokeSource
3053
val action = intent.getSerializableExtra(Constants.INTENT_EXTRA_ACTION) as Action

0 commit comments

Comments
 (0)