Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature branch: Recommended Content #4859

Merged
merged 122 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
b2a0d98
Initial commit
cooltey Aug 6, 2024
a2fc566
Merge branch 'main' into recommended-content-design
cooltey Aug 7, 2024
c384172
More layout included
cooltey Aug 7, 2024
9aee7d4
Merge branch 'recommended-content-design' of github.com:wikimedia/app…
cooltey Aug 7, 2024
b00f139
Add package and view
cooltey Aug 7, 2024
a35a7b6
Create functions in helper
cooltey Aug 8, 2024
9b8a33f
Merge branch 'main' into recommended-content-design
cooltey Aug 8, 2024
fc8a151
Add more methods
cooltey Aug 8, 2024
4c8f98a
Add functions
cooltey Aug 8, 2024
9dc0862
Lint fix
cooltey Aug 8, 2024
3db9245
Use fragment instead of view
cooltey Aug 8, 2024
2459cda
Fix lint
cooltey Aug 8, 2024
0fef046
ViewModel
cooltey Aug 8, 2024
88e0f2d
Merge branch 'main' into recommended-content-design
cooltey Aug 12, 2024
9212f5b
Add enum and ids
cooltey Aug 12, 2024
8c6ab12
Merge branch 'main' into recommended-content-design
cooltey Aug 15, 2024
3ccef93
Lint
cooltey Aug 15, 2024
1c4d9a8
Make responses to use PageSummary
cooltey Aug 15, 2024
38602cd
Update return state format
cooltey Aug 15, 2024
9deadb9
Add more functions
cooltey Aug 15, 2024
d29e122
Add comment
cooltey Aug 15, 2024
b01cc92
Add qq strings
cooltey Aug 15, 2024
430f168
Intent argument
cooltey Aug 15, 2024
bdd76b0
add recommended content blocks
cooltey Aug 15, 2024
d2abec3
Bind to History
cooltey Aug 15, 2024
df4cb71
Complete history list
cooltey Aug 15, 2024
d05171d
Fix stuff
cooltey Aug 15, 2024
3b1df70
Use correct divider
cooltey Aug 16, 2024
4ed2760
Layout
cooltey Aug 16, 2024
37a478e
Fix inflate layout
cooltey Aug 16, 2024
a82d96f
Adjust view
cooltey Aug 16, 2024
dc2c5c3
Put multiple sections
cooltey Aug 16, 2024
59038a9
More view adjustment
cooltey Aug 16, 2024
36f61bd
Update fragment manager
cooltey Aug 16, 2024
1c5150a
Add demo buttons
cooltey Aug 16, 2024
0cc4add
Adjust layout
cooltey Aug 16, 2024
13c4c03
Add async
cooltey Aug 16, 2024
c1a01cc
Fix logic
cooltey Aug 16, 2024
8f69493
Add more demo options
cooltey Aug 16, 2024
6355f89
Fix fragment issues
cooltey Aug 16, 2024
365a688
Fix crash
cooltey Aug 16, 2024
402b256
Remove log
cooltey Aug 16, 2024
12cd9ce
Merge branch 'main' into recommended-content-design
cooltey Aug 19, 2024
cdbbf0c
Merge branch 'main' into recommended-content-design
cooltey Aug 19, 2024
f8ae8a8
Optimize it
cooltey Aug 19, 2024
16f8446
Search query
cooltey Aug 19, 2024
bb8b026
Refresh fragments after removing from RC
cooltey Aug 19, 2024
5486c8e
List stuff
cooltey Aug 19, 2024
e185350
Add callback
cooltey Aug 19, 2024
39ae6be
Fix lint
cooltey Aug 19, 2024
43db330
Fix lint
cooltey Aug 19, 2024
6144357
Build a term builder for Explore
cooltey Aug 20, 2024
d025170
Take 5
cooltey Aug 20, 2024
b3b3acc
Delete .kotlin/errors/errors-1724120854384.log
cooltey Aug 20, 2024
3f7437c
Use recycler view
cooltey Aug 20, 2024
99e6ada
Remove HTML tag for explore item
cooltey Aug 20, 2024
2e10149
Update term logic
cooltey Aug 20, 2024
83357f0
Merge branch 'main' into recommended-content-design
cooltey Aug 20, 2024
4d4ebc9
Flat list and simply it
cooltey Aug 20, 2024
fca336c
Add progressbar
cooltey Aug 20, 2024
eb83be2
pop it back when error
cooltey Aug 20, 2024
470c58f
Reload history list
cooltey Aug 20, 2024
f7e5e48
Merge branch 'main' into recommended-content-design
cooltey Aug 20, 2024
cf582fd
Remove more button for experiment
cooltey Aug 21, 2024
1f13ba2
Merge branch 'main' into recommended-content-design
cooltey Aug 21, 2024
5d8016c
Merge branch 'main' into recommended-content-design
cooltey Aug 22, 2024
d44e6ad
Design update
cooltey Aug 22, 2024
3e3d85b
Merge branch 'main' into recommended-content-design
cooltey Aug 22, 2024
9e44cbd
Select language tab when add
cooltey Aug 22, 2024
6efbf8e
Padding instead of margin
cooltey Aug 22, 2024
0179bea
Dont pop current fragment
cooltey Aug 23, 2024
d83571f
Merge branch 'main' into recommended-content-design
cooltey Aug 23, 2024
69bf27e
Merge branch 'main' into recommended-content-design
cooltey Aug 26, 2024
862e437
Add underscore
cooltey Aug 26, 2024
43a76fe
Merge branch 'main' into recommended-content-design
cooltey Aug 26, 2024
f6ec57d
Update explore term order and update search term for history entries
cooltey Aug 26, 2024
3595b58
Remove duplicated items
cooltey Aug 26, 2024
cbad747
Update history delete action correctly
cooltey Aug 26, 2024
c0dd1fc
Make sure the list is updated properly
cooltey Aug 26, 2024
9d7b2a4
Reduce duplicate code
cooltey Aug 26, 2024
4d1191a
Update parameter
cooltey Aug 27, 2024
3f71e61
Code clean up a bit
cooltey Aug 27, 2024
731c8de
Merge branch 'main' into recommended-content-design
cooltey Aug 28, 2024
17461a5
Add ABC test and target languages logic for Recommended Content (#4894)
cooltey Aug 28, 2024
8b1a32d
start building survey stuff
cooltey Aug 28, 2024
f4f8255
Rename dialog resources and add strings
cooltey Aug 28, 2024
33a5ad4
Dialog
cooltey Aug 28, 2024
99ce705
Merge branch 'main' into recommended-content-design
cooltey Aug 29, 2024
cbcef7a
Remove padding
cooltey Aug 29, 2024
0d402e8
Update design for the edit patrol
cooltey Aug 29, 2024
677ac32
Further update
cooltey Aug 29, 2024
7ab8b3b
Multiple langs
cooltey Aug 29, 2024
07e4efd
Set up cache and wikiSite properly
cooltey Aug 30, 2024
aabd002
Use dialog message instead of in a view
cooltey Aug 30, 2024
50e1ee2
Adjust dialog a bit
cooltey Aug 30, 2024
69a9b59
Fully custom dialog layout
cooltey Aug 30, 2024
eed7bc1
Analytics for recommended content. (#4910)
dbrant Aug 30, 2024
7166efe
Reorder the constant values
cooltey Aug 30, 2024
438fe66
Remove old codes
cooltey Aug 30, 2024
cdebaa3
Assign wikiSite correctly
cooltey Aug 30, 2024
8abff17
Add preference for survey
cooltey Aug 30, 2024
c3b1caf
Make dialog scrollable
cooltey Aug 30, 2024
4600447
Merge branch 'main' into recommended-content-design
cooltey Aug 30, 2024
5187e69
Add return back
cooltey Aug 31, 2024
3c7fa3b
Refine the search term logic
cooltey Aug 31, 2024
cadee28
Merge branch 'main' into recommended-content-design
dbrant Sep 3, 2024
01eff9c
Change to preProd and remove comments
cooltey Sep 3, 2024
3397857
Enforce end-date for Recommended Content experiment. (#4915)
dbrant Sep 3, 2024
d75537f
Dont show message in the input field dialog
cooltey Sep 3, 2024
f1e0622
Remove unnecessary variable
cooltey Sep 3, 2024
125dca6
Use style instead of deprecated method for resizing dialog window. (#…
dbrant Sep 4, 2024
1ad4ffb
Merge branch 'main' into recommended-content-design
cooltey Sep 4, 2024
21b1ad8
Code review addressed
cooltey Sep 4, 2024
d7a1621
Update logic of getting news item
cooltey Sep 4, 2024
032b033
Merge branch 'main' into recommended-content-design
cooltey Sep 4, 2024
2b4aef5
Some style update
cooltey Sep 4, 2024
fe349e1
Remove unused import
cooltey Sep 4, 2024
3a938ab
Better news logic
cooltey Sep 4, 2024
b751ba7
Update the date
cooltey Sep 4, 2024
9c0b2ed
Merge branch 'main' into recommended-content-design
cooltey Sep 5, 2024
2ab799b
Simplify stuff
cooltey Sep 5, 2024
ef629ce
Merge branch 'main' into recommended-content-design
cooltey Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/java/org/wikipedia/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object Constants {
const val ARG_TITLE = "title"
const val ARG_WIKISITE = "wikiSite"
const val ARG_TEXT = "text"
const val ARG_BOOLEAN = "boolean"
const val INTENT_APP_SHORTCUT_CONTINUE_READING = "appShortcutContinueReading"
const val INTENT_APP_SHORTCUT_RANDOMIZER = "appShortcutRandomizer"
const val INTENT_APP_SHORTCUT_SEARCH = "appShortcutSearch"
Expand Down Expand Up @@ -105,6 +106,7 @@ object Constants {
USER_CONTRIB_ACTIVITY("userContribActivity"),
EDIT_ADD_IMAGE("editAddImage"),
SUGGESTED_EDITS_RECENT_EDITS("suggestedEditsRecentEdits"),
RECOMMENDED_CONTENT("recommendedContent"),
}

enum class ImageEditType(name: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.wikipedia.analytics.eventplatform
package org.wikipedia.analytics

import org.wikipedia.settings.PrefsIoUtil
import kotlin.random.Random
Expand All @@ -24,6 +24,7 @@ open class ABTest(private val abTestName: String, private val abTestGroupCount:
companion object {
private const val AB_TEST_KEY_PREFIX = "ab_test_"
const val GROUP_SIZE_2 = 2
const val GROUP_SIZE_3 = 3
const val GROUP_1 = 0
const val GROUP_2 = 1
const val GROUP_3 = 2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.wikipedia.analytics.metricsplatform

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.wikimedia.metrics_platform.context.PageData
import org.wikipedia.dataclient.page.PageSummary
import org.wikipedia.json.JsonUtil
import org.wikipedia.page.PageFragment
import org.wikipedia.page.PageTitle
import org.wikipedia.settings.Prefs
Expand Down Expand Up @@ -201,9 +204,14 @@ class ArticleTocInteraction(private val fragment: PageFragment, private val numS
}
}

class ArticleLinkPreviewInteraction : TimedMetricsEvent {
open class ArticleLinkPreviewInteraction : TimedMetricsEvent {
private val pageData: PageData?
private val source: Int
var source: Int

constructor(source: Int) {
this.source = source
this.pageData = null
}

constructor(fragment: PageFragment, source: Int) {
this.source = source
Expand All @@ -221,28 +229,73 @@ class ArticleLinkPreviewInteraction : TimedMetricsEvent {
}

fun logLinkClick() {
submitEvent("linkclick")
submitEvent("linkclick", ContextData(timeSpentMillis = timer.elapsedMillis))
}

fun logNavigate() {
submitEvent(if (Prefs.isLinkPreviewEnabled) "navigate" else "disabled")
open fun logNavigate() {
submitEvent(if (Prefs.isLinkPreviewEnabled) "navigate" else "disabled", ContextData(timeSpentMillis = timer.elapsedMillis))
}

fun logCancel() {
submitEvent("cancel")
submitEvent("cancel", ContextData(timeSpentMillis = timer.elapsedMillis))
}

private fun submitEvent(action: String) {
protected fun submitEvent(action: String, contextData: ContextData) {
submitEvent(
"android.product_metrics.article_link_preview_interaction",
"article_link_preview_interaction",
getInteractionData(
action,
null,
source.toString(),
"time_spent_ms.${timer.elapsedMillis}",
JsonUtil.encodeToString(contextData)
),
pageData
)
}

@Serializable
class ContextData(
@SerialName("group_assigned") val groupAssigned: String? = null,
@SerialName("time_spent_ms") val timeSpentMillis: Long? = null,
@SerialName("rec_shown") val recShown: Boolean? = null,
@SerialName("feedback_shown") val feedbackShown: Boolean? = null,
@SerialName("feedback_select") val feedbackSelect: String? = null,
@SerialName("feedback_text") val feedbackText: String? = null,
)
}

class ExperimentalLinkPreviewInteraction(
source: Int,
private val groupAssigned: String,
private val recommendationsShown: Boolean? = null
) : ArticleLinkPreviewInteraction(source) {

fun logImpression(feedbackShown: Boolean? = null, feedbackSelect: String? = null,
feedbackText: String? = null) {
submitEvent("impression", ContextData(
timeSpentMillis = timer.elapsedMillis,
groupAssigned = groupAssigned,
recShown = recommendationsShown,
feedbackShown = feedbackShown,
feedbackSelect = feedbackSelect,
feedbackText = feedbackText
))
}

override fun logNavigate() {
logNavigate(null, null, null)
}

fun logNavigate(feedbackShown: Boolean? = null, feedbackSelect: String? = null,
feedbackText: String? = null) {
submitEvent("navigate", ContextData(
timeSpentMillis = timer.elapsedMillis,
groupAssigned = groupAssigned,
recShown = recommendationsShown,
feedbackShown = feedbackShown,
feedbackSelect = feedbackSelect,
feedbackText = feedbackText
))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.wikipedia.analytics.metricsplatform

import org.wikipedia.analytics.ABTest

class RecommendedContentABCTest : ABTest("recommendedContent", GROUP_SIZE_3) {
fun getGroupName(): String {
return when (group) {
GROUP_2 -> "general"
GROUP_3 -> "personal"
else -> "control"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.wikipedia.analytics.metricsplatform

import org.wikipedia.util.GeoUtil
import org.wikipedia.util.ReleaseUtil
import java.time.LocalDate

class RecommendedContentAnalyticsHelper {

companion object {
val abcTest = RecommendedContentABCTest()

private val enabledCountries = listOf(
// sub-saharan africa
"AO", "BJ", "BW", "IO", "BF", "BI", "CV", "CM", "CF", "TD", "KM", "CG", "IC", "CD", "DJ", "GQ", "ER",
"SZ", "ET", "GA", "GM", "GH", "GN", "GW", "KE", "LS", "LR", "MG", "MW", "ML", "MR", "YT", "MZ", "NA",
"NE", "NG", "RE", "RW", "SH", "ST", "SN", "SC", "SL", "SO", "ZA", "SS", "TG", "UG", "TZ", "ZM", "ZW",
// south asia
"IN", "PK", "BD", "LK", "MU", "MV", "NP", "BT", "AF"
)

val recommendedContentEnabled get() = ReleaseUtil.isPreBetaRelease ||
(enabledCountries.contains(GeoUtil.geoIPCountry.orEmpty()) &&
LocalDate.now() <= LocalDate.of(2024, 10, 2))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ class ArticleEditDetailsFragment : Fragment(), WatchlistExpiryDialog.Callback, M
if (Prefs.showOneTimeRecentEditsFeedbackForm) {
sendPatrollerExperienceEvent("toolbar_first_feedback", "pt_feedback")
}
SurveyDialog.showFeedbackOptionsDialog(requireActivity(), InvokeSource.SUGGESTED_EDITS_RECENT_EDITS)
SurveyDialog.showFeedbackOptionsDialog(requireActivity(), invokeSource = InvokeSource.SUGGESTED_EDITS_RECENT_EDITS)
}

private fun updateActionButtons() {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/wikipedia/history/HistoryEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@ class HistoryEntry(
const val SOURCE_SINGLE_WEBVIEW = 40
const val SOURCE_SUGGESTED_EDITS_RECENT_EDITS = 41
const val SOURCE_FEED_PLACES = 42
const val SOURCE_RECOMMENDED_CONTENT_PERSONALIZED = 43
const val SOURCE_RECOMMENDED_CONTENT_GENERALIZED = 44
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@ interface HistoryEntryWithImageDao {
else SearchResults(entries.take(3).map { SearchResult(toHistoryEntry(it).title, SearchResult.SearchResultType.HISTORY) }.toMutableList())
}

fun filterHistoryItemsWithoutTime(searchQuery: String = ""): List<HistoryEntry> {
return findEntriesBySearchTerm("%${normalizedQuery(searchQuery)}%").map { toHistoryEntry(it) }
}

fun filterHistoryItems(searchQuery: String): List<Any> {
val list = mutableListOf<Any>()
val normalizedQuery = StringUtils.stripAccents(searchQuery).lowercase(Locale.getDefault())
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")

val entries = findEntriesBySearchTerm("%$normalizedQuery%")
val entries = findEntriesBySearchTerm("%${normalizedQuery(searchQuery)}%")

for (i in entries.indices) {
// Check the previous item, see if the times differ enough
Expand Down Expand Up @@ -78,6 +77,13 @@ interface HistoryEntryWithImageDao {
return entries.map { toHistoryEntry(it) }
}

private fun normalizedQuery(searchQuery: String): String {
return StringUtils.stripAccents(searchQuery).lowercase(Locale.getDefault())
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
}

private fun toHistoryEntry(entryWithImage: HistoryEntryWithImage): HistoryEntry {
val entry = HistoryEntry(entryWithImage.authority, entryWithImage.lang, entryWithImage.apiTitle,
entryWithImage.displayTitle, 0, entryWithImage.namespace, entryWithImage.timestamp,
Expand Down
29 changes: 29 additions & 0 deletions app/src/main/java/org/wikipedia/page/PageFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,22 @@ import org.wikipedia.theme.ThemeChooserDialog
import org.wikipedia.util.ActiveTimer
import org.wikipedia.util.DimenUtil
import org.wikipedia.util.FeedbackUtil
import org.wikipedia.util.ReleaseUtil
import org.wikipedia.util.ResourceUtil
import org.wikipedia.util.ShareUtil
import org.wikipedia.util.ThrowableUtil
import org.wikipedia.util.UriUtil
import org.wikipedia.util.log.L
import org.wikipedia.views.ObservableWebView
import org.wikipedia.views.PageActionOverflowView
import org.wikipedia.views.SurveyDialog
import org.wikipedia.views.ViewUtil
import org.wikipedia.watchlist.WatchlistExpiry
import org.wikipedia.watchlist.WatchlistExpiryDialog
import org.wikipedia.wiktionary.WiktionaryDialog
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeUnit

class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback,
ReferenceDialog.Callback, WiktionaryDialog.Callback, WatchlistExpiryDialog.Callback {
Expand Down Expand Up @@ -685,6 +688,31 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi
}
}

private fun maybeShowRecommendedContentSurvey() {
if (Prefs.recommendedContentSurveyShown) {
return
}
historyEntry?.let {
val duration = if (ReleaseUtil.isDevRelease) 1L else 10L
binding.pageContentsContainer.postDelayed({
if (!isAdded) {
return@postDelayed
}
if (it.source == HistoryEntry.SOURCE_RECOMMENDED_CONTENT_PERSONALIZED ||
it.source == HistoryEntry.SOURCE_RECOMMENDED_CONTENT_GENERALIZED) {
SurveyDialog.showFeedbackOptionsDialog(
requireActivity(),
titleId = R.string.recommended_content_survey_dialog_title,
messageId = R.string.recommended_content_survey_dialog_message,
snackbarMessageId = R.string.recommended_content_survey_dialog_submitted_message,
invokeSource = InvokeSource.RECOMMENDED_CONTENT,
historyEntry = it
)
}
}, TimeUnit.SECONDS.toMillis(duration))
}
}

private fun showFindReferenceInPage(referenceAnchor: String,
backLinksList: List<String?>,
referenceText: String) {
Expand Down Expand Up @@ -928,6 +956,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi
webView.visibility = View.VISIBLE
}
maybeShowAnnouncement()
maybeShowRecommendedContentSurvey()
bridge.onMetadataReady()
// Explicitly set the top margin (even though it might have already been set in the setup
// handler), since the page metadata might have altered the lead image display state.
Expand Down
Loading
Loading