CMM-1990: Add UTM stats card to new Stats Traffic tab#22772
Conversation
Add a new UTM card that displays UTM tracking data with a dropdown category selector (Source/Medium, Campaign/Source/Medium, Source, Medium, Campaign), horizontal bar items with view counts, and a "Show all" detail screen. The data layer is stubbed pending wordpress-rs UTM API bindings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update wordpress-rs to 1253-2cdcba95 which includes UTM UniFFI bindings, and replace the stubbed fetchUtm with a real implementation using StatsUtmParams and the statsUtm() request executor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show the detail screen CTA unconditionally when items are present, matching the Authors card behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stop clearing loadingPeriod in coroutine finally blocks so the onPeriodChanged guard correctly prevents duplicate fetches when LaunchedEffect re-fires. Affects Authors, UTM, MostViewed, and BaseStatsCard (Clicks, SearchTerms, VideoPlays, FileDownloads). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d tests (CMM-1990) Remove unused change fields from UTM UI models and detail activity, make loadingPeriod per-category to prevent races, use category label as dynamic list header, and add UtmViewModelTest with 8 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…il screen (CMM-1990) Replace mutable maps with ConcurrentHashMap in UtmViewModel for thread-safe access. Refactor UtmDetailActivity to fetch data via its own ViewModel instead of receiving all data through Intent extras, avoiding TransactionTooLargeException risk. Add StatsPeriod serialization helpers for passing period info between screens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nTest (CMM-1990) Restore try/finally blocks that clear loadingPeriod after fetch completes in Authors, BaseStatsCard, MostViewed, and UTM ViewModels, reverting the changes from 15702a3. Also add UTM to the expected hidden cards list in StatsCardsConfigurationTest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract helper methods to reduce method length in StatsRepository.fetchUtm and UtmDetailViewModel.fetchData. Suppress SpreadOperator and ReturnCount warnings where alternatives are not practical. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generated by 🚫 Danger |
Project manifest changes for WordPressThe following changes in the --- ./build/reports/diff_manifest/WordPress/wordpressRelease/base_manifest.txt 2026-04-09 09:01:32.121855256 +0000
+++ ./build/reports/diff_manifest/WordPress/wordpressRelease/head_manifest.txt 2026-04-09 09:01:40.981854950 +0000
@@ -205,6 +205,10 @@
android:exported="false"
android:theme="@style/WordPress.NoActionBar" />
<activity
+ android:name="org.wordpress.android.ui.newstats.utm.UtmDetailActivity"
+ android:exported="false"
+ android:theme="@style/WordPress.NoActionBar" />
+ <activity
android:name="org.wordpress.android.ui.newstats.yearinreview.YearInReviewDetailActivity"
android:exported="false"
android:theme="@style/WordPress.NoActionBar" />Go to https://buildkite.com/automattic/wordpress-android/builds/25952/canvas?sid=019d7175-3b6c-423a-ab05-836d3e77671a, click on the |
Project dependencies changeslist! Upgraded Dependencies
rs.wordpress.api:android:trunk-0d94794142482d1b7f9395c0afef57ac991c452e, (changed from trunk-262a778ead5f163f3450d62adfac21fb32048714)
rs.wordpress.api:kotlin:trunk-0d94794142482d1b7f9395c0afef57ac991c452e, (changed from trunk-262a778ead5f163f3450d62adfac21fb32048714)tree +--- project :libs:fluxc
-| \--- rs.wordpress.api:android:trunk-262a778ead5f163f3450d62adfac21fb32048714
-| +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
-| +--- com.squareup.okhttp3:okhttp-tls:5.3.2
-| | +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
-| | +--- com.squareup.okio:okio:3.16.4 (*)
-| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 -> 2.3.20 (*)
-| +--- net.java.dev.jna:jna:5.18.1
-| +--- rs.wordpress.api:kotlin:trunk-262a778ead5f163f3450d62adfac21fb32048714
-| | +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
-| | +--- com.squareup.okhttp3:okhttp-tls:5.3.2 (*)
-| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2 (*)
-| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.20 (*)
-| \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.20 (*)
+| \--- rs.wordpress.api:android:trunk-0d94794142482d1b7f9395c0afef57ac991c452e
+| +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
+| +--- com.squareup.okhttp3:okhttp-tls:5.3.2
+| | +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
+| | +--- com.squareup.okio:okio:3.16.4 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 -> 2.3.20 (*)
+| +--- net.java.dev.jna:jna:5.18.1
+| +--- rs.wordpress.api:kotlin:trunk-0d94794142482d1b7f9395c0afef57ac991c452e
+| | +--- com.squareup.okhttp3:okhttp:5.3.2 (*)
+| | +--- com.squareup.okhttp3:okhttp-tls:5.3.2 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.20 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.20 (*)
-\--- rs.wordpress.api:android:trunk-262a778ead5f163f3450d62adfac21fb32048714 (*)
+\--- rs.wordpress.api:android:trunk-0d94794142482d1b7f9395c0afef57ac991c452e (*) |
Project manifest changes for WordPressThe following changes in the --- ./build/reports/diff_manifest/WordPress/jetpackRelease/base_manifest.txt 2026-04-09 09:01:36.379032386 +0000
+++ ./build/reports/diff_manifest/WordPress/jetpackRelease/head_manifest.txt 2026-04-09 09:01:43.959040987 +0000
@@ -393,6 +393,10 @@
android:exported="false"
android:theme="@style/WordPress.NoActionBar" />
<activity
+ android:name="org.wordpress.android.ui.newstats.utm.UtmDetailActivity"
+ android:exported="false"
+ android:theme="@style/WordPress.NoActionBar" />
+ <activity
android:name="org.wordpress.android.ui.newstats.yearinreview.YearInReviewDetailActivity"
android:exported="false"
android:theme="@style/WordPress.NoActionBar" />Go to https://buildkite.com/automattic/wordpress-android/builds/25952/canvas?sid=019d7175-3b6d-4b81-b186-167c80c50eae, click on the |
|
|
|
|
|
Claude finished @adalpari's task in 3m 16s —— View job Code Review
Overall this is a solid, well-structured feature. The architecture follows existing patterns (per-category state caching, Bugs1. The
The fix is to add Code Quality Issues2. Fragile positional access in The A safer approach is to look up the state directly from the map after the combine fires: val uiState: StateFlow<UtmCardUiState> = combine(
_selectedCategory,
*_categoryStates.values.toTypedArray()
) { _ -> // ignore the array, react to any change
_categoryStates[_selectedCategory.value]?.value
?: UtmCardUiState.Loading
}.stateIn(...)Or simply combine 3. The function splits on internal fun formatUtmName(raw: String): String {
if (!raw.startsWith("[")) return raw
return try {
val arr = org.json.JSONArray(raw)
(0 until arr.length()).joinToString(" / ") { arr.getString(it) }
} catch (_: Exception) {
raw
}
}4. Auth error in detail screen silently drops "Open WP Admin" action
onOpenWpAdmin = if (uiState.isAuthError) {
{ viewModel.getAdminUrl()?.let { openWpAdmin(it, context) } }
} else null5.
Testing6. No tests for The detail ViewModel contains non-trivial logic: Minor Observations
|
The loadingPeriods map was never written to, making the duplicate fetch guard in onPeriodChanged dead code. Add the assignment before launching coroutines in loadData() and onCategoryChanged() so rapid period changes are correctly deduplicated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fragile positional combine with flatMapLatest for safe category state lookup. Use Gson JsonParser instead of manual string splitting to correctly handle commas in UTM values. Wire up Open WP Admin action in detail screen error state for auth error recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hange race (CMM-1990) Extract shared UtmExpandableRow/UtmPostRow into UtmCommonComposables, replace StatsCardErrorContent with a lightweight DetailErrorContent on the detail screen, remove unused per-item change properties from UtmItemData, cancel in-flight fetch jobs on period change to prevent stale data, and track UtmDetailViewModelTest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…(CMM-1990) Drop the previous-period API call and change percentage from UTM stats since this card does not need period diffs. Also add proportional bar backgrounds to expanded top-post rows, matching the web design. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@adalpari The UTM card isn't shown by default - I have to add it. Is this intentional? |
Good catch. I didn't think about this tbh (it's a premium card), and because there are so many cards, maybe we should restrict the number of the default ones. I'll open a new PR selecting those cards by default, and maybe showing the premium ones depending on the account type. So, not a blocker for this PR. (I'm adding it to the default list anyway) |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@adalpari The use a of button to show a menu seems off to me, especially since the button style is identical to the toggle button style used in other cards. Maybe a dropdown with an arrow would work better here?
|
Swap the OutlinedButton for a plain text label with a dropdown arrow icon so the category picker is visually distinct from toggle buttons used in other stats cards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sounds reasonable. Done!
|
|
@adalpari I'm unable to test this with the Fieldguide due to a login issue. Could you privately send a video of this in action with real data? In the meantime, Claude had some suggestions: |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Narrow bare Exception catch to JsonParseException in formatUtmName, use calculateCurrentDateRange instead of unused comparison range in StatsRepository, and adapt SiteCapabilityChecker and SampleUsers to the new wordpress-rs UserCapabilitiesMap type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thank you. I've made some changes and sent you a video. About this point:
I saw it, but I do believe it's better to actually clear loaded period to avoid outdated or corrupted data. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## trunk #22772 +/- ##
==========================================
- Coverage 37.41% 37.33% -0.09%
==========================================
Files 2321 2327 +6
Lines 123775 124627 +852
Branches 16804 16883 +79
==========================================
+ Hits 46312 46527 +215
- Misses 73747 74366 +619
- Partials 3716 3734 +18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
nbradbury
left a comment
There was a problem hiding this comment.
Thanks for the video, looks good! ![]()




Description
Note: Danger is failing because of the code targeting a worpdress-rs PR instead of trunk
Adds a new UTM stats card to the Traffic tab in the new Stats screen, allowing users to view UTM-tagged traffic data broken down by category (Source/Medium, Campaign/Source/Medium, Source, Medium, Campaign).
Key changes:
UtmCardCompose component with category dropdown selectorUtmDetailActivitywith its own ViewModel that independently fetches data (avoids Intent size limits)StatsDataSource.fetchUtm()→StatsRepository.fetchUtm()with comparison period supportConcurrentHashMapfor thread safetyAppPrefsWrapperStatsPeriodserialization helpers (toTypeString/fromTypeString) for passing period info between screensUtmViewModelcovering name formatting, error states, item capping, and top postsAlso includes (from trunk):
Testing instructions
UTM card on Traffic tab:
Note: Fieldguide stats contain UTM data is you select 6 or 12 months period