Skip to content

CMM 1222 add stats top authors card#22561

Merged
adalpari merged 24 commits intotrunkfrom
feat/CMM-1222-Add-stats-top-authors-card
Feb 19, 2026
Merged

CMM 1222 add stats top authors card#22561
adalpari merged 24 commits intotrunkfrom
feat/CMM-1222-Add-stats-top-authors-card

Conversation

@adalpari
Copy link
Copy Markdown
Contributor

@adalpari adalpari commented Feb 5, 2026

Note: Danger is failing because of this

Description

Add a new "Authors" card to the new stats system (/ui/newstats/). The card displays a list of top authors with their view counts, avatars, and change indicators compared to the previous period. Tapping "Show All" opens a detail screen with the complete author list.

This implementation follows the existing Countries card pattern but uses circular avatars instead of flag icons. Also fixes column headers in MostViewed cards to use "Title" and "Referrer" instead of repeating the card title.

Screenshot 2026-02-09 at 15 00 27

Testing instructions

Authors card displays correctly:

  1. Open the app and navigate to the new Stats screen
  2. Scroll to find the "Authors" card
  • Verify the card displays with author names, circular avatars, and view counts
  • Verify the change indicator (green up arrow or red down arrow) appears for each author
  • Verify the progress bar shows relative views compared to the top author

Authors card period changes:

  1. Change the stats period using the period selector (e.g., from "Last 7 days" to "Last 30 days")
  • Verify the Authors card reloads with new data
  • Verify the change indicators update based on the new comparison period

Authors detail screen:

  1. Tap "Show All" on the Authors card
  • Verify the detail screen opens with a summary card showing total views and change
  • Verify all authors are listed with position numbers (1, 2, 3...)
  • Verify each author shows name, avatar, views, and change indicator

Card menu functionality:

  1. Tap the three-dot menu on the Authors card
  • Verify "Remove card" option works
  • Verify "Move up" and "Move down" options work

@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Feb 5, 2026

2 Warnings
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Feb 5, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
FlavorJalapeno
Build TypeDebug
Versionpr22561-47e48a6
Build Number1484
Application IDorg.wordpress.android.prealpha
Commit47e48a6
Installation URL4ku2u9240a1to
Note: Google Login is not supported on these builds.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Feb 5, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
FlavorJalapeno
Build TypeDebug
Versionpr22561-47e48a6
Build Number1484
Application IDcom.jetpack.android.prealpha
Commit47e48a6
Installation URL4es96qq6a2fco
Note: Google Login is not supported on these builds.

…ats-top-authors-card

# Conflicts:
#	WordPress/src/main/java/org/wordpress/android/ui/newstats/datasource/StatsDataSourceImpl.kt
#	gradle/libs.versions.toml
@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Feb 5, 2026

Project manifest changes for WordPress

The following changes in the WordPress's merged AndroidManifest.xml file were detected (build variant: wordpressVanillaRelease):

--- ./build/reports/diff_manifest/WordPress/wordpressVanillaRelease/base_manifest.txt	2026-02-19 09:32:21.041001751 +0000
+++ ./build/reports/diff_manifest/WordPress/wordpressVanillaRelease/head_manifest.txt	2026-02-19 09:32:30.121029422 +0000
@@ -199,6 +199,10 @@
         <activity
             android:name="org.wordpress.android.ui.newstats.countries.CountriesDetailActivity"
             android:exported="false"
+            android:theme="@style/WordPress.NoActionBar" />
+        <activity
+            android:name="org.wordpress.android.ui.newstats.authors.AuthorsDetailActivity"
+            android:exported="false"
             android:theme="@style/WordPress.NoActionBar" /> <!-- Account activities -->
         <activity
             android:name="org.wordpress.android.ui.main.MeActivity"

Go to https://buildkite.com/automattic/wordpress-android/builds/25086/canvas?sid=019c7536-6710-4471-ba62-730c61126272, click on the Artifacts tab and audit the files.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Feb 5, 2026

Project manifest changes for WordPress

The following changes in the WordPress's merged AndroidManifest.xml file were detected (build variant: jetpackVanillaRelease):

--- ./build/reports/diff_manifest/WordPress/jetpackVanillaRelease/base_manifest.txt	2026-02-19 09:33:00.221676325 +0000
+++ ./build/reports/diff_manifest/WordPress/jetpackVanillaRelease/head_manifest.txt	2026-02-19 09:33:10.471711811 +0000
@@ -393,6 +393,10 @@
         <activity
             android:name="org.wordpress.android.ui.newstats.countries.CountriesDetailActivity"
             android:exported="false"
+            android:theme="@style/WordPress.NoActionBar" />
+        <activity
+            android:name="org.wordpress.android.ui.newstats.authors.AuthorsDetailActivity"
+            android:exported="false"
             android:theme="@style/WordPress.NoActionBar" /> <!-- Account activities -->
         <activity
             android:name="org.wordpress.android.ui.main.MeActivity"

Go to https://buildkite.com/automattic/wordpress-android/builds/25086/canvas?sid=019c7536-6718-4c3f-9d08-0537890bfe9f, click on the Artifacts tab and audit the files.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Feb 5, 2026

Project dependencies changes

list
! Upgraded Dependencies
rs.wordpress.api:android:trunk-9edcee430afd18d7d440baf497a763f9b1bb83d9, (changed from trunk-f22a59cb660208f8706b7a2dcc1a9324567a92e4)
rs.wordpress.api:kotlin:trunk-9edcee430afd18d7d440baf497a763f9b1bb83d9, (changed from trunk-f22a59cb660208f8706b7a2dcc1a9324567a92e4)
tree
 +--- project :libs:fluxc
-|    \--- rs.wordpress.api:android:trunk-f22a59cb660208f8706b7a2dcc1a9324567a92e4
-|         +--- 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.10 (*)
-|         +--- net.java.dev.jna:jna:5.18.1
-|         +--- rs.wordpress.api:kotlin:trunk-f22a59cb660208f8706b7a2dcc1a9324567a92e4
-|         |    +--- 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.10 (*)
-|         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.10 (*)
+|    \--- rs.wordpress.api:android:trunk-9edcee430afd18d7d440baf497a763f9b1bb83d9
+|         +--- 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.10 (*)
+|         +--- net.java.dev.jna:jna:5.18.1
+|         +--- rs.wordpress.api:kotlin:trunk-9edcee430afd18d7d440baf497a763f9b1bb83d9
+|         |    +--- 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.10 (*)
+|         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.3.10 (*)
-\--- rs.wordpress.api:android:trunk-f22a59cb660208f8706b7a2dcc1a9324567a92e4 (*)
+\--- rs.wordpress.api:android:trunk-9edcee430afd18d7d440baf497a763f9b1bb83d9 (*)

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 5, 2026

Codecov Report

❌ Patch coverage is 23.45679% with 558 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.16%. Comparing base (234bd8b) to head (47e48a6).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
.../android/ui/newstats/components/StatsCardCommon.kt 0.00% 180 Missing ⚠️
...rdpress/android/ui/newstats/authors/AuthorsCard.kt 0.00% 148 Missing ⚠️
...droid/ui/newstats/authors/AuthorsDetailActivity.kt 0.00% 62 Missing ⚠️
...roid/ui/newstats/datasource/StatsDataSourceImpl.kt 0.00% 52 Missing and 1 partial ⚠️
...ess/android/ui/newstats/countries/CountriesCard.kt 0.00% 40 Missing ⚠️
.../wordpress/android/ui/newstats/NewStatsActivity.kt 0.00% 26 Missing ⚠️
.../android/ui/newstats/components/StatsViewChange.kt 26.08% 16 Missing and 1 partial ⚠️
...d/ui/newstats/countries/CountriesDetailActivity.kt 0.00% 9 Missing ⚠️
...ss/android/ui/newstats/authors/AuthorsViewModel.kt 93.90% 2 Missing and 3 partials ⚠️
...ndroid/ui/newstats/countries/CountriesViewModel.kt 70.58% 2 Missing and 3 partials ⚠️
... and 5 more
Additional details and impacted files
@@            Coverage Diff             @@
##            trunk   #22561      +/-   ##
==========================================
- Coverage   38.18%   38.16%   -0.03%     
==========================================
  Files        2248     2254       +6     
  Lines      112879   113362     +483     
  Branches    15733    15791      +58     
==========================================
+ Hits        43102    43259     +157     
- Misses      66197    66516     +319     
- Partials     3580     3587       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

# Conflicts:
#	WordPress/src/main/java/org/wordpress/android/ui/newstats/datasource/StatsDataSourceImpl.kt
#	gradle/libs.versions.toml
@adalpari
Copy link
Copy Markdown
Contributor Author

adalpari commented Feb 9, 2026

@claude

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 9, 2026

Claude finished @adalpari's task in 1m 44s —— View job


Code Review Complete ✓

I've performed a comprehensive review of the Authors card implementation. Here's my detailed feedback:


🎯 Overall Assessment

This is a well-structured implementation that successfully follows the existing Countries card pattern. The code demonstrates good architectural practices with proper separation of concerns, comprehensive testing, and reusable components. However, there are several areas that need attention.


⚠️ Critical Issues

1. Security: Missing Input Validation (AuthorsViewModel.kt:100-103)

Severity: High

The access token is used without validation:

private fun initializeRepository() {
    accountStore.accessToken?.let { token ->
        statsRepository.init(token)
    }
}

Issue: If accessToken is null, the repository is never initialized, but subsequent operations don't check this. This could lead to crashes when wpComApiClient is accessed (StatsDataSourceImpl.kt:44).

Recommendation:

  • Add explicit null checks and error handling
  • Consider initializing repository in a try-catch block
  • Set error state if initialization fails
private fun initializeRepository() {
    val token = accountStore.accessToken
    if (token == null) {
        _uiState.value = AuthorsCardUiState.Error("Authentication required")
        return
    }
    statsRepository.init(token)
}

2. Potential Memory Leak (AuthorsViewModel.kt:40-43)

Severity: Medium

Cached data is stored in the ViewModel without size limits:

private var allAuthors: List<AuthorUiItem> = emptyList()
private var cachedTotalViews: Long = 0L
private var cachedTotalViewsChange: Long = 0L
private var cachedTotalViewsChangePercent: Double = 0.0

Issue: If the API returns thousands of authors, this could consume significant memory since the list is never cleared. The ViewModel persists across configuration changes.

Recommendation: Consider adding a maximum author limit or clearing cache when appropriate.


🐛 Bugs & Logic Issues

3. Division by Zero Risk (AuthorsCard.kt:149-151)

Severity: Medium

val percentage = if (state.maxViewsForBar > 0) {
    author.views.toFloat() / state.maxViewsForBar.toFloat()
} else 0f

Issue: While protected here, maxViewsForBar is set to 1 when the list is empty (AuthorsViewModel.kt:136), which is inconsistent. If authors list has items but all have 0 views, this could still produce incorrect percentages.

Recommendation: Ensure consistent handling of edge cases where all authors have 0 views.

4. Inconsistent Error Handling (AuthorsViewModel.kt:54-56)

Severity: Low

Hardcoded error message:

if (site == null) {
    _uiState.value = AuthorsCardUiState.Error("No site selected")
    return@launch
}

Issue: This error message is not localized and won't match the user's language preferences. Other error messages come from the repository and may be localized.

Recommendation: Use resourceProvider.getString() for all user-facing error messages.


🚀 Performance Concerns

5. Unnecessary List Iteration (StatsRepository - implied from test)

Severity: Low

Based on the test file (StatsRepositoryAuthorsTest.kt:69-97), the repository fetches both current and previous period data sequentially. This could be optimized.

Recommendation: Consider using async/await to fetch both periods in parallel:

coroutineScope {
    val currentDeferred = async { fetchCurrentPeriod() }
    val previousDeferred = async { fetchPreviousPeriod() }
    val current = currentDeferred.await()
    val previous = previousDeferred.await()
}

6. Redundant State Flow (AuthorsViewModel.kt:35-36)

Severity: Low

private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()

Issue: The isRefreshing state is set to true then immediately back to false in the same function (lines 66-74). The UI likely won't see the true state due to how quickly it changes.

Recommendation: Either remove this if unused, or ensure the state changes are actually visible to observers.


📝 Code Quality Issues

7. Missing Documentation (Multiple Files)

Severity: Low

Key public functions lack KDoc comments:

  • AuthorsViewModel.refresh()
  • AuthorsViewModel.onRetry()
  • AuthorsViewModel.onPeriodChanged()
  • StatsListItem() composable

Recommendation: Add KDoc comments explaining parameters, behavior, and return values for public APIs.

8. Magic String in Detail Activity (AuthorsDetailActivity.kt:73)

Severity: Low

@Suppress("LongParameterList")

Issue: While suppression is valid here, consider if this design could be improved.

Recommendation: Consider using a data class for the parameters:

data class AuthorsDetailParams(
    val authors: List<AuthorUiItem>,
    val totalViews: Long,
    val totalViewsChange: Long,
    val totalViewsChangePercent: Double,
    val dateRange: String
)

9. Inconsistent Naming (StatsCardCommon.kt)

Severity: Very Low

The file contains both card-specific components (StatsCardContainer, StatsCardHeader) and list-specific components (StatsListItem, StatsListHeader).

Recommendation: Consider splitting into StatsCardComponents.kt and StatsListComponents.kt for better organization.


✅ Strengths

  1. Excellent Test Coverage: The ViewModel has comprehensive unit tests covering all major scenarios (471 lines of tests!)
  2. Proper Abstraction: Good use of sealed classes for UI state and shared components
  3. Consistent Pattern: Successfully follows the Countries card pattern, making the codebase more maintainable
  4. Error Handling: Most error cases are handled gracefully with appropriate UI states
  5. Compose Best Practices: Good use of state hoisting, remember, and proper composable structure
  6. Separation of Concerns: Clean separation between data layer, ViewModel, and UI components

📊 Test Coverage Concern

According to Codecov, this PR has only 22.49% patch coverage with 541 lines missing coverage. The main gaps are:

  • StatsCardCommon.kt: 179 lines missing (0% coverage)
  • AuthorsCard.kt: 146 lines missing (0% coverage)
  • AuthorsDetailActivity.kt: 62 lines missing (0% coverage)

Recommendation: While ViewModel tests are excellent, consider adding:

  • Composable preview tests or screenshot tests
  • Integration tests for the detail activity
  • Tests for the common components

🔍 Minor Issues

  1. Unused Parameter Warning (AuthorsCard.kt:184)

    • name parameter in AuthorAvatar is used for contentDescription but could be more semantic
  2. Potential NPE (AuthorsDetailActivity.kt:48)

    • Using ?: arrayListOf() is good, but consider logging when extras are missing
  3. Hardcoded Constants (AuthorsViewModel.kt:23)

    • CARD_MAX_ITEMS = 10 could be shared across all card types via a common constants file

📋 Summary

Category Count
Critical Security Issues 1
Medium Severity Bugs 2
Low Severity Issues 9
Strengths 6

Recommendation: Address the critical security issue (#1) and the potential memory leak (#2) before merging. Other issues can be addressed in follow-up PRs or tickets.


✨ Additional Notes

  • The PR description mentions fixing column headers in MostViewed cards - this is a good cleanup
  • The dependency on wordpress-rs PR (mentioned in dangermattic warning) should be resolved before merge
  • Consider adding integration tests for the full user flow from card → detail screen

@adalpari
Copy link
Copy Markdown
Contributor Author

adalpari commented Feb 9, 2026

2. Potential Memory Leak (AuthorsViewModel.kt:40-43)

Severity: Medium

Cached data is stored in the ViewModel without size limits:

private var allAuthors: List<AuthorUiItem> = emptyList()
private var cachedTotalViews: Long = 0L
private var cachedTotalViewsChange: Long = 0L
private var cachedTotalViewsChangePercent: Double = 0.0

Issue: If the API returns thousands of authors, this could consume significant memory since the list is never cleared. The ViewModel persists across configuration changes.

Recommendation: Consider adding a maximum author limit or clearing cache when appropriate.

This would be a very edge case, so I don't think that having a max will benefit common cases. I'm finding a proper way to clean it, though.

@adalpari
Copy link
Copy Markdown
Contributor Author

adalpari commented Feb 9, 2026

5. Unnecessary List Iteration (StatsRepository - implied from test)

StatsRepository.fetchTopAuthors() at line 754 already uses coroutineScope { async {} } to fetch both current and previous periods in parallel. The reviewer likely looked only at the test file mocking setup and assumed sequential execution.

6. Redundant State Flow (AuthorsViewModel.kt:35-36)

t's observed in NewStatsActivity.kt:249 where it's combined with all other card refresh states to drive the PullToRefreshBox indicator. The true state is visible during the entire fetchTopAuthors() suspend call — it's not an instant flip.

@adalpari adalpari marked this pull request as ready for review February 9, 2026 14:56
@adalpari adalpari requested a review from a team as a code owner February 9, 2026 14:56
@adalpari adalpari removed the request for review from a team February 9, 2026 14:56
@adalpari adalpari requested a review from nbradbury February 9, 2026 14:56
Comment thread gradle/libs.versions.toml Outdated
wordpress-lint = '2.2.0'
wordpress-persistent-edittext = '1.0.2'
wordpress-rs = 'trunk-ceb966c5b7f1af692aa9320b97a0cf2f316bb6c6'
wordpress-rs = '1151-58d91c951e1967880df2365f43bc6d008d5a79b7'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new danger rule to check we are not targeting branches on the RS project anymore. So, the PR cannot be merged until I point this dependency to trunk

@nbradbury
Copy link
Copy Markdown
Contributor

@adalpari While testing this I noticed multiple requests for top-authors when opening stats. Is this expected?

Screenshot 2026-02-09 at 10 19 05 AM

@nbradbury
Copy link
Copy Markdown
Contributor

@adalpari It looks like "Show all" is limited to 10 authors even if there are more. Should it show all authors rather than the top 10?

@adalpari
Copy link
Copy Markdown
Contributor Author

adalpari commented Feb 9, 2026

@adalpari While testing this I noticed multiple requests for top-authors when opening stats. Is this expected?

Screenshot 2026-02-09 at 10 19 05 AM

There should be only two. Let me check it with the "Show all" issue. Bot look like bugs

@nbradbury
Copy link
Copy Markdown
Contributor

@adalpari The "Show all" label appears even if there aren't more authors. Also, Claude is reporting this, but I'm not sure if this is accurate:

In StatsDataSourceImpl.kt, the fetchTopAuthors() method hardcodes StatsGranularity.DAYS
and ignores the num parameter from the date range. When a user selects "Last 7 Days" or
"Last 30 Days", the authors card only shows data for the end date -- not the full period.
This also makes the comparison percentages wrong since the previous period is also a
single day.

@nbradbury
Copy link
Copy Markdown
Contributor

  1. Disable network and refresh the stats screen
  • Verify the Authors card shows an error state with a retry button

I'm not seeing any error on the Authors card, instead I see an offline message that fills the entire screen. I think this is fine but it's not as described.

offline

@adalpari
Copy link
Copy Markdown
Contributor Author

adalpari commented Feb 9, 2026

  1. "Show All" is now showing all the results
  2. I think that always showing the "Show All" is harmless because it just opens the details screen with some extra info.
  3. Multiple calls. I am not able to see more than 2 calls (current and previous period to compare). I see 4 calls in your screenshot. Is it reproducible for you?
  4. Thanks to that, I noticed that we are calling all stats even if some of them are hidden. Will fix it in a separate PR.
  5. About the Claude comment. Yes, something feels off. I'm investigating it. And will let you know when I have an answer/fix

@nbradbury
Copy link
Copy Markdown
Contributor

3. I am not able to see more than 2 calls (current and previous period to compare). I see 4 calls in your screenshot. Is it reproducible for you?

Yes, when I enter the stats screen I see four calls to the authors endpoint. This does not occur when I pull-to-refresh, though.

Screenshot 2026-02-09 at 11 43 42 AM

@sonarqubecloud
Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@adalpari
Copy link
Copy Markdown
Contributor Author

@nbradbury I've fixed what Claude suggested because it was definitely a bug.

On the other hand:

  • I kept the "Show All" for all because of being consistent in opening the detail screen like other cards.
  • Multiple calls: this is another bug, but it is affecting all cards and not only this one. So, I'll open a new PR to solve that and an error calling stats for the hidden card.

So, please, give the PR another go when you have time.

Note: Danger is failing to prevent merging the PR containing a dependency to a RS branch instead of trunk

Copy link
Copy Markdown
Contributor

@nbradbury nbradbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I'll approve this so you can merge it after you update the wordpress-rs hash. :shipit:

@adalpari adalpari changed the title CMM1222 add stats top authors card CMM 1222 add stats top authors card Feb 16, 2026
@sonarqubecloud
Copy link
Copy Markdown

@adalpari adalpari merged commit 18ad789 into trunk Feb 19, 2026
24 checks passed
@adalpari adalpari deleted the feat/CMM-1222-Add-stats-top-authors-card branch February 19, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants