Repository: wordpress-mobile/WordPress-Android (language: kotlin)

Issue:
[Prompts] Add attribution property to dashboard card tracking
- update `blogging_prompts_my_site_card_viewed`
  - add property for `attribution` (none, `dayone`, `bloganuary`)
- update `blogging_prompts_my_site_card_answer_prompt_tapped`
  - add property for `attribution` (none, `dayone`, `bloganuary`)
- update `my_site_create_sheet_answer_prompt_tapped`
  - add property for `attribution` (none, `dayone`, `bloganuary`)


Discussion hints:
nan

Pull request: [Bloganuary] Add attribution property to dashboard card tracking
Fixes #19594 

-----

## To Test:

- Install Jetpack and log in;
- Open "My Site" -> "HOME" with a blog selected;
- Verify the Blogging Prompts card is visible;
- Verify the track log is shown: `🔵 Tracked: blogging_prompts_my_site_card_viewed` **with the property** `"attribution"`;
- Tap `"Answer prompt` button: verify the track log is shown  `🔵 Tracked: blogging_prompts_my_site_card_answer_prompt_tapped` **with the property** `"attribution"`;
- Tap on `"My Site"` FAB and then tap the `"Answer prompt"` action: verify the track log is shown: `🔵 Tracked: my_site_create_sheet_answer_prompt_tapped` **with the property** `"attribution"`.

> [!WARNING]  
> It's not clear to me how to test all the possibilities for `"attribution"` (none, `dayone` or `bloganuary`)

-----

## Regression Notes

1. Potential unintended areas of impact

    - Blogging prompts cards

2. What I did to test those areas of impact (or what existing automated tests I relied on)

    - Manual testing + updated unit tests

3. What automated tests I added (or what prevented me from doing so)

    - Updated `BloggingPromptsCardTrackHelperTest`, `BloggingPromptCardViewModelSliceTest`, `BloggingPromptsCardAnalyticsTrackerTest`, `WPMainActivityViewModelTest` and `MySiteViewModelTest`.

-----

## PR Submission Checklist:

- [x] I have completed the Regression Notes.
- [x] I have considered adding accessibility improvements for my changes.
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

-----

## UI Changes Testing Checklist:

- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)


Test command: ./gradlew :WordPress:testWordpressVanillaDebugUnitTest --tests "org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptCardViewModelSliceTest" --tests "org.wordpress.android.viewmodel.main.WPMainActivityViewModelTest" --tests "org.wordpress.android.ui.mysite.BloggingPromptsCardTrackHelperTest" --tests "org.wordpress.android.ui.mysite.MySiteViewModelTest" --tests "org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptsCardAnalyticsTrackerTest"

Fix patch:
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/CompactBloggingPromptCardViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/CompactBloggingPromptCardViewHolder.kt
index b6793405407a..8d9fb79b03ed 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/main/CompactBloggingPromptCardViewHolder.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/main/CompactBloggingPromptCardViewHolder.kt
@@ -24,7 +24,7 @@ class CompactBloggingPromptCardViewHolder(
         setupAttributionContainer(action.attribution)
 
         answerButton.setOnClickListener {
-            action.onClickAction?.invoke(action.promptId)
+            action.onClickAction?.invoke(action.promptId, action.attribution)
         }
         promptHelpButtonContainer.setOnClickListener {
             action.onHelpAction?.invoke()
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MainActionListItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MainActionListItem.kt
index f7fe0d24a730..cfbac905fc29 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/main/MainActionListItem.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MainActionListItem.kt
@@ -31,7 +31,7 @@ sealed class MainActionListItem {
         val isAnswered: Boolean,
         val promptId: Int,
         val attribution: BloggingPromptAttribution,
-        val onClickAction: ((promptId: Int) -> Unit)?,
+        val onClickAction: ((promptId: Int, attribution: BloggingPromptAttribution) -> Unit)?,
         val onHelpAction: (() -> Unit)?
     ) : MainActionListItem()
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelper.kt
index 6fa89ea3b74d..08dc088750e3 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelper.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelper.kt
@@ -30,9 +30,14 @@ class BloggingPromptsCardTrackHelper @Inject constructor(
     private val waitingToTrack = AtomicBoolean(false)
     private val currentSite = AtomicReference<Int?>(null)
 
-    private fun onDashboardRefreshed() {
+    private fun onDashboardRefreshed(state: MySiteViewModel.State.SiteSelected?) {
+        val bloggingPromptCards = state?.dashboardData
+            ?.filterIsInstance<BloggingPromptCard>()
+            ?: listOf()
+
         latestPromptCardVisible.get()?.let { isPromptCardVisible ->
-            if (isPromptCardVisible) tracker.trackMySiteCardViewed()
+            val attribution = bloggingPromptCards.firstOrNull()?.attribution
+            if (isPromptCardVisible) tracker.trackMySiteCardViewed(attribution)
             waitingToTrack.set(false)
         } ?: run {
             waitingToTrack.set(true)
@@ -40,7 +45,11 @@ class BloggingPromptsCardTrackHelper @Inject constructor(
     }
 
 
-    fun onDashboardCardsUpdated(scope: CoroutineScope, bloggingPromptCards: List<BloggingPromptCard>) {
+    fun onDashboardCardsUpdated(scope: CoroutineScope, state: MySiteViewModel.State.SiteSelected?) {
+        val bloggingPromptCards = state?.dashboardData
+            ?.filterIsInstance<BloggingPromptCard>()
+            ?: listOf()
+
         // cancel any existing job (debouncing mechanism)
         dashboardUpdateDebounceJob?.cancel()
 
@@ -52,7 +61,8 @@ class BloggingPromptsCardTrackHelper @Inject constructor(
 
             latestPromptCardVisible.set(isVisible)
             if (isVisible && waitingToTrack.getAndSet(false)) {
-                tracker.trackMySiteCardViewed()
+                val attribution = bloggingPromptCards.firstOrNull()?.attribution
+                tracker.trackMySiteCardViewed(attribution)
             }
         }.also {
             it.invokeOnCompletion { cause ->
@@ -62,17 +72,22 @@ class BloggingPromptsCardTrackHelper @Inject constructor(
         }
     }
 
-    fun onResume() {
-        onDashboardRefreshed()
+    fun onResume(state: MySiteViewModel.State.SiteSelected?) {
+        onDashboardRefreshed(state)
     }
 
-    fun onSiteChanged(siteId: Int?) {
+    fun onSiteChanged(siteId: Int?, state: MySiteViewModel.State.SiteSelected?) {
         if (currentSite.getAndSet(siteId) != siteId) {
             latestPromptCardVisible.set(null)
-            onDashboardRefreshed()
+            onDashboardRefreshed(state)
         }
     }
 
+    private val BloggingPromptCard.attribution: String?
+        get() {
+            return (this as? BloggingPromptCard.BloggingPromptCardWithData)?.attribution?.value
+        }
+
     companion object {
         private const val PROMPT_CARD_VISIBLE_DEBOUNCE = 500L
     }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt
index 8b05a07112a9..dfa72c20e01d 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt
@@ -288,13 +288,13 @@ class MySiteViewModel @Inject constructor(
 
                 bloggingPromptCardViewModelSlice.onDashboardCardsUpdated(
                     viewModelScope,
-                    state.dashboardData.filterIsInstance<MySiteCardAndItem.Card.BloggingPromptCard>()
+                    state as? SiteSelected
                 )
                 state
             } else {
                 buildNoSiteState(currentAvatarUrl, avatarName)
             }
-            bloggingPromptCardViewModelSlice.onSiteChanged(site?.id)
+            bloggingPromptCardViewModelSlice.onSiteChanged(site?.id, state as? SiteSelected)
 
             dashboardCardPlansUtils.onSiteChanged(site?.id, state as? SiteSelected)
 
@@ -695,7 +695,7 @@ class MySiteViewModel @Inject constructor(
         isSiteSelected = false
         checkAndShowJetpackFullPluginInstallOnboarding()
         checkAndShowQuickStartNotice()
-        bloggingPromptCardViewModelSlice.onResume()
+        bloggingPromptCardViewModelSlice.onResume(uiModel.value as? SiteSelected)
         dashboardCardPlansUtils.onResume(uiModel.value as? SiteSelected)
         quickLinksItemViewModelSlice.onResume()
     }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt
index 453c3bca8c5e..2d678e1d15f9 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt
@@ -5,24 +5,25 @@ import androidx.annotation.StringRes
 import org.wordpress.android.R
 
 enum class BloggingPromptAttribution(
+    val value: String,
     @StringRes val contentRes: Int,
     @DrawableRes val iconRes: Int,
 ) {
-    NO_ATTRIBUTION(-1, -1),
+    NO_ATTRIBUTION("", -1, -1),
     DAY_ONE(
+        "dayone",
         R.string.my_site_blogging_prompt_card_attribution_dayone,
         R.drawable.ic_dayone_24dp,
     ),
     BLOGANUARY(
+        "bloganuary",
         R.string.my_site_blogging_prompt_card_attribution_bloganuary,
         R.drawable.ic_bloganuary_24dp,
     );
 
     companion object {
-        fun fromString(value: String): BloggingPromptAttribution = when (value) {
-            "dayone" -> DAY_ONE
-            "bloganuary" -> BLOGANUARY
-            else -> NO_ATTRIBUTION
-        }
+        fun fromString(value: String): BloggingPromptAttribution = entries
+            .firstOrNull { it.value == value }
+            ?: NO_ATTRIBUTION
     }
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt
index a71e95adccf2..93bc45fe6f0a 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt
@@ -11,10 +11,10 @@ import org.wordpress.android.ui.bloggingprompts.BloggingPromptsPostTagProvider
 import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper
 import org.wordpress.android.ui.mysite.BloggingPromptCardNavigationAction
 import org.wordpress.android.ui.mysite.BloggingPromptsCardTrackHelper
-import org.wordpress.android.ui.mysite.MySiteCardAndItem
 import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams
 import org.wordpress.android.ui.mysite.MySiteSourceManager
 import org.wordpress.android.ui.mysite.MySiteUiState
+import org.wordpress.android.ui.mysite.MySiteViewModel
 import org.wordpress.android.ui.mysite.SelectedSiteRepository
 import org.wordpress.android.ui.mysite.SiteNavigationAction
 import org.wordpress.android.ui.pages.SnackbarMessageHolder
@@ -53,7 +53,7 @@ class BloggingPromptCardViewModelSlice @Inject constructor(
         return MySiteCardAndItemBuilderParams.BloggingPromptCardBuilderParams(
             bloggingPrompt = bloggingPromptUpdate?.promptModel,
             onShareClick = this::onBloggingPromptShareClick,
-            onAnswerClick = this::onBloggingPromptAnswerClick,
+            onAnswerClick = { id -> onBloggingPromptAnswerClick(id, bloggingPromptUpdate?.promptModel?.attribution) },
             onSkipClick = this::onBloggingPromptSkipClick,
             onViewMoreClick = this::onBloggingPromptViewMoreClick,
             onViewAnswersClick = this::onBloggingPromptViewAnswersClick,
@@ -65,8 +65,8 @@ class BloggingPromptCardViewModelSlice @Inject constructor(
         _onNavigation.value = Event(BloggingPromptCardNavigationAction.SharePrompt(message))
     }
 
-    private fun onBloggingPromptAnswerClick(promptId: Int) {
-        bloggingPromptsCardAnalyticsTracker.trackMySiteCardAnswerPromptClicked()
+    private fun onBloggingPromptAnswerClick(promptId: Int, attribution: String?) {
+        bloggingPromptsCardAnalyticsTracker.trackMySiteCardAnswerPromptClicked(attribution)
         val selectedSite = requireNotNull(selectedSiteRepository.getSelectedSite())
         _onNavigation.value = Event(BloggingPromptCardNavigationAction.AnswerPrompt(selectedSite, promptId))
     }
@@ -106,8 +106,12 @@ class BloggingPromptCardViewModelSlice @Inject constructor(
     private fun onBloggingPromptRemoveClick() {
         scope.launch(bgDispatcher) {
             updatePromptsCardEnabled(isEnabled = false).join()
-            _onNavigation.postValue(Event(BloggingPromptCardNavigationAction
-                .CardRemoved(this@BloggingPromptCardViewModelSlice::onBloggingPromptUndoClick)))
+            _onNavigation.postValue(
+                Event(
+                    BloggingPromptCardNavigationAction
+                        .CardRemoved(this@BloggingPromptCardViewModelSlice::onBloggingPromptUndoClick)
+                )
+            )
         }
     }
 
@@ -123,16 +127,18 @@ class BloggingPromptCardViewModelSlice @Inject constructor(
         }
     }
 
-    fun onDashboardCardsUpdated(scope: CoroutineScope,
-                                bloggingPromptCard: List<MySiteCardAndItem.Card.BloggingPromptCard>) {
-        bloggingPromptsCardTrackHelper.onDashboardCardsUpdated(scope, bloggingPromptCard)
+    fun onDashboardCardsUpdated(
+        scope: CoroutineScope,
+        state: MySiteViewModel.State.SiteSelected?
+    ) {
+        bloggingPromptsCardTrackHelper.onDashboardCardsUpdated(scope, state)
     }
 
-    fun onSiteChanged(siteId: Int?) {
-        bloggingPromptsCardTrackHelper.onSiteChanged(siteId)
+    fun onSiteChanged(siteId: Int?, state: MySiteViewModel.State.SiteSelected?) {
+        bloggingPromptsCardTrackHelper.onSiteChanged(siteId, state)
     }
 
-    fun onResume() {
-        bloggingPromptsCardTrackHelper.onResume()
+    fun onResume(state: MySiteViewModel.State.SiteSelected?) {
+        bloggingPromptsCardTrackHelper.onResume(state)
     }
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTracker.kt
index 464dc55b16c4..697c939dc657 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTracker.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTracker.kt
@@ -7,9 +7,9 @@ import javax.inject.Inject
 class BloggingPromptsCardAnalyticsTracker @Inject constructor(
     private val analyticsTracker: AnalyticsTrackerWrapper
 ) {
-    fun trackMySiteCardAnswerPromptClicked() = analyticsTracker.track(
+    fun trackMySiteCardAnswerPromptClicked(attribution: String?) = analyticsTracker.track(
         Stat.BLOGGING_PROMPTS_MY_SITE_CARD_ANSWER_PROMPT_CLICKED,
-        emptyMap()
+        mapOf("attribution" to attribution).filterValues { !it.isNullOrBlank() }
     )
 
     fun trackMySiteCardShareClicked() = analyticsTracker.track(
@@ -57,8 +57,8 @@ class BloggingPromptsCardAnalyticsTracker @Inject constructor(
         emptyMap()
     )
 
-    fun trackMySiteCardViewed() = analyticsTracker.track(
+    fun trackMySiteCardViewed(attribution: String?) = analyticsTracker.track(
         Stat.BLOGGING_PROMPTS_MY_SITE_CARD_VIEWED,
-        emptyMap()
+        mapOf("attribution" to attribution).filterValues { !it.isNullOrBlank() }
     )
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt
index 951841cda068..5b833b9a0160 100644
--- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt
+++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt
@@ -248,8 +248,11 @@ class WPMainActivityViewModel @Inject constructor(
         }
     }
 
-    private fun onAnswerPromptActionClicked(promptId: Int) {
-        analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED)
+    private fun onAnswerPromptActionClicked(promptId: Int, attribution: BloggingPromptAttribution) {
+        analyticsTracker.track(
+            Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED,
+            mapOf("attribution" to attribution.value).filterValues { !it.isNullOrBlank() }
+        )
         _isBottomSheetShowing.postValue(Event(false))
         _createPostWithBloggingPrompt.postValue(promptId)
     }


Test patch:
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelperTest.kt
index 158deabf14eb..253bc5a805a4 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelperTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/BloggingPromptsCardTrackHelperTest.kt
@@ -11,7 +11,11 @@ import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 import org.wordpress.android.BaseUnitTest
+import org.wordpress.android.ui.avatars.TrainOfAvatarsItem
+import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution
 import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptsCardAnalyticsTracker
+import org.wordpress.android.ui.utils.ListItemInteraction
+import org.wordpress.android.ui.utils.UiString
 
 @ExperimentalCoroutinesApi
 class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
@@ -29,25 +33,23 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
     fun `given onResume was called in dashboard, when dashboard cards are received with prompts card, then track once`() =
         test {
             launch {
-                helper.onResume()
+                helper.onResume(siteSelected())
 
                 // with prompt card (transient state)
                 helper.onDashboardCardsUpdated(
-                    this,
-                    mock()
+                    this, siteSelected()
                 )
 
                 delay(10)
 
                 // again with prompt card (final state) to test debounce
                 helper.onDashboardCardsUpdated(
-                    this,
-                    mock()
+                    this, siteSelected()
                 )
 
                 advanceUntilIdle()
 
-                verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed()
+                verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed("bloganuary")
 
                 // need to cancel this internal job to finish the test
                 cancel()
@@ -59,13 +61,12 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
     fun `given onResume was called in dashboard, when dashboard cards are received without prompts card, then don't track`() =
         test {
             launch {
-                helper.onResume()
+                helper.onResume(siteSelected())
 
-                val bloggingPromptCards = mock<List<MySiteCardAndItem.Card.BloggingPromptCard>>()
                 // with prompt card (transient state)
                 helper.onDashboardCardsUpdated(
                     this,
-                    bloggingPromptCards
+                    siteSelected()
                 )
 
                 delay(10)
@@ -73,12 +74,12 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
                 // again without prompt card (final state) to test debounce
                 helper.onDashboardCardsUpdated(
                     this,
-                    listOf()
+                    siteSelected()
                 )
 
                 advanceUntilIdle()
 
-                verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed()
+                verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed("attribution")
 
                 // need to cancel this internal job to finish the test
                 cancel()
@@ -91,11 +92,10 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
     fun `given dashboard cards were received with prompts card, when onResume is called in dashboard, then track once`() =
         test {
             launch {
-                val bloggingPromptCards = mock<List<MySiteCardAndItem.Card.BloggingPromptCard>>()
                 // with prompt card (transient state)
                 helper.onDashboardCardsUpdated(
                     this,
-                    bloggingPromptCards
+                    siteSelected()
                 )
 
                 delay(10)
@@ -103,14 +103,14 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
                 // again with prompt card (final state) to test debounce
                 helper.onDashboardCardsUpdated(
                     this,
-                    bloggingPromptCards
+                    siteSelected()
                 )
 
                 advanceUntilIdle()
 
-                helper.onResume()
+                helper.onResume(siteSelected())
 
-                verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed()
+                verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed("bloganuary")
 
                 // need to cancel this internal job to finish the test
                 cancel()
@@ -122,11 +122,10 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
     fun `given dashboard cards were received without prompts card, when onResume is called in dashboard, then don't track`() =
         test {
             launch {
-                val bloggingPromptCards = mock<List<MySiteCardAndItem.Card.BloggingPromptCard>>()
                 // with prompt card (transient state)
                 helper.onDashboardCardsUpdated(
                     this,
-                    bloggingPromptCards
+                    siteSelected()
                 )
 
                 delay(10)
@@ -134,14 +133,14 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
                 // again without prompt card (final state) to test debounce
                 helper.onDashboardCardsUpdated(
                     this,
-                    listOf()
+                    siteSelected()
                 )
 
                 advanceUntilIdle()
 
-                helper.onResume()
+                helper.onResume(siteSelected())
 
-                verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed()
+                verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed("attribution")
 
                 // need to cancel this internal job to finish the test
                 cancel()
@@ -161,20 +160,19 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
             delay(1000L)
 
             // new site selected
-            helper.onSiteChanged(1)
+            helper.onSiteChanged(1, siteSelected())
 
             // screen resumed
-            helper.onResume()
+            helper.onResume(siteSelected())
 
             // dashboard cards updated with prompt card
             helper.onDashboardCardsUpdated(
-                this,
-                mock()
+                this, siteSelected()
             )
 
             advanceUntilIdle()
 
-            verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed()
+            verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardViewed("bloganuary")
 
             // need to cancel this internal job to finish the test
             cancel()
@@ -184,35 +182,68 @@ class BloggingPromptsCardTrackHelperTest : BaseUnitTest() {
     @Test
     fun `given new site selected, when dashboard cards are updated without prompt card, then don't track`() = test {
         launch {
-            // old site had prompt card
-            val bloggingPromptCards = mock<List<MySiteCardAndItem.Card.BloggingPromptCard>>()
-
             helper.onDashboardCardsUpdated(
                 this,
-                bloggingPromptCards
+                siteSelected().copy(
+                    dashboardData = emptyList()
+                )
             )
 
             // simulate the user was here for a while
             delay(1000L)
 
             // new site selected
-            helper.onSiteChanged(1)
+            helper.onSiteChanged(1, siteSelected())
 
             // screen resumed
-            helper.onResume()
+            helper.onResume(siteSelected())
 
             // dashboard cards updated without prompt card
             helper.onDashboardCardsUpdated(
                 this,
-                listOf()
+                siteSelected()
             )
 
             advanceUntilIdle()
 
-            verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed()
+            verify(bloggingPromptsCardAnalyticsTracker, never()).trackMySiteCardViewed("attribution")
 
             // need to cancel this internal job to finish the test
             cancel()
         }
     }
+
+    private fun siteSelected() = MySiteViewModel.State.SiteSelected(
+        siteInfoHeader = MySiteCardAndItem.SiteInfoHeaderCard(
+            title = "title",
+            url = "url",
+            iconState = MySiteCardAndItem.SiteInfoHeaderCard.IconState.Progress,
+            showTitleFocusPoint = true,
+            showSubtitleFocusPoint = true,
+            showIconFocusPoint = true,
+            onTitleClick = ListItemInteraction.create {},
+            onUrlClick = ListItemInteraction.create {},
+            onIconClick = ListItemInteraction.create {},
+            onSwitchSiteClick = ListItemInteraction.create {},
+        ),
+        dashboardData = listOf(
+            MySiteCardAndItem.Card.BloggingPromptCard.BloggingPromptCardWithData(
+                prompt = UiString.UiStringText("prompt"),
+                respondents = listOf(
+                    TrainOfAvatarsItem.AvatarItem("url")
+                ),
+                numberOfAnswers = 5,
+                isAnswered = true,
+                promptId = 123,
+                tagUrl = "tagUrl",
+                attribution = BloggingPromptAttribution.BLOGANUARY,
+                onShareClick = {},
+                onAnswerClick = {},
+                onSkipClick = {},
+                onViewMoreClick = {},
+                onViewAnswersClick = {},
+                onRemoveClick = {},
+            )
+        ),
+    )
 }
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt
index 715823280275..d7d4b77ff827 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt
@@ -905,11 +905,13 @@ class MySiteViewModelTest : BaseUnitTest() {
     fun `given blogging prompt card, when resuming dashboard, then tracker helper called as expected`() = test {
         initSelectedSite()
 
-        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId)
+        val siteSelected = uiModels.last() as SiteSelected
+
+        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId, siteSelected)
 
         viewModel.onResume()
 
-        verify(bloggingPromptCardViewModelSlice).onResume()
+        verify(bloggingPromptCardViewModelSlice).onResume(siteSelected)
         verify(bloggingPromptCardViewModelSlice, atLeastOnce())
             .onDashboardCardsUpdated(
                 any(),
@@ -921,11 +923,13 @@ class MySiteViewModelTest : BaseUnitTest() {
     fun `given no blogging prompt card, when resuming dashboard, then tracker helper called as expected`() = test {
         initSelectedSite()
 
-        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId)
+        val siteSelected = uiModels.last() as SiteSelected
+
+        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId, siteSelected)
 
         viewModel.onResume()
 
-        verify(bloggingPromptCardViewModelSlice).onResume()
+        verify(bloggingPromptCardViewModelSlice).onResume(siteSelected)
         verify(bloggingPromptCardViewModelSlice, atMost(1))
             .onDashboardCardsUpdated(
                 any(),
@@ -937,11 +941,13 @@ class MySiteViewModelTest : BaseUnitTest() {
     fun `given blogging prompt card, when resuming menu, then tracker helper called as expected`() = test {
         initSelectedSite()
 
-        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId)
+        val siteSelected = uiModels.last() as SiteSelected
+
+        verify(bloggingPromptCardViewModelSlice, atLeastOnce()).onSiteChanged(siteLocalId, siteSelected)
 
         viewModel.onResume()
 
-        verify(bloggingPromptCardViewModelSlice).onResume()
+        verify(bloggingPromptCardViewModelSlice).onResume(siteSelected)
         verify(bloggingPromptCardViewModelSlice, atLeastOnce())
             .onDashboardCardsUpdated(
                 any(),
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt
index 69cd81d89eea..d97bb8a4fec9 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt
@@ -17,12 +17,14 @@ import org.mockito.kotlin.whenever
 import org.wordpress.android.BaseUnitTest
 import org.wordpress.android.R
 import org.wordpress.android.fluxc.model.SiteModel
+import org.wordpress.android.fluxc.model.bloggingprompts.BloggingPromptModel
 import org.wordpress.android.models.ReaderTag
 import org.wordpress.android.ui.bloggingprompts.BloggingPromptsPostTagProvider
 import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper
 import org.wordpress.android.ui.mysite.BloggingPromptCardNavigationAction
 import org.wordpress.android.ui.mysite.BloggingPromptsCardTrackHelper
 import org.wordpress.android.ui.mysite.MySiteSourceManager
+import org.wordpress.android.ui.mysite.MySiteUiState
 import org.wordpress.android.ui.mysite.SelectedSiteRepository
 import org.wordpress.android.ui.mysite.SiteNavigationAction
 import org.wordpress.android.ui.pages.SnackbarMessageHolder
@@ -111,11 +113,17 @@ class BloggingPromptCardViewModelSliceTest : BaseUnitTest() {
 
     @Test
     fun `given blogging prompt card, when answer button is clicked, answer action is called`() = test {
-        val params = viewModelSlice.getBuilderParams(mock())
+        val attribution = "attribution"
+        val bloggingPromptUpdate = mock<MySiteUiState.PartialState.BloggingPromptUpdate>().apply {
+            val mockPromptModel = mock<BloggingPromptModel>()
+            whenever(mockPromptModel.attribution).thenReturn(attribution)
+            whenever(promptModel).thenReturn(mockPromptModel)
+        }
+        val params = viewModelSlice.getBuilderParams(bloggingPromptUpdate)
 
         params.onAnswerClick(123)
 
-        verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardAnswerPromptClicked()
+        verify(bloggingPromptsCardAnalyticsTracker).trackMySiteCardAnswerPromptClicked(attribution)
         assertThat(navigationActions).containsOnly(BloggingPromptCardNavigationAction.AnswerPrompt(site, 123))
     }
 
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTrackerTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTrackerTest.kt
index 61949dbcf00e..97dad4d5a867 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTrackerTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptsCardAnalyticsTrackerTest.kt
@@ -12,10 +12,11 @@ class BloggingPromptsCardAnalyticsTrackerTest {
 
     @Test
     fun `Should track my site card answer prompt clicked`() {
-        classToTest.trackMySiteCardAnswerPromptClicked()
+        val attribution = "attribution"
+        classToTest.trackMySiteCardAnswerPromptClicked(attribution)
         verify(analyticsTracker).track(
             Stat.BLOGGING_PROMPTS_MY_SITE_CARD_ANSWER_PROMPT_CLICKED,
-            emptyMap()
+            mapOf("attribution" to attribution)
         )
     }
 
diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt
index 0e2d8bad965c..36562fb40408 100644
--- a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt
@@ -48,6 +48,7 @@ import org.wordpress.android.ui.main.MainActionListItem.AnswerBloggingPromptActi
 import org.wordpress.android.ui.main.MainActionListItem.CreateAction
 import org.wordpress.android.ui.main.MainFabUiState
 import org.wordpress.android.ui.mysite.SelectedSiteRepository
+import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution
 import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository
 import org.wordpress.android.ui.prefs.AppPrefsWrapper
 import org.wordpress.android.ui.prefs.privacy.banner.domain.ShouldAskPrivacyConsent
@@ -356,7 +357,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() {
 
         val promptId = 123
 
-        action!!.onClickAction?.invoke(promptId)
+        action!!.onClickAction?.invoke(promptId, BloggingPromptAttribution.BLOGANUARY)
         assertThat(viewModel.createPostWithBloggingPrompt.value).isEqualTo(promptId)
     }